Version: 1.2.0 Date: January 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.14)$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
basePrice 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')": { ... } |
$appliedIfNot |
"$appliedIfNot active(true)": { ... } |
$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.2.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)