Version: 1.7.0 Date: April 2026 Status: Draft
License: This annex is part of the Open Okyline Language Specification and is subject to the same license terms (CC BY-SA 4.0). See the Core Specification for full license details.
This annex defines virtual fields ($field), a mechanism to declare computed values that exist only during validation and can be used in conditional directives. Virtual fields enable scenarios where conditional rules depend on derived values not present in the validated data, such as computed tiers, classifications, or flags.
This annex defines the semantics of virtual fields ($field), a mechanism to declare computed values that exist only during validation and can be used in conditional directives.
Virtual fields require the Expression Language defined in Annex C. Implementations claiming support for virtual fields MUST implement Annex C (Level 3 conformance).
Virtual fields are distinct from:
$compute expressions (Annex C) - reusable expressions for field constraints$defs definitions (Annex D) - reusable schema fragmentsWhile $compute expressions validate field values, virtual fields derive new values from existing data for use in conditional logic.
Virtual fields address scenarios where conditional rules depend on derived values not present in the validated data:
Virtual fields are:
Virtual fields are declared using the $field directive within an object schema. Each virtual field is declared as a separate key-value pair:
{
"$oky": {
"order": {
"$field discountTier": "%CalculateTier",
"total": 1500.00,
"$appliedIf discountTier('GOLD')": {
"loyaltyBonus|@": 50.00
}
}
},
"$compute": {
"CalculateTier": "total >= 1000 ? 'GOLD' : 'STANDARD'"
}
}The syntax is:
"$field <variableName>": "%<ComputeExpressionName>"
$field declaration is a separate JSON key starting with $field followed by the variable name.$compute expression, using the %Name syntax.$appliedIf payloads.$requiredIf or $forbiddenIf field lists (they are computed, not data fields).$field directives MUST be declared at object level, not inside $appliedIf payloads.Virtual field expressions (via their $compute reference) are evaluated in the same context as other expressions at that object level:
parent, root (see §6.3.20)$compute expressions via %Name syntaxVirtual fields are evaluated sequentially in declaration order:
$field in declaration order:
$compute expression$requiredIf, $forbiddenIf, $appliedIf, etc.)Forward Reference Prohibition: A $compute expression referenced by a $field MUST NOT use another virtual field declared later in the same object. This is detected at schema parsing time and produces a schema error.
Virtual fields can form chains where one field depends on another. Each evaluated virtual field becomes immediately available to subsequent virtual field expressions.
{
"$oky": {
"order": {
"$field subtotal": "%ComputeSubtotal",
"$field tax": "%ComputeTax",
"$field total": "%ComputeTotal",
"price": 150,
"quantity": 5,
"$appliedIf total(> 500)": {
"bulkDiscount|@": "APPLIED"
}
}
},
"$compute": {
"ComputeSubtotal": "price * quantity",
"ComputeTax": "subtotal * 0.2",
"ComputeTotal": "subtotal + tax"
}
}In this example: - subtotal references price and quantity, evaluates to 750 - tax references subtotal, evaluates to 150 - total references subtotal and tax, evaluates to 900 - The condition total(> 500) triggers
Invalid example (forward reference):
{
"$oky": {
"order": {
"$field total": "%UsesLater",
"$field subtotal": "%GetSubtotal",
"price": 100,
"quantity": 5
}
},
"$compute": {
"UsesLater": "subtotal + tax",
"GetSubtotal": "price * quantity"
}
}This produces a schema error: $field 'total' references 'subtotal' which is declared later.
Virtual field expressions MAY return any type:
| Result Type | Condition Usage |
|---|---|
| String | $appliedIf fieldName('value') |
| Number | $appliedIf fieldName(>100) |
| Boolean | $appliedIf fieldName(true) |
| Null | $appliedIf fieldName(null) |
If evaluation produces null, the virtual field’s value is null. Use the null literal in conditions to test for this case (see F.4.2).
If a virtual field expression fails to evaluate:
null (not existing)null for the failed fieldVirtual fields are referenced in condition triggers by their name (without any prefix):
fieldName
This is the same syntax as for regular data fields. The validator resolves the name first against virtual fields, then against data fields.
Virtual fields can be used as triggers in value-based conditional directives:
| Directive | Example |
|---|---|
$requiredIf |
"$requiredIf tier('PREMIUM')": ["supportEmail"] |
$requiredIfNot |
"$requiredIfNot active(true)": ["reason"] |
$forbiddenIf |
"$forbiddenIf category('RESTRICTED')": ["publicUrl"] |
$forbiddenIfNot |
"$forbiddenIfNot enabled(true)": ["legacyMode"] |
$appliedIf |
"$appliedIf type('SPECIAL')": { ... } |
$appliedIf (switch) |
See F.4.3 |
Restriction: Virtual fields MUST NOT be used as triggers in existence-based directives ($requiredIfExist, $forbiddenIfExist, $appliedIfExist and their negated variants). These directives test whether a field is present in the JSON data, which is a concept that does not apply to computed values. Using a virtual field as trigger in an IfExist directive produces a schema parsing error.
To test if a virtual field’s value is null, use the null literal in a value-based condition:
{
"$requiredIf computedField(null)": ["fallback"],
"$requiredIfNot computedField(null)": ["derived"]
}Virtual fields can be used with the switch form of $appliedIf:
{
"$oky": {
"order": {
"$field tier": "%ComputeTier",
"amount": 500.00,
"$appliedIf tier": {
"('GOLD')": {
"discount|@": 0.15
},
"('SILVER')": {
"discount|@": 0.10
},
"('BRONZE')": {
"discount|@": 0.05
}
}
}
},
"$compute": {
"ComputeTier": "amount >= 1000 ? 'GOLD' : amount >= 500 ? 'SILVER' : 'BRONZE'"
}
}Virtual fields are visible only within the object where they are declared:
{
"$oky": {
"order": {
"$field orderTier": "%GetTier",
"total": 1500,
"items|[*]": [{
"$appliedIf orderTier('HIGH')": { }
}]
}
},
"$compute": {
"GetTier": "total >= 1000 ? 'HIGH' : 'LOW'"
}
}In this example, orderTier is not accessible from within items array elements. Each array element has its own validation context.
Virtual fields are not inherited through $ref inclusion:
{
"$oky": {
"derived": {
"$ref": "&Base",
"$appliedIf computed(>15)": { }
}
},
"$defs": {
"Base": {
"$field computed": "%DoubleValue",
"value": 10
}
},
"$compute": {
"DoubleValue": "value * 2"
}
}The computed virtual field from Base is not available in derived. Virtual fields must be redeclared if needed.
Each nested object has its own independent scope for virtual fields:
{
"$oky": {
"parent": {
"$field category": "%GetParentType",
"$field isPremium": "%IsPremium",
"parentType": "STANDARD",
"$appliedIf isPremium(true)": {
"parentFeature|@": "premium"
},
"child": {
"$field category": "%GetChildType",
"$field isPremium": "%IsPremium",
"childType": "PREMIUM",
"$appliedIf isPremium(true)": {
"childFeature|@": "premium"
}
}
}
},
"$compute": {
"GetParentType": "parentType",
"GetChildType": "childType",
"IsPremium": "category == 'PREMIUM'"
}
}In this example: - Parent’s category = “STANDARD”, isPremium = false → parentFeature not required - Child’s category = “PREMIUM”, isPremium = true → childFeature required
The same virtual field name (category, isPremium) can be used in both scopes without conflict.
$compute$computeVirtual field values MUST reference $compute expressions. Inline expressions are not supported:
Valid:
{
"$oky": {
"order": {
"$field tier": "%CalculateTier",
"total": 1500.00
}
},
"$compute": {
"CalculateTier": "total >= 1000 ? 'GOLD' : 'STANDARD'"
}
}Invalid:
{
"$oky": {
"order": {
"$field tier": "total >= 1000 ? 'GOLD' : 'STANDARD'",
"total": 1500.00
}
}
}| Feature | $compute |
$field |
|---|---|---|
| Declaration | Root level | Object level |
| Value | Expression string | Reference to $compute (%Name) |
| Purpose | Reusable expressions | Derived values for conditions |
| Usage in constraints | (%Name) in field constraints |
Name in condition triggers |
| Scope | Global within schema | Local to declaring object |
| Chaining | N/A | Can reference earlier virtual fields |
Virtual fields do not exist in the JSON data, so they cannot be directly represented in JSON Schema. The transpiler handles virtual fields by:
x-oky-virtualField-<directive> extension in the if block"not": {} to make the condition always false (JSON Schema cannot evaluate compute expressions)then block for documentation purposes{
"if": {
"x-oky-virtualField-appliedIf": {
"virtualField": "tier",
"compute": "CalculateTier",
"condition": "('GOLD')"
},
"not": {}
},
"then": {
"properties": { ... },
"required": [ ... ]
}
}Virtual field conditions cannot be validated by JSON Schema validators. The transpiled schema:
x-oky- extensions| Feature | Description |
|---|---|
| Syntax | "$field <name>": "%<ComputeName>" |
| Expression | Must reference a $compute expression |
| Trigger syntax | fieldName in value-based conditional directives |
| IfExist restriction | Virtual fields MUST NOT be used in IfExist directives |
| Scope | Local to declaring object; not inherited |
| Chaining | Can reference previously declared virtual fields |
| Forward reference | Referencing a later-declared virtual field is a schema error |
| Null handling | Use fieldName(null) to test for null values |
| Evaluation order | Sequential in declaration order, before conditional directives |
{
"$okylineVersion": "1.4.0",
"$version": "1.0.0",
"$title": "Order Schema with Virtual Fields",
"$oky": {
"order": {
"customerId|@ {5,20}": "CUST-12345",
"orderDate|@ ~$Date~": "2025-06-15",
"items|@ [1,100]": [{
"sku|@": "SKU-001",
"quantity|@ (1..1000)": 2,
"unitPrice|@ (>0)": 49.99
}],
"subtotal|@ (%CheckSubtotal)": 99.98,
"shippingCost|@ (>=0)": 5.00,
"total|@ (%CheckTotal)": 104.98,
"$field orderTotal": "%GetTotal",
"$field itemCount": "%GetItemCount",
"$field orderTier": "%ComputeTier",
"$field isHighValue": "%IsHighValue",
"$field isBulkOrder": "%IsBulkOrder",
"$appliedIf orderTier": {
"('PREMIUM')": {
"priorityShipping|@": true,
"loyaltyPoints|@ (>=0)": 500
},
"('STANDARD')": {
"loyaltyPoints|@ (>=0)": 100
},
"('BASIC')": {
"loyaltyPoints?": 0
}
},
"$requiredIf isHighValue(true)": ["approvalCode"],
"approvalCode|? {10,20}": "APPROVE-12345",
"$appliedIf isBulkOrder(true)": {
"bulkOrderNote|@": "Bulk order processing"
}
}
},
"$compute": {
"GetTotal": "total",
"GetItemCount": "count(items)",
"ComputeTier": "orderTotal >= 500 ? 'PREMIUM' : orderTotal >= 100 ? 'STANDARD' : 'BASIC'",
"IsHighValue": "orderTotal >= 1000",
"IsBulkOrder": "itemCount > 10",
"LineTotal": "quantity * unitPrice",
"CheckSubtotal": "subtotal == sum(items, %LineTotal)",
"CheckTotal": "total == subtotal + shippingCost"
}
}orderTotal - Captures the total for use in other computationsitemCount - Counts items in the orderorderTier - Computes tier based on orderTotal (chained reference)isHighValue - Boolean flag based on orderTotal (chained reference)isBulkOrder - Boolean flag based on itemCount (chained reference)$appliedIf orderTier - Applies different schemas based on computed tier$requiredIf isHighValue(true) - Requires approval code for high-value orders$appliedIf isBulkOrder(true) - Adds bulk order note for large ordersEnd of Annex F - Virtual Fields (Normative)