Okyline Annexes Quick Reference

Version: 1.6.0 Date: April 2026 Status: Draft

Companion to the Okyline Core Language Quick Reference. Covers: computed expressions ($compute), internal references ($defs/$ref), and virtual fields ($field).


C — Expression Language

Okyline includes a pure, deterministic expression language for computed validations. It lets you express cross-field business rules — e.g. total == subtotal * (1 + taxRate) — that cannot be captured by per-field constraints alone. Expressions are declared in a $compute block and attached to fields with (%Name).

C.1 $compute Block

Declared at root level, alongside $oky:

{
  "$compute": {
    "TaxAmount": "subtotal * taxRate",
    "ValidTotal": "total == subtotal + %TaxAmount"
  },
  "$oky": {
    "invoice": {
      "subtotal": 100.0,
      "taxRate": 0.2,
      "total|(%ValidTotal)": 120.0
    }
  }
}

%TaxAmount references another expression; (%ValidTotal) validates the field. %Name.field.deep accesses members of the compute’s result.

Rules: - Expression names: start with letter, then letters/digits/underscores - Circular dependencies → parsing error - Expressions are evaluated lazily when referenced

Parameterized computes — suffix the name with a formal parameter list; call with matching arguments:

"$compute": {
  "CategoryCheck(cat)": "category != cat || rate > 0",
  "InRange(lo, hi)": "it >= lo && it <= hi"
},
"$oky": {
  "age|(%InRange(18, 120))": 30,
  "rateS|(%CategoryCheck('S'))": 20
}

↳ Params shadow sibling fields inside the body; use this.field to unshadow. Args on a field constraint must be literals or dotted paths; full expressions are allowed in compute bodies. Arity mismatch = load error. Params must not collide with special variables (it, parent, index, …). A parameter bound to an object exposes its members via p.field; a list parameter composes with firstOf/at/findFirst/sum/…


C.2 Field Constraint — (%Name)

"fieldName|(%ExpressionName)": exampleValue
Result Validation
true Pass
false Fail — Compute error
Non-boolean / null Fail — Compute error

Constraint exclusivity: (%Name) cannot combine with other (...) constraints (range, comparison). Include range checks inside the expression instead.

Evaluation order: Type → Standard constraints (@, {...}, ~...~) → Computed expression

Container vs element compute:

"field|@ [size] (%ContainerCheck) -> (%ElementCheck)": [10, 20]

↳ Before ->: evaluated once on collection (it = collection). After ->: evaluated per element (it = element).


C.3 Evaluation Context

Prefix Resolves from
(none) Current object
this. Current object (explicit)
parent. Parent of the current context
root. Document root
origin. Origin element (lambda only)
prev. / next. Adjacent elements (lambda only)
first. / last. Collection endpoints (lambda only)

Field resolution: direct name access, dot notation for nested (address.city), missing fields → null, null-safe navigation.

The it variable: - In field constraint: value of the field being validated - In aggregation lambda on scalars: current element - In aggregation lambda on objects: current object (rarely needed — use property names) - Outside any context: null


C.4 List Iteration Context (Lambda Only)

Variable Description Null when
origin Element whose validation triggered the call Never
prev / next Adjacent elements First / Last
first / last Collection endpoints Empty collection
index 0-based iteration index Never
size Collection size Never
Predicate True when
isOrigin Current element is the origin
isFirst Current element is first
isLast Current element is last
"$compute": {
  "IsUnique": "countIf(parent.items, id == origin.id) == 1"
}

↳ Inside the lambda: id = iteration element’s id, origin.id = validated element’s id


C.5 Operators

Operator Description Null behavior
?? Null coalescing (high precedence, short-circuit) Returns right if left is null
+ - * / Arithmetic Null propagates. Exception: string + null → treats null as ""
> < >= <= Comparison Returns null
== != Equality null == null → true
=== !== Strict equality (IEEE-754 bit-exact) Strict
&& \|\| ! Logical Null → false; !null → true
? : Ternary Condition null → false branch

List literals: [a, b, c] — inline list, usable anywhere a value is expected.

Precedence (high to low): ??* /+ - → comparisons → equality → &&||? :


C.6 Date Functions

Function Description Example
date(s, pattern?) Parse to LocalDate (default yyyy-MM-dd) date("2024-03-15")
formatDate(d, pattern?) Format date to string formatDate(d, "dd/MM/yy")"15/03/24"
today() Current system date today()
daysBetween(start, end) Days difference daysBetween("2024-03-15","2024-03-18") → 3
plusDays(d, n) Add days plusDays("2024-02-28", 1)"2024-02-29"
minusDays(d, n) Subtract days
plusMonths(d, n) Add months plusMonths("2024-01-31", 1)"2024-02-29"
minusMonths(d, n) Subtract months
plusYears(d, n) Add years
minusYears(d, n) Subtract years
year(d) month(d) day(d) Extract component month("2024-03-15") → 3
dayOfWeek(d) MON=1 … SUN=7 dayOfWeek("2024-03-15") → 5
dayOfYear(d) 1–366
weekOfYear(d) ISO week 1–53
quarter(d) 1–4 quarter("2024-09-15") → 3
semester(d) 1–2
isWeekend(d) True if Sat/Sun
isLeapYear(d) True if leap year
before(d1, d2) d1 < d2
after(d1, d2) d1 > d2
equals(d1, d2) d1 == d2

C.7 String Functions

Function Description Example
length(s) String length (null → 0) length("hey") → 3
isEmpty(s) Length == 0
isNullOrEmpty(s) Null or empty
isNull(v) v is null
hasValue(v) v is not null (opposite of isNull) hasValue(code) → presence check
substring(s, start, len) Extract substring (len = length, not end index) substring("Hello",1,3)"ell"
substringBefore(s, delim) Before first occurrence substringBefore("a:b:c",":")"a"
substringAfter(s, delim) After first occurrence substringAfter("a:b:c",":")"b:c"
substringBeforeLast(s, d) Before last occurrence
substringAfterLast(s, d) After last occurrence substringAfterLast("a.b.txt",".")"txt"
replace(s, target, repl) Replace all
replaceFirst(s, t, r) Replace first
replaceLast(s, t, r) Replace last
trim(s) ltrim(s) rtrim(s) Trim whitespace
toUpperCase(s) toLowerCase(s) Case conversion
capitalize(s) decapitalize(s) First char case capitalize("hello")"Hello"
startsWith(s, prefix) Check prefix
endsWith(s, suffix) Check suffix
contains(s, search) Substring found
removePrefix(s, p) Remove prefix if present
removeSuffix(s, sf) Remove suffix if present
removeRange(s, start, len) Remove len chars from start removeRange("Hello",1,3)"Ho"
padStart(s, len, ch) Left pad padStart("7",3,"0")"007"
padEnd(s, len, ch) Right pad
repeat(times, ch) Repeat char repeat(5,"*")"*****"
indexOf(s, sub) First index (-1 if not found)
indexOfFirst(s, sub) Alias for indexOf
indexOfLast(s, sub) Last index

String index handling: negative start → clamped to 0, negative length → 0, start > length → "", end > length → clamped. Never throws.

String ↔︎ List:

Function Description Example
chars(s) String → list of characters chars("abc")["a","b","c"]
split(s, sep) Split by literal separator split("a,b,c", ",")["a","b","c"]
join(coll, sep) Join list into string join(["a","b"], "-")"a-b"
countIf(chars(name), in(it, ["a","e","i","o","u"]))  // count vowels
join(filter(split(csv, ","), it != ""), ",")           // clean CSV

C.8 Numeric Functions

Function Description Example
abs(x) Absolute value abs(-5) → 5
sqrt(x) Square root sqrt(9) → 3.0
floor(x, scale?) Round down floor(3.1415, 2) → 3.14
ceil(x, scale?) Round up ceil(3.1415, 2) → 3.15
round(x, scale?, mode?) Round (default HALF_UP) round(3.5, 0, "HALF_EVEN") → 4.0
mod(a, b) Remainder mod(10, 3) → 1
pow(base, exp) Power pow(2, 3) → 8.0
log(x) Natural log
log10(x) Base-10 log log10(1000) → 3.0
toInt(v) Convert to integer toInt(3.7) → 4
toNum(v) Convert to number toNum("42") → 42.0
toStr(v) Convert to string toStr(42)"42"

Rounding modes: HALF_UP (default), HALF_DOWN, HALF_EVEN, UP, DOWN, CEILING, FLOOR

Precision: Exact decimal arithmetic within 6 decimal places. No IEEE-754 artifacts (0.1 + 0.2 = 0.3 exactly). Division by zero → null.

Type promotion: int + int → int, int + decimal → decimal, int / int → decimal (division always returns decimal).


C.9 Aggregation & Utility Functions

Function Object collection Scalar collection Description
sum sum(coll, expr) sum(coll) Sum
average average(coll, expr) average(coll) Mean
min min(coll, expr) min(coll) Minimum
max max(coll, expr) max(coll) Maximum
count count(coll) count(coll) Non-null elements
countAll countAll(coll) countAll(coll) All elements (incl. null)
countIf countIf(coll, pred) countIf(coll, pred) Count where true
exists exists(coll, pred) exists(coll, pred) Any match
notExists notExists(coll, pred) notExists(coll, pred) No match
sumIf sumIf(coll, pred, expr) sumIf(coll, pred, val) Conditional sum
map map(coll, expr) map(coll, expr) Transform each
filter filter(coll, pred) filter(coll, pred) Keep where true
sum(items, quantity * unitPrice)           // object collection
sum([10, 20, 30])                          // scalar collection
filter([1, 2, 3, 4], it > 2)              // → [3, 4]
map(items, toUpperCase(code))              // transform

Membership — in:

Form Example
List literal in(status, ["DRAFT", "SENT"])
Nomenclature in(status, '$INVOICE_STATUS')
Array field in(code, allowedCodes)

null value → false. List as first arg → containsAll semantics.

Lookup — lookup:

lookup(currency, rates)                    // get value by key from map
lookup(key, src) ?? defaultValue           // with fallback
join(map(chars(code), lookup(it, charMap) ?? it), "")  // char-by-char transform

Compute reference — %Name:

"$compute": {
  "LineTotal": "netAmount * (1 + vat)",
  "InvoiceTotal": "sum(lines, %LineTotal)"
}

%LineTotal evaluates LineTotal for each element in the aggregation

List element access — retrieve a single element (null-safe):

Function Returns
firstOf(coll) First element, null if empty
lastOf(coll) Last element, null if empty
findFirst(coll, pred) First matching element, null otherwise
findLast(coll, pred) Last matching element (reverse scan), null otherwise
at(coll, idx) Element at idx (0-based), null if out of bounds / negative

Member access on any expression — chain .field after a function call or parenthesized expression:

firstOf(items).amount
findFirst(lines, cat == 'S').amount
%getParty('S').address.city
at(payments, 0).status

↳ Dotted paths on bare identifiers (parent.x.y) are unchanged — the postfix .field applies after ) or ]. Resolution on null base → null.


C.10 Null Handling Summary

Context Behavior
Arithmetic (+ - * /) Null propagates
String + Null treated as ""
Comparisons (< > >= <=) Returns null
Equality (==) null == null → true; null == x → false
Logical (&& \|\|) Null → false
!null → true
Division by zero → null
Missing field → null
obj.field on null obj → null (null-safe)

D — Internal References ($defs / $ref)

Okyline supports reusable schema fragments to avoid duplicating the same structure across a document. You define templates once in $defs and reference them with $ref in two modes: property-level (a field whose type comes from a definition) or object-level (an object that includes all fields of a template). Included structures can be adapted with $override, $amend, or $remove.

D.1 Definition Repository — $defs

Declared at root level. Contains reusable schema fragments (objects or scalars):

{
  "$defs": {
    "Address": {
      "street|@ {2,100}": "12 rue du Saule",
      "city|@ {2,100}": "Lyon"
    },
    "Email|~$Email~ {5,100}": "user@example.com",
    "Percentage|(0..100)": 50
  }
}

Rules: First-level entries only (no nested paths). Resolution is case-sensitive.


D.2 Reference Syntax — &Name

&Address    → resolves to "Address" in $defs
&Email      → resolves to "Email" in $defs

& = current document’s definition namespace. Unknown name → schema rejected.


D.3 Property-Level Reference — field | $ref

"address | $ref @": "&Address"

address uses Address schema, required (@ is local)

List of referenced elements:

"addresses | $ref [1,10]": ["&Address"]

↳ Array of 1-10 elements, each validated against Address

Constraint categories:

Structural (local, at usage) Value (included from def)
@ ? [min,max] ! % label # {min,max} (min..max) ('A','B') ~pattern~ (%Compute)
"primaryEmail | $ref @": "&Email",
"backupEmail | $ref ?": "&Email"

↳ Same Email definition, different structural constraints per usage


D.4 Object-Level Reference — Structural Composition

{
  "$oky": {
    "Person": {
      "$ref": "&Address",
      "name|@ {2,50}": "Dupond"
    }
  }
}

↳ All Address fields injected into Person + local name field added

Rules: - $ref value = single reference string (one template only) - Object-level $ref MUST target an object definition - Object-level cycles → forbidden (detected at load time) - Property-level recursion → allowed - Field collision → must use $override or $amend (otherwise rejected)


D.5 Template Adaptation — $remove

{
  "AnonymousPerson": {
    "$ref": "&Person",
    "$remove": ["email", "ssn"]
  }
}

↳ Includes Person minus email and ssn

Rules: Array of field names. Each must exist in template. NOT permitted when template contains conditional rules or $compute.


D.6 Field Adaptation — $override / $amend

"fieldName | $override <constraints>": <value>
"fieldName | $amend <constraints>": <value>
Directive Unspecified blocks Flags (@ ? ! #)
$override Removed from effective field Only adapter’s flags kept
$amend Inherited from base Logical OR (base + adapter)

Invariants preserved by both: field type, collection nature, $ref target.

{
  "Employee": {
    "$ref": "&Person",
    "name | $amend @": "John Doe",
    "salary|@ (>=0)": 3000
  }
}

↳ Person’s name|? {1,50} + $amend @ → effective name|@? {1,50} (added @, kept ? and {1,50})


D.7 Order of Application

  1. Template injection — Resolve $ref, inject all fields
  2. Removals — Apply $remove
  3. Adaptations — Apply $override / $amend
  4. Local additions — Add remaining local fields
Error condition Result
$remove targets non-existent field Rejected
$override/$amend targets non-existent field (after removes) Rejected
Both $override and $amend on same field Rejected
Type/collection change via $override/$amend Rejected
Local field collision (no $override/$amend) Rejected
Object-level cycle Rejected
$remove + template with conditionals/$compute Rejected

F — Virtual Fields ($field)

Virtual fields inject computed properties into an object during validation, as if they were present in the JSON data — but they are derived from existing fields, not supplied by the producer. Typical use: compute a tier, a classification, or a flag, then use it as a trigger in conditional directives ($appliedIf, $requiredIf, etc.). They require $compute (Annex C).

F.1 Syntax

{
  "$oky": {
    "order": {
      "$field discountTier": "%CalculateTier",
      "total": 1500.00,
      "$appliedIf discountTier('GOLD')": {
        "loyaltyBonus|@": 50.00
      }
    }
  },
  "$compute": {
    "CalculateTier": "total >= 1000 ? 'GOLD' : 'STANDARD'"
  }
}

discountTier is computed, exists only during validation, used as condition trigger

Syntax: "$field <name>": "%<ComputeName>"


F.2 Rules


F.3 Chaining

Virtual fields evaluate sequentially in declaration order. Each can reference previously declared ones:

"$field subtotal": "%ComputeSubtotal",
"$field tax": "%ComputeTax",
"$field total": "%ComputeTotal"
"ComputeSubtotal": "price * quantity",
"ComputeTax": "subtotal * 0.2",
"ComputeTotal": "subtotal + tax"

tax uses subtotal, total uses both. Order matters.

Evaluation order: Parse → Virtual fields (in order) → Conditional directives → Field constraints


F.4 Usage in Conditionals

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) "$appliedIf tier": { "('GOLD')": {...}, "('SILVER')": {...} }

Null test: "$requiredIf computedField(null)": ["fallback"]


F.5 Scope


F.6 $compute vs $field

$compute $field
Declared at Root level Object level
Value Expression string %ComputeName reference
Purpose Reusable expressions Derived values for conditions
Used via (%Name) in field constraints Name in condition triggers
Scope Global Local to declaring object

Okyline® is a registered trademark of Akwatype.