Version: 1.7.0 Date: May 2026 Status: Draft
Okyline is a declarative language designed to describe the structure and constraints of JSON documents in a lightweight and readable manner. It enriches JSON examples with inline constraints, enabling data validation while keeping schemas human-friendly.
Okyline was designed from the outset to support structural validation, conditional logic, and computed business invariants within JSON document definitions.
Okyline® is a registered trademark of Akwatype. The Open Okyline Language Specification is licensed under the Creative Commons Attribution-ShareAlike 4.0 International License (CC BY-SA 4.0).
Modified versions of this specification must be clearly identified as such and must not be presented as official or endorsed by Akwatype. Use of the Okyline® name must not create confusion regarding the origin, affiliation, or approval of the document.
You are free to:
Under the following terms:
Full license text: https://creativecommons.org/licenses/by-sa/4.0/
A free version of Okyline Studio Free, implementing this specification (including live documentation, JSON Schema generation, and real-time validation), is available online:
https://community.studio.okyline.io
Okyline Studio Free fully supports: - Okyline Core language - Annex C - Expression Language - Annex D - Internal Schema References - Annex E - External imports - Annex F - Virtual Fields
This document is published as a Draft of the Okyline 1.7.0 specification. The term “Draft” refers solely to the ongoing formalization of the specification text.
Okyline is considered stable for practical use and is implemented by the reference implementation. Annex E - External Imports and Versioning - is now part of this specification and implemented by the reference implementation. Like the rest of the specification, its text remains in Draft pending final formalization.
During the Draft phase, the reference implementation serves as the authoritative behavioral reference in case of ambiguity.
## Progressive Adoption Levels (Non-Normative)
Okyline is designed as a progressively adoptable language. Not all features are required for all use cases, and implementations or users may intentionally restrict themselves to a subset of the language.
The following adoption levels are informative only. They do not introduce any additional requirements and do not alter the normative rules defined in the core specification or annexes.
Each level represents a coherent and sufficient usage scope. Moving to a higher level is a deliberate design choice, not an obligation.
| Level | Scope | Specification Coverage | Typical Usage |
|---|---|---|---|
| 1 | Structural and field validation | Core | CRUD payloads, simple APIs |
| 2 | Conditional logic | Core | Structural business variants |
| 3 | Computed business invariants | Annex C | Business coherence rules |
| 4 | Internal schema composition and reuse | Annex D | Complex contract structuring |
| 5 | Virtual fields for conditional logic | Annex F | Derived values in conditions |
| 6 | Registry and versioning | Annex E | Shared schema ecosystems |
This progressive approach enables a smooth and incremental adoption path, starting from a simple JSON example that already validates the structure and inferred types of a payload, and extending gradually to enterprise-scale usage involving schema composition, versioning, and governance.
Conformance to the Okyline specification is defined per annex. An implementation MUST explicitly declare which normative annexes it supports.
If a schema uses any feature defined in a normative annex that the implementation does not support, the implementation MUST reject the schema as unsupported.
This mechanism allows implementations to support coherent subsets of the language while preserving strict and predictable validation semantics.
Conformance to Annex E - External Imports and Versioning REQUIRES conformance to Annex D - Internal Schema References.
An implementation MUST NOT claim conformance to Annex E unless it fully implements Annex D.
Conformance to Annex F - Virtual Fields REQUIRES conformance to Annex C - Expression Language.
An implementation MUST NOT claim conformance to Annex F unless it fully implements Annex C.
Okyline is a declarative language designed to describe the structure and constraints of JSON documents in a lightweight, readable way. It enriches simple JSON examples with inline constraints, making validation easier while keeping schemas human-friendly.
Purpose: Validate JSON data by example.
Design Philosophy: Start with an example JSON document, then add constraints directly to field names.
Modern data contracts and design-first approaches often struggle under the weight of overly complex schema languages. Okyline takes a lighter path.
The best way to describe data is to show examples. The best way to validate data is to define rules.
Okyline lets you do both, in one place, with one syntax.
{
"$oky": {
"name|@ {2,100}|User name": "Julie",
"status|@ ('ACTIVE','INACTIVE')|User status":"ACTIVE",
"$appliedIf status('ACTIVE')": {
"nbrDaysOfActivities|@ (1..22)|Number of days of activities": 22
}
}
}Breakdown: - name|@ {2,100} → required string, length between 2 and 100 - status|@ ('ACTIVE','INACTIVE') → required enumeration with two allowed values - $appliedIf status('ACTIVE') → applies the nested block only when status is “ACTIVE” - nbrDaysOfActivities|@ (1..22) → required integer between 1 and 22
Equivalent schema in JSON Schema
{
"$schema": "http://json-schema.org/draft-07/schema",
"x-oky-generated-from": "okyline",
"type": "object",
"properties": {
"name": {
"type": "string",
"title": "User name",
"examples": ["Julie"],
"minLength": 2,
"maxLength": 100
},
"status": {
"type": "string",
"title": "User status",
"examples": ["ACTIVE"],
"enum": [
"ACTIVE", "INACTIVE"
]
}
},
"required": [
"name", "status"
],
"allOf": [{
"if": {
"properties": {
"status": {
"enum": ["ACTIVE"]
}
},
"required": ["status"]
},
"then": {
"properties": {
"nbrDaysOfActivities": {
"type": "integer",
"title": "Number of days of activities",
"examples": [22],
"minimum": 1,
"maximum": 22
}
},
"required": ["nbrDaysOfActivities"]
}
}]
}The fastest way to understand Okyline is by example:
{
"$oky": {
"username|@ {3,20}": "alice",
"email|@ ~$Email~": "alice@example.com",
"age|@ (18..120)": 25,
"role|('USER','ADMIN')": "USER",
"verified|?": true
}
}Reading this: - username|@ {3,20} → Required string, 3-20 characters - email|@ ~$Email~ → Required, email format - age|@ (18..120) → Required integer, 18-120 - role|(...) → Enum: USER or ADMIN - verified|? → nullable
| Symbol | Type | Meaning | Example |
|---|---|---|---|
@ |
All | Required | "name|@": "value" |
? |
All | nullable | "field|?": "value" |
{min,max} |
String | Length | "name|{2,50}": "Alice" |
(min..max) |
Number | Range | "age|(18..65)": 30 |
('a','b') |
All | Enum | "status|('ACTIVE','INACTIVE')" |
~format~ |
String | Format/regex | "email|~$Email~" |
[min,max] |
Array | Size | "items|[1,10]": [...] |
-> |
Array/Map | Element constraint | "tags|[*] -> {2,10}" |
! |
Array | Uniqueness | "codes|[*]!": ["A","B"] |
# |
Scalar | Key field | "id|#": 123 |
For complete details: See Section 5: Constraint Reference
Okyline does not require explicit type declarations. The field type is automatically inferred from the example value provided.
Principle: The example value determines the type that will be validated.
| Type | Example Value | Description |
|---|---|---|
| String | "Alice" |
Unicode text |
| Integer | 42 |
Whole numbers |
| Number | 3.14 |
Floating-point numbers |
| Boolean | true or false |
Boolean values |
| Array | ["a", "b"] |
Ordered lists |
| Object | {"key": "value"} |
Nested structures |
"street": "Yellow tree avenue" // → String
"age": 42 // → Integer
"price": 35.5 // → Number
"active": true // → Boolean
"tags": ["eco", "garden"] // → Array[String]
"address": {"city": "Paris"} // → ObjectNote: JSON discards trailing zeros (e.g., 78.00 becomes 78). To preserve decimal inference, you can write "78.00" as a string - Okyline automatically converts it to Number. See §6.4.
"scores": [10, 20, 30] // → Array[Integer]
"prices": [10.5, 20.99] // → Array[Number]
"labels": ["A", "B", "C"] // → Array[String]Important: All elements in an array must conform to the inferred type.
Arrays in schema examples MUST contain at least one element for type inference.
// ❌ INVALID - cannot infer type
"tags": []
// ✅ VALID
"tags|[0,10]": ["example"]null cannot be used as an example value. To indicate that a field can be null, use the ? constraint.
// ❌ INVALID
"middleName": null
// ✅ VALID - field can be null
"middleName|?": "John"Okyline does not perform type coercion during validation. A value must exactly match the inferred type.
"age": 42 // Integer expected
// Validation results:
42 → ✅ Valid
"42" → ❌ Invalid (string, not integer)
42.0 → ❌ Invalid (number, not integer)Every field in an Okyline schema follows this pattern:
"fieldName | constraints | label": exampleValue
Components: 1. fieldName (required): The JSON field name 2. constraints (optional): Pipe-separated constraints 3. label (optional): The JSON field label
The base identifier for the field in JSON documents.
Rules: - Must be a valid JSON string - No special restrictions beyond JSON requirements
Constraints applied to the field, separated from the field name by |.
General form: fieldName|constraint1 constraint2 constraint3
Example:
"email|@ ~$Email~": "user@example.com"@ → required field~$Email~ → must match email formatWhitespace policy: Spaces are allowed everywhere for readability. All the following forms are equivalent:
// Compact form (no spaces)
"email|@ ~$Email~": "user@example.com"
// Spaced form (readable)
"email| @ ~$Email~": "user@example.com"
// Fully spaced (maximum readability)
"email | @ ~$Email~ ": "user@example.com"
// Complex example - all equivalent:
"tags|@ [1,10]->{2,20}!": ["eco", "bio"]
"tags|@ [1,10] -> {2,20} !": ["eco", "bio"]
"tags | @ [1, 10] -> {2, 20} !": ["eco", "bio"]
"tags | @ [ 1 , 10 ] -> { 2 , 20 } ! ": ["eco", "bio"]An optional human-readable description placed after the constraints.
Syntax: fieldName|constraints|Description text
Example:
"status|@ ('ACTIVE','INACTIVE')|User account status": "ACTIVE"| (U+007C) after JSON decoding.Purpose: - Self-documenting schemas - UI generation - Developer guidance
Okyline supports inline comments by prefixing attribute names with //.
A JSON key starting with // (U+002F U+002F) is treated as a comment and ignored during parsing.
Syntax: "//anyText": anyValue
When an attribute is commented out, the entire subtree rooted at that attribute is ignored, including the attribute value and all nested children.
Comments are recognized in all structural blocks:
| Block | Example |
|---|---|
$oky |
"//user\|@": {...} |
$nomenclature |
"//COLORS": "RED,GREEN" |
$format |
"//Phone": "^\\+[0-9]+$" |
$defs |
"//Address": {...} |
$compute |
"//total": "a + b" |
Prefix matching: A key is a comment if and only if it starts with exactly // after JSON decoding.
Subtree exclusion: The commented attribute and its entire value subtree MUST be ignored by the parser. No validation, type inference, or constraint processing occurs on commented content.
No nesting effect: Comments do not propagate. Uncommenting a parent does not automatically uncomment its children.
JSON validity: Commented entries MUST remain valid JSON. The value associated with a comment key MUST be a valid JSON value.
The value provided determines the expected type and serves as documentation.
Rules: - MUST be a valid JSON value - MUST NOT be null (use ? constraint instead) - For arrays: MUST contain at least one element - Should represent a realistic example
These constraints apply to string, integer, number, and boolean fields.
@ - Required FieldIndicates that the field MUST be present in validated documents.
Applies to: All types
Example:
"name|@": "Alice"Validation: - {"name": "Bob"} → ✅ Valid - {} → ❌ Invalid (missing required field)
? - Nullable FieldIndicates that the field can contain null values.
Applies to: All types
Example:
"middleName|?": "Marie"Validation: - {"middleName": "John"} → ✅ Valid - {"middleName": null} → ✅ Valid - {} → ✅ Valid (field is optional)
Note: A field can be both required and nullable using @? - the field must be present but can be null.
{...} - String LengthRestricts the character length of string values measured in Unicode code points
Applies to: String only
Syntax variants: - {max} - Maximum length - {min,max} - Minimum and maximum length - {min,*} - Minimum only (no maximum) - {*} - Any length (no constraints)
Examples:
"username|{3,10}": "Alice" // min 3, max 10 characters
"city|{50}": "Paris" // max 50 characters (no minimum)
"code|{5,5}": "ABC12" // exactly 5 characters
"bio|{3,*}": "Hello" // at least 3 characters (no maximum)
"notes|{*}": "free text" // any lengthValidation: - Length is measured in Unicode code points - Empty string has length 0 - Minimum defaults to 0 if not specified
(...) - Value ConstraintsRestricts values to specific sets, ranges, or conditions.
Applies to: Strings, integers, numbers
List specific allowed values separated by commas.
Syntax: ('value1','value2','value3')
Example:
"status|('ACTIVE','INACTIVE','PENDING')": "ACTIVE"Validation: - {"status": "ACTIVE"} → ✅ Valid - {"status": "DELETED"} → ❌ Invalid
Define inclusive ranges using .. notation.
Syntax: (min..max)
Example:
"age|(18..120)": 30
"price|(0..1000)": 49.99Validation: - Boundaries are inclusive: (1..10) accepts both 1 and 10 - Works with integers and numbers
Use comparison operators for one-sided constraints.
Operators: >, <, >=, <=
Examples:
"quantity|(>0)": 5 // strictly greater than 0
"discount|(<=50)": 20 // less than or equal to 50
"score|(>=10)": 85 // greater than or equal to 10String ranges using alphabetical ordering.
Syntax: ('start'..'end')
Example:
"letter|('A'..'Z')": "B" // A through Z (single uppercase letter)
"grade|('A'..'F')": "C" // Letter gradesNote: Comparisons use Unicode lexicographic ordering.
Combine multiple constraints with commas.
Examples:
"value|(1,2..5,>10)": 12 // equals 1, OR between 2-5, OR greater than 10
"code|('A','B',100..200)": "A"Validation logic: Value must satisfy at least one of the listed constraints (logical OR).
Use values from a predefined registry (see §6.1).
Syntax: ($REGISTRY_NAME)
Example:
{
"$oky": {
"favoriteColor|($COLORS)": "RED"
},
"$nomenclature": {
"COLORS": "RED,GREEN,BLUE,YELLOW"
}
}~...~ - Format ValidationValidates strings against regular expressions or semantic formats.
Applies to: String only
Embed a regular expression directly in the constraint.
Regex flavor: Okyline uses ECMA-262 (JavaScript) regular expression syntax.
Syntax: ~pattern~
Examples:
"postalCode|~^[0-9]{5}$~": "75001"
"phoneNumber|~^\\+33[0-9]{9}$~": "+33612345678"
"sku|~^[A-Z]{2}-\\d{4}$~": "AB-1234"Important: - Backslashes must be escaped in JSON strings (\d becomes \\d) - Regex delimiters (/) are not used; the tilde ~ serves as delimiter - Flags are not supported in inline patterns (use anchors ^ $ instead)
ECMA-262 reference: https://262.ecma-international.org/13.0/#sec-regexp-regular-expression-objects
Reference predefined patterns from $format block or use built-in formats.
Syntax: ~$FormatName~
Example:
{
"$oky": {
"zipCode|~$PostalCode~": "75001"
},
"$format": {
"PostalCode": "^[0-9]{5}$"
}
}Non-normative - Implementation note (regex)
Okyline specifies ECMA-262 regex syntax and match semantics. Operational concerns such as regex timeouts, backtracking limits, input size limits, or sandboxing are out of scope for this specification and are left to implementations. Implementations MAY apply safeguards (e.g., timeouts or complexity limits). If a safeguard prevents evaluation, the validator SHOULD report a clear execution error rather than altering match semantics.
Okyline provides standard formats that can be used without declaration.
Semantic validation (validates logical consistency):
| Format | Description | Validation | Example |
|---|---|---|---|
$Date |
ISO 8601 date | Validates leap years, month lengths, day ranges | "2025-05-30" |
$DateTime |
ISO 8601 datetime | Validates date/time consistency, timezone offsets | "2025-05-30T14:30:00Z" |
Syntactic validation with structural constraints:
| Format | Description | Validation | Example |
|---|---|---|---|
$Time |
ISO 8601 time | RFC 3339 syntax (HH:MM:SS + optional fractions/offset) | "14:30:00","14:30:00.123Z" |
$Uri |
URI with scheme | RFC 3986 syntax + port range validation (1-65535) | "https://example.com:8080/path" |
$Ipv4 |
IPv4 address | Dotted-decimal notation, octets 0-255 | "192.168.1.1" |
$Ipv6 |
IPv6 address | RFC 4291 syntax + single :: compression |
"2001:db8::1" |
$Hostname |
Hostname | RFC 1034 labels + total length ≤ 255 chars | "example.com" |
Syntactic validation (pattern matching):
| Format | Description | Example |
|---|---|---|
$Email |
Email address | "user@example.com" |
$Uuid |
UUID (versions 1-5) | "550e8400-e29b-41d4-a716-446655440000" |
Overriding built-in formats:
All built-in formats, including semantic formats, can be overridden in the $format block to accommodate different validation rules or regional formats.
Example - Override $Date for European format:
{
"$oky": {
"birthDate|~$Date~": "15/05/90",
"eventDate|~$Date~": "31/12/25"
},
"$format": {
"Date": "^(0[1-9]|[12]\\d|3[01])/(0[1-9]|1[0-2])/\\d{2}$"
}
}Warning: When overriding semantic formats with regex, you lose semantic validation. The pattern 31/02/25 would be syntactically valid but semantically incorrect (February 31 doesn’t exist).
Example - Override $Email for stricter validation:
{
"$format": {
"Email": "^[a-zA-Z0-9._%+-]+@ [a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$"
},
"$oky": {
"email|~$Email~": "user@example.com"
}
}Validation examples:
// Default $Date - Semantic validation (ISO 8601)
"date|~$Date~": "2024-02-29" // ✅ Valid (2024 is leap year)
"date|~$Date~": "2025-02-29" // ❌ Invalid (2025 is not leap year)
"date|~$Date~": "2025-13-01" // ❌ Invalid (month 13 doesn't exist)
// Custom $Date - Regex validation (DD/MM/YY)
{
"$format": {
"Date": "^(0[1-9]|[12]\\d|3[01])/(0[1-9]|1[0-2])/\\d{2}$"
},
"$oky": {
"date|~$Date~": "29/02/25" // ✅ Syntactically valid (but semantically wrong!)
}
}# - Key FieldMarks a field as an identifier within an object. Used for enforcing uniqueness in lists of objects.
Applies to: Scalars inside objects
Example:
{
"users|@ [1,10] -> !": [
{
"id|#": "user001",
"name": "Alice"
},
{
"id|#": "user002",
"name": "Bob"
}
]
}Behavior: - Key fields are used to determine object uniqueness in lists - Multiple fields can be marked as keys (composite key) - See §5.2.3 for uniqueness validation
% - Default ValueIndicates that the example value is also the default value. This is informational only and does not affect validation.
Applies to: Scalars and collections of scalars
Example:
"country|%": "France",
"theme|%('light','dark')": "light"Use cases: - UI form generators - Documentation - Code generation tools
These constraints apply to array fields.
[...] - List SizeDefines minimum and maximum number of elements in a list.
Syntax variants: - [max] - Maximum size only - [min,max] - Minimum and maximum - [min,*] - Minimum only (no maximum) - [*] - Any size (no constraints)
Examples:
"tags|[1,5]": ["eco", "garden"] // 1 to 5 items
"codes|[10,*]": ["A","B"] // at least 10 items
"letters|[5]": ["A","B"] // max 5 items
"items|[*]": ["x"] // any number of itemsEmpty Lists & Type Inference
Array examples MUST NOT be empty, regardless of their size constraints.
Validation: - Empty lists are valid if minimum is 0 or [*] - Boundaries are inclusive
-> - Element/Value ConstraintsApplies constraints to each element in a collection (list or map).
Syntax: - Arrays: [size] -> constraints - Maps: [key:size] -> constraints
Examples:
// Each tag must be 2-10 characters and unique
"tags|@ [1,5] -> {2,10}!": ["eco", "garden"]
// Each score must be between 0-100
"scores|[*] -> (0..100)": [85, 92, 78]
// Each email must match format
"contacts|[1,10] -> ~$Email~": ["a@example.com", "b@example.com"]Combination with object constraints:
"products|[1,50] -> !": [
{
"sku|#": "ABC123",
"name|@ {2,100}": "Product Name",
"price|@ (0..10000)": 29.99
}
]! - UniquenessEnforces that all elements in a list are unique.
Applies to: Arrays
Behavior: - For scalar lists: uniqueness by value - For object lists: uniqueness by composite key formed from key fields (#)
Examples
Scalar uniqueness:
"codes|[1,10] -> !": ["A001", "B002", "C003"]["A", "B", "C"]["A", "B", "A"] (duplicate “A”)Object-based Uniqueness
When applied to a list of objects, the ! modifier enforces uniqueness based on one or more fields marked with #.
Requirements: - At least one key field (#) MUST be declared - If no key fields are declared, validation MUST fail - An object missing all declared key fields MUST cause a validation error - An object missing some key fields is valid; only present ones contribute to the composite key
Example:
"records|[*] -> !": [
{"type|#": "A", "code|#": "001", "label": "First"},
{"type|#": "A", "code|#": "002", "label": "Second"},
{"type|#": "B", "code|#": "001", "label": "Third"}
]A-001, A-002, B-001) are uniqueComposite Key Construction
When multiple fields are marked as key fields (modifier #), Okyline constructs a composite key to verify uniqueness of objects in a list.
Algorithm:
The composite key is formed by concatenating the values of fields marked with #, in their order of declaration in the schema, separated by a hyphen (-).
Rules:
- (U+002D HYPHEN-MINUS)1.0 → 1, 123.000 → 123)true or false (lowercase)Example 1: Two string fields
Schema:
"items|[*] -> !": [
{
"country|#": "FR",
"code|#": "75001"
}
]Object: { "country": "FR", "code": "75001" } Composite key: "FR-75001"
Example 2: String + number
Schema:
"sessions|[*] -> !": [
{
"userId|#": 42,
"sessionId|#": "abc-123"
}
]Object: { "userId": 42, "sessionId": "abc-123" } Composite key: "42-abc%2D123" (hyphen in “abc-123” is URL-encoded)
Example 3: Missing value
Schema:
"addresses|[*] -> !": [
{
"country|#": "FR",
"region|#?": "IDF",
"code|#": "75001"
}
]Object: { "country": "FR", "code": "75001" } (region absent) Composite key: "FR-75001" (region ignored because absent)
Example 4: Number normalization
Schema:
"products|[*] -> !": [
{
"sku|#": "ABC",
"version|#": 1.0
}
]Object 1: { "sku": "ABC", "version": 1.0 } Object 2: { "sku": "ABC", "version": 1 } Both have composite key: "ABC-1" → ❌ Duplicate!
Example 5: Boolean values
Schema:
"flags|[*] -> !": [
{
"name|#": "feature",
"enabled|#": true
}
]Object: { "name": "feature", "enabled": true } Composite key: "feature-true"
Example 6: URL encoding
Schema:
"paths|[*] -> !": [
{
"path|#": "/api/v1",
"method|#": "GET"
}
]Object: { "path": "/api/v1", "method": "GET" } Composite key: "%2Fapi%2Fv1-GET" (slashes are URL-encoded)
Error Handling
| Situation | Behavior |
|---|---|
| No key fields declared | ❌ Validation error if uniqueness (!) is requested |
| All key fields absent/null in an object | ❌ Validation error (uniqueness not verifiable) |
| Duplicate composite keys | ❌ Validation error: NOT_UNIQUE |
| Non-scalar key field (object/array) | Field is ignored in key construction |
Example - Error cases:
// ❌ No key fields declared
"items|[*] -> !": [
{"name": "A"},
{"name": "B"}
]
// Error: Cannot verify uniqueness without key fields
// ❌ All key fields absent
"items|[*] -> !": [
{"id|#": 1, "name": "A"},
{"name": "B"} // id missing
]
// Error: Object missing all key fields
// ❌ Duplicate keys
"items|[*] -> !": [
{"id|#": 1, "name": "A"},
{"id|#": 1, "name": "B"}
]
// Error: Duplicate key "1"Design Rationale
✅ Explicit semantics via mandatory key field declaration ✅ Superior performance through O(n) hash-based checking ✅ Business alignment by validating identity, not structure ✅ Clear errors when uniqueness cannot be established ✅ No collision ambiguity via RFC 3986 URL encoding
This design makes Okyline schemas self-documenting and production-ready for high-performance validation scenarios.
Maps are objects used as dictionaries with dynamic keys.
[key_constraint:size_constraint] - Map ConstraintsSyntax: [key_pattern:max_entries]
Variants: - [*:max] - Any string key, maximum entries - [~pattern~:max] - Keys matching regex, maximum entries - [~pattern~:*] - Keys matching regex, any number of entries
Examples:
Free keys:
"translations|[*:5]": {
"en": "Hello",
"fr": "Bonjour",
"es": "Hola"
}Pattern-constrained keys:
"products|[~^SKU-\\d{5}$~:*]": {
"SKU-12345": {
"name|@": "Product A",
"price|@ (0..1000)": 29.99
},
"SKU-67890": {
"name|@": "Product B",
"price|@ (0..1000)": 49.99
}
}SKU- followed by 5 digitsLanguage codes:
"labels|[~^[a-z]{2}(-[A-Z]{2})?$~:10] -> {1,100}": {
"en": "Label",
"fr": "Étiquette",
"en-US": "Label (US)"
}$oneOf - Exclusive MatchThe value must match exactly one of the provided schema examples.
A candidate schema matches if validating the instance against that schema produces no validation errors.
Matching is evaluated independently for each candidate schema.
Applies to: Objects and arrays of objects
Example:
"payment|@ $oneOf": [
{
"type|@ ('card')": "card",
"cardNumber|@ {16}": "1234567812345678",
"cvv|@ {3}": "123"
},
{
"type|@ ('paypal')": "paypal",
"email|@ ~$Email~": "user@example.com"
},
{
"type|@ ('bank')": "bank",
"iban|@ {15,34}": "FR7630006000011234567890189"
}
]Validation: - Document must match one and only one schema - Matching is typically determined by a discriminator field (here: type)
$anyOf - Non-Exclusive MatchThe value must match at least one of the provided schema examples.
A candidate schema matches if validating the instance against that schema produces no validation errors.
Applies to: Objects and arrays of objects
Default behavior: When multiple object examples are provided without an explicit $oneOf or $anyOf modifier, $anyOf semantics apply implicitly. This applies uniformly to collections and to single-value fields using $obj (see §6.4.3).
Example with explicit $anyOf:
"notification|$anyOf": [
{"email|~$Email~": "user@example.com"},
{"sms|~^\\+[0-9]{10,15}$~": "+33612345678"}
]Example with implicit $anyOf (no modifier):
"telecom|[*]": [
{"system|@ ('phone')": "phone", "value|@ {1,100}": "+61355556473"},
{"system|@ ('email')": "email", "value|@ ~$Email~": "user@example.com"}
]Validation: - The instance must match at least one of the candidate schemas - When no explicit modifier is specified, $anyOf is the implicit default for any field with multiple object examples
Only one constraint of the same type is allowed per field.
Invalid:
// ❌ Two length constraints
"name|{10,50}{5,20}": "Alice"
// ❌ Two value constraints
"age|(0..100)(18..65)": 30Valid:
// ✅ Multiple constraints in single constraint
"age|(0..18,65..100)": 75
// ✅ Different constraint types
"name|@ {2,50}": "Alice"// ✅ Required + length + pattern
"email|@ {5,100}~$Email~": "user@example.com"
// ✅ Required + range + label
"age|@ (18..120)|User age in years": 30
// ✅ List size + element constraints + uniqueness
"tags|[1,5] -> {2,20}!": ["eco", "bio"]While order doesn’t affect validation, the recommended order is: 1. Existence (@, ?) 2. Value constraints ((...), {...}, ~...~)
Recommended:
"email|@ {5,100}~$Email~|User email address": "user@example.com"$nomenclatureDefine centralized lists of allowed values that can be reused across multiple fields.
Purpose: - Centralize enum definitions - Improve maintainability - Ensure consistency
Declared at the root level alongside $oky.
Syntax:
{
"$nomenclature": {
"REGISTRY_NAME": "value1,value2,value3"
}
}Example:
{
"$nomenclature": {
"STATUS": "DRAFT,VALIDATED,REJECTED,ACTIVE,INACTIVE,ARCHIVED",
"COUNTRIES": "FRA,DEU,ESP,USA,GBR",
"UNITS": "kg,m,cm,L,°C"
}
}Rules: - Keys are uppercase identifiers - Values are comma-separated strings - No quotes needed around individual items
Reference a nomenclature using ($NAME) syntax in value constraints.
Example:
{
"$nomenclature": {
"COLORS": "RED,GREEN,BLUE,YELLOW"
},
"$oky": {
"product": {
"name|@": "T-Shirt",
"color|@ ($COLORS)": "RED",
"secondaryColor|($COLORS)": "BLUE"
}
}
}Validation: - {"color": "RED"} → ✅ Valid - {"color": "PURPLE"} → ❌ Invalid
A nomenclature entry may associate a value with each key, using key:value pairs separated by commas:
{
"$nomenclature": {
"IbanLetters": "A:10,B:11,C:12,D:13,E:14,F:15,G:16,H:17,I:18,J:19,K:20,L:21,M:22,N:23,O:24,P:25,Q:26,R:27,S:28,T:29,U:30,V:31,W:32,X:33,Y:34,Z:35"
}
}The two forms cannot be mixed within a single entry: either every item has a value, or none does.
Behaviour:
($NAME) syntax), the keys are the allowed values. Both forms are interchangeable: "A,B,C" and "A:10,B:11,C:12" accept the same inputs.lookup(key, '$NAME') (see Annex C §C.10.2). This enables data-driven transformations without embedding business knowledge in the expression engine - the mapping table lives in the schema, not in the code.$formatDefine named regular expressions for reuse across the schema.
Declared at root level alongside $oky.
Syntax:
{
"$format": {
"PatternName": "^regular_expression$"
}
}Example:
{
"$format": {
"PostalCode": "^[0-9]{5}$",
"PhoneNumber": "^\\+33[0-9]{9}$",
"Sku": "^SKU-[0-9]{5}$",
"IsoDate": "^[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\\d|3[01])$"
}
}Important: Remember to escape backslashes in JSON (\d → \\d).
Reference with ~$PatternName~ syntax.
Example:
{
"$format": {
"Code": "^[A-Z]{2}-\\d{4}$"
},
"$oky": {
"session": {
"code|@ ~$Code~": "SR-0012",
"backupCode|~$Code~": "AB-9999"
}
}
}When ~$Name~ is encountered, Okyline resolves in this order:
$format[Name]Name is a recognized built-in (see §5.1.5)Override built-ins:
{
"$format": {
"Date": "^[0-9]{2}/[0-9]{2}/[0-9]{4}$"
},
"$oky": {
"eventDate|~$Date~": "25/12/2025"
}
}$Date pattern overrides the built-in ISO date formatApply structural changes based on field values or existence.
$requiredIf - Conditional Required FieldsRequire fields when a condition is met.
Syntax: "$requiredIf condition": ["field1", "field2"]
Example:
{
"$oky": {
"person": {
"age": 17,
"parentConsent": true,
"$requiredIf age(<18)": ["parentConsent"]
}
}
}parentConsent must be present$requiredIfNot - Required If Condition Not MetRequire fields when a condition is NOT met.
Syntax: "$requiredIfNot condition": ["field1", "field2"]
Example:
{
"$oky": {
"person": {
"age": 25,
"idCard": "AB123456",
"$requiredIfNot age(<18)": ["idCard"]
}
}
}idCard must be present$forbiddenIf - Conditional Forbidden FieldsForbid fields when a condition is met.
Syntax: "$forbiddenIf condition": ["field1", "field2"]
Example:
{
"$oky": {
"account": {
"status": "CLOSED",
"lastLogin": "2025-01-15",
"$forbiddenIf status('CLOSED')": ["lastLogin"]
}
}
}lastLogin must not be present$forbiddenIfNot - Forbidden If Condition Not MetForbid fields when a condition is NOT met.
Syntax: "$forbiddenIfNot condition": ["field1", "field2"]
Example:
{
"$oky": {
"account": {
"status": "ACTIVE",
"closureReason": "Moved abroad",
"$forbiddenIfNot status('CLOSED')": ["closureReason"]
}
}
}closureReason must not be present$appliedIf - Conditional StructureAdd fields dynamically based on a condition.
Syntax variants:
Simple if/else:
"$appliedIf condition": {
"field1|@": value,
"$else": {
"field2|@": value
}
}Example:
{
"$oky": {
"employee": {
"status": "ACTIVE",
"$appliedIf status('ACTIVE')": {
"workDays|@ (1..22)": 20
}
}
}
}{
"$oky": {
"employee": {
"status": "ACTIVE",
"$appliedIf status('ACTIVE')": {
"workDays|@ (1..22)": 20,
"$else": {
"reason|@": "On leave"
}
}
}
}
}Switch-case:
"$appliedIf fieldName": {
"('value1')": { "field1|@": value },
"('value2')": { "field2|@": value },
"$else": { "field3|@": value },
"$notExist": { "field4|@": value }
}Example:
{
"$oky": {
"employee": {
"status": "ACTIVE",
"$appliedIf status": {
"('ACTIVE')": {
"workDays|@ (1..22)": 20
},
"('INACTIVE')": {
"reason|@": "On leave"
},
"$else": {
"note|@": "Status unknown"
}
}
}
}
}Special branches in switch-case:
| Branch | Triggers when |
|---|---|
$else |
The trigger field exists but its value does not match any listed branch |
$notExist |
The trigger field is absent from the validated data |
$else and $notExist are mutually independent: if the trigger field is absent, only $notExist applies (not $else). If the trigger field is present but matches no branch, only $else applies. Both can be declared in the same switch-case block.
Field redefinition in a branch. A branch of $appliedIf (or any of its variants, including $else, $notExist and switch-case bodies) MAY declare fields that do not exist at the parent object level - those are simply added while the branch is active. A branch MAY also adapt a field that is already declared at the parent object level, in which case it MUST use $override or $amend as defined in Annex D §D.7. Redefining a parent-level field in a branch without $override or $amend MUST cause a schema parsing error.
When $override or $amend is used, the effective field definition while the branch is active is the merge of the parent’s definition and the branch’s adapter, computed as defined in Annex D §D.7.3. The validator uses this merged definition instead of the parent’s definition during the evaluation of the branch; a single validation is performed, never both.
Directives permitted inside a conditional block. A conditional block behaves like an ordinary object node: besides field declarations it MAY contain the presence directives ($required, $forbidden), the structural-group directives ($atLeastOne, $mutuallyExclusive, $exactlyOne, $allOrNone) and the value-list conditional directives ($requiredIf*, $forbiddenIf*). Such directives are evaluated in the context of the host object that carries the $appliedIf, MAY reference both host fields and fields introduced by the active branch, and take effect only while that branch is active.
Two constructs MUST NOT appear inside a conditional block:
$appliedIf* (any variant) - a structural conditional MUST NOT be nested directly inside another conditional block. To express a further structural decision, descend into an object-typed field of the block and place the inner $appliedIf* there, or compose both triggers into a single condition through a virtual field (Annex F).$field (virtual field declaration) - virtual fields MUST be declared at the object level (Annex F), never inside a conditional block.This rule applies identically to the blocks of $appliedIf (§6.3.5), $appliedIfExist (§6.3.10) and $appliedIfNotExist (§6.3.11), including their $else, $notExist and switch-case bodies.
$requiredIfExist - Existence-Based RequiredRequire fields if another field exists.
Example:
{
"$oky": {
"contact": {
"firstName": "John",
"$requiredIfExist firstName": ["lastName"]
}
}
}$requiredIfNotExist - Require If Field Does Not ExistRequire fields if another field does NOT exist.
Example:
{
"$oky": {
"contact": {
"$requiredIfNotExist email": ["phone"]
}
}
}email does not exist, then phone must be present$forbiddenIfExist - Existence-Based ForbiddenForbid fields if another field exists.
Example:
{
"$oky": {
"product": {
"archived": true,
"$forbiddenIfExist archived": ["active"]
}
}
}$forbiddenIfNotExist - Forbid If Field Does Not ExistForbid fields if another field does NOT exist.
Example:
{
"$oky": {
"product": {
"$forbiddenIfNotExist sku": ["internalCode"]
}
}
}sku does not exist, then internalCode must not be present$appliedIfExist - Existence-Based StructureAdd fields if another field exists.
Its conditional block follows the same rules as $appliedIf (§6.3.5), including which directives are permitted or forbidden inside it.
Example:
{
"$oky": {
"order": {
"tracking": "ABC123",
"$appliedIfExist tracking": {
"carrier|@": "DHL",
"estimatedDelivery|@ ~$Date~": "2025-12-25"
}
}
}
}$appliedIfNotExist - Add Fields If Field Does Not ExistAdd fields dynamically if another field does NOT exist.
Its conditional block follows the same rules as $appliedIf (§6.3.5), including which directives are permitted or forbidden inside it.
Example:
{
"$oky": {
"contact": {
"$appliedIfNotExist email": {
"phone|@": "+33612345678",
"phoneVerified|@": true
}
}
}
}email does not exist, then phone and phoneVerified fields are required$required - Unconditional Required FieldsForces the presence of listed fields in the current object.
Syntax: "$required": ["field1", "field2"]
The value MUST be a non-empty array of strings. Each string identifies a field name that MUST be present in the validated data. This directive is valid in any object node, including $appliedIf, $else, and switch-case payloads. A field already marked @ is accepted but redundant.
$forbidden - Unconditional Forbidden FieldsForces the exclusion of listed fields from the current object.
Syntax: "$forbidden": ["field1", "field2"]
The value MUST be a non-empty array of strings. Each string identifies a field name that MUST NOT be present in the validated data. This directive is valid in any object node, including $appliedIf, $else, and switch-case payloads.
$atLeastOne - At Least One RequiredRequires that at least one field from a given group is present.
Syntax: "$atLeastOne": ["field1", "field2", ...]
The value MUST be a non-empty array of at least two strings. At least one of the listed fields MUST be present in the validated data. To declare multiple independent groups in the same object, append a unique suffix: $atLeastOne_contact, $atLeastOne_address, etc. The suffix is semantically ignored and serves only to produce a unique JSON key.
Example:
{
"$oky": {
"contact": {
"email": "alice@example.com",
"phone": "+33612345678",
"$atLeastOne": ["email", "phone"]
}
}
}email or phone must be present.$mutuallyExclusive - Mutually Exclusive FieldsEnsures that at most one field from a given group is present.
Syntax: "$mutuallyExclusive": ["field1", "field2", ...]
The value MUST be a non-empty array of at least two strings. At most one of the listed fields MAY be present; having two or more is a validation error. The suffix mechanism ($mutuallyExclusive_xxx) applies as described in §6.3.14.
Example:
{
"$oky": {
"payment": {
"cardNumber": "4111111111111111",
"iban": "FR7630006000011234567890189",
"$mutuallyExclusive": ["cardNumber", "iban"]
}
}
}cardNumber and iban cannot both be present.$exactlyOne - Exactly One RequiredRequires that exactly one field from a given group is present.
Syntax: "$exactlyOne": ["field1", "field2", ...]
The value MUST be a non-empty array of at least two strings. Exactly one of the listed fields MUST be present. Zero or more than one is a validation error. Semantically equivalent to combining $atLeastOne and $mutuallyExclusive on the same group. The suffix mechanism ($exactlyOne_xxx) applies as described in §6.3.14.
Example:
{
"$oky": {
"auth": {
"password": "secret",
"oauthToken": "tok_abc123",
"$exactlyOne": ["password", "oauthToken"]
}
}
}password or oauthToken must be present.$allOrNone - All Or NoneRequires that either all fields in a group are present or none of them.
Syntax: "$allOrNone": ["field1", "field2", ...]
The value MUST be a non-empty array of at least two strings. Either all listed fields MUST be present, or none of them. Partial presence is a validation error. The suffix mechanism ($allOrNone_xxx) applies as described in §6.3.14.
Example:
{
"$oky": {
"address": {
"street": "123 Main St",
"city": "Paris",
"zip": "75001",
"$allOrNone": ["street", "city", "zip"]
}
}
}Type Guards allow checking the runtime type of a field value in conditional expressions.
Syntax: fieldName(_TypeGuard_)
Important: Type Guards can only be used in condition expressions ($appliedIf, $requiredIf, $forbiddenIf), not as field value constraints.
| Type Guard | Description |
|---|---|
_Null_ |
Value is null |
_Boolean_ |
Value is a boolean |
_String_ |
Value is a string |
_Integer_ |
Value is an integer (no fractional part) |
_Number_ |
Value is a number (integer or decimal) |
_Object_ |
Value is an object |
_EmptyList_ |
Value is an empty array |
_ListOfNull_ |
Value is an array containing only null values |
_ListOfBoolean_ |
Value is an array of booleans |
_ListOfString_ |
Value is an array of strings |
_ListOfInteger_ |
Value is an array of integers |
_ListOfNumber_ |
Value is an array of numbers |
_ListOfObject_ |
Value is an array of objects |
Conditional structure based on type:
{
"$oky": {
"data": "example",
"$appliedIf data(_String_)": {
"length": 7,
"$else": {
"type": "non-string"
}
}
}
}Require field based on array type:
{
"$oky": {
"items": [1, 2, 3],
"$requiredIf items(_ListOfInteger_)": ["sum"]
}
}Multiple type guards (OR logic):
{
"$oky": {
"value": null,
"$appliedIf value(_String_,_Null_)": {
"isTextOrEmpty": true
}
}
}_Integer_ is stricter than _Number_: 3.0 matches _Number_ but NOT _Integer__Number_ includes integers: 42 matches both _Integer_ and _Number__ListOfXXX_ guards ignore null elements within the array for type inference_EmptyList_ only matches arrays with zero elementsThe null literal can be used in condition triggers to test if a field’s value is null.
Syntax: fieldName(null)
Important: The null literal is only allowed in condition expressions ($appliedIf, $requiredIf, $forbiddenIf and their variants), not as field value constraints. Use the ? modifier to indicate field nullability.
_Null_| Syntax | Meaning | Can mix with other values? |
|---|---|---|
field(_Null_) |
Type guard: checks if value is null | No (type guards cannot mix with value constraints) |
field(null) |
Value literal: matches null value | Yes |
The null literal allows combining null with other discrete values, which is not possible with the _Null_ type guard.
Require fallback when value is null:
{
"$oky": {
"item": {
"value|?": "example",
"fallback|?": "default",
"$requiredIf value(null)": ["fallback"]
}
}
}Require field when value is NOT null:
{
"$oky": {
"item": {
"config|?": "settings",
"configLabel|?": "active",
"$requiredIfNot config(null)": ["configLabel"]
}
}
}Mix null with other values (OR logic):
{
"$oky": {
"order": {
"status|?": "ACTIVE",
"reason|?": "explanation",
"$requiredIf status('CANCELLED', 'INACTIVE', null)": ["reason"]
}
}
}In this example, reason is required when status is 'CANCELLED', 'INACTIVE', or null.
null literal can be mixed with other value types (strings, numbers, booleans) in the same conditionfield('A', 'B', null) matches if value is 'A' OR 'B' OR nullnull in field value constraints (e.g., "field|@ (null)": ...) produces a parsing errorConditional directives support path expressions to reference fields at any level of the document structure.
Path expressions are supported in: - Trigger conditions of all conditional directives ($requiredIf, $forbiddenIf, $appliedIf, and their Exist/Not/NotExist variants) - Target field lists of $requiredIf and $forbiddenIf directives
The validation context is the object currently being validated in the schema tree. When validating: - A root-level object: context is the root object - A nested object: context is that nested object - An array element: context is the element itself (not the array)
Use dot notation to navigate into child objects from the current context.
Syntax: field.subfield.subsubfield
Example:
{
"$oky": {
"company": {
"info": {
"type": "CORP"
},
"$appliedIf info.type('CORP')": {
"registrationNumber|@": "RC-123456"
}
}
}
}company; info.type navigates to company.info.typeUse parent to reference the parent object of the current validation context.
Syntax: parent, parent.field, parent.field.subfield
Example:
{
"$oky": {
"order": {
"type": "WHOLESALE",
"items": [{
"name": "Widget",
"$appliedIf parent.type('WHOLESALE')": {
"bulkDiscount|@": 15
}
}]
}
}
}{"name": "Widget", ...}parent refers to order (arrays are transparent in the parent chain)parent.type resolves to "WHOLESALE"Multi-level: Use parent.parent to access grandparent, parent.parent.parent for great-grandparent, etc.
Use root to reference the document root object, regardless of nesting depth.
Syntax: root, root.field, root.field.subfield
Example:
{
"$oky": {
"config": {
"strictMode": true
},
"data": {
"items": [{
"value": "test",
"$appliedIf root.config.strictMode(true)": {
"validatedBy|@": "admin"
}
}]
}
}
}Use this to explicitly reference the current validation context. This is equivalent to omitting the prefix but allows disambiguation when a field name collides with a reserved prefix.
Syntax: this, this.field
Example:
{
"$oky": {
"node": {
"parent": "value",
"$appliedIf this.parent('value')": {
"note|@": "Field named parent"
}
}
}
}this.parent refers to the field named parent, not the parent contextPath expressions in target field lists reference fields relative to the current context.
Example:
{
"$oky": {
"user": {
"isPremium": true,
"profile": {
"displayName?": "str"
},
"$requiredIf isPremium(true)": ["profile.displayName"]
}
}
}isPremium is true, the nested field profile.displayName must be present| Prefix | Resolves From |
|---|---|
| (none) | Current validation context |
this. |
Current validation context (explicit) |
parent. |
Parent object of current context |
root. |
Document root |
Parent chain: Arrays are transparent in the parent chain. parent always refers to the nearest ancestor object, skipping any intermediate array containers.
Parent at root: Using parent when the current context is the root object evaluates to not found (condition is false).
Reserved prefixes: The keywords parent, root, and this are reserved as scope prefixes. A path segment matching these keywords at the start of an expression is interpreted as a scope reference, not a field name. To reference a field literally named parent, root, or this, use the this. prefix (e.g., this.parent, this.root).
Path syntax: A path expression MUST conform to one of the following forms:
| Form | Example | Description |
|---|---|---|
fieldPath |
info.type |
From current context (implicit) |
this.fieldPath |
this.parent |
From current context (explicit) |
parent[.parent]*.fieldPath |
parent.parent.status |
From ancestor context |
root.fieldPath |
root.config.mode |
From document root |
Where fieldPath is one or more field names separated by .. Field names MUST start with a letter or underscore and contain only alphanumeric characters and underscores.
The prefixes root, parent, and this are mutually exclusive starting points and MUST NOT be combined (e.g., parent.root.field is invalid).
Malformed paths (empty segments, leading/trailing dots, invalid characters) MUST be rejected as schema parsing errors.
Case sensitivity: Path resolution is case-sensitive. The reserved prefixes parent, root, and this MUST be lowercase. Parent, ROOT, or This are treated as field names, not scope references.
Missing paths: If any segment of a path does not exist or is not an object (except for the final segment), the path is considered not found:
$requiredIf) or absent (for $forbiddenIf)Array traversal restriction: Paths navigate object structures only. Array index notation (e.g., items[0].name) is not supported. Attempting to navigate through an array value (other than via the implicit array element context) results in not found.
Okyline provides modifiers that override default type inference behavior. These are useful when the example value structure differs from the intended schema type.
JSON numeric serialization discards trailing zeros from decimal values (e.g., 78.00 becomes 78). To preserve decimal precision in examples, Okyline allows writing decimal values as strings.
When an example value is a string representing a decimal numeric literal (containing a decimal point .): - The value is automatically interpreted as a Number - The numeric value is obtained by decimal parsing - The inferred type is Number, never Integer - This rule applies regardless of the fractional value
"amount": "78.00" // → Number (automatically converted to 78.0)
"price": "5.0" // → Number (automatically converted to 5.0)
"rate": "0.125" // → Number (automatically converted to 0.125)
"code": "78" // → String (no decimal point, no conversion)
"value": 78 // → Integer
"value": 78.5 // → Number$str - Force String TypeUse $str to prevent the automatic decimal-to-number conversion and force the field to remain a String type.
Syntax: fieldName|$str
Use case: When a field looks like a decimal but should validate as a String (e.g., version numbers, codes).
"version|$str": "1.0" // → String (not converted to Number)
"productCode|$str": "78.00" // → String (not converted to Number)$str prevents the automatic decimal string conversion{min,max} apply to string length$obj - Single Value from Array ExampleWhen an example value is an array but the field should accept a single value (not an array), use the $obj modifier. This allows providing multiple example values for documentation while defining a non-array field.
Syntax: fieldName|$obj
Applies to: All types (scalars and objects)
"street|@ $obj {5,100}|Street address": ["123 Maple Street", "456 Oak Avenue"]Interpretation: - Without $obj: Field type would be Array[String] - With $obj: Field type is String - The constraint {5,100} applies to string length - Each array element is validated as a valid string example
"address|@ $obj": [
{"city": "Paris", "zip": "75001"},
{"city": "London", "zip": "SW1A 1AA"}
]Interpretation: - Without $obj: Field type would be Array[Object] - With $obj: Field type is Object - $obj is purely a type inference modifier - it prevents array inference from the example format - When multiple object examples are provided, $anyOf semantics apply implicitly (see §5.4.2): the instance must match at least one of the examples. Use $oneOf to require an exclusive match.
$oneOfA key use case is defining polymorphic fields where different object structures are valid:
"payment|@ $oneOf $obj": [
{
"type|@ ('card')": "card",
"number|@ {16,16}": "4111111111111111",
"expiry|@ ~^(0[1-9]|1[0-2])/\\d{2}$~": "12/25"
},
{
"type|@ ('paypal')": "paypal",
"email|@ ~$Email~": "user@example.com"
},
{
"type|@ ('transfer')": "transfer",
"iban|@ {15,34}": "FR7630006000011234567890189"
}
]Interpretation: - The payment field expects one object (not an array) - $oneOf indicates the object must match exactly one of the variants - Each array element defines a valid variant structure - All variants are validated during schema parsing
$obj is present and the example value is an array:
$obj affects only type inference; it does not alter validation semantics. Multiple object examples follow $anyOf semantics by default (see §5.4.2), or $oneOf if explicitly specified.$obj is present and the example value is NOT an array:
$obj is an error:
| Example | Without $obj |
With $obj |
|---|---|---|
["Alice", "Bob"] |
Array[String] |
String |
[42, 100] |
Array[Integer] |
Integer |
[{"a":1}, {"b":2}] |
Array[Object] |
Object |
| Modifier | Purpose | Example Value | Inferred Type |
|---|---|---|---|
| (none) | Auto decimal conversion | "78.00" |
Number |
$str |
Force string | "78.00" |
String |
| (none) | Standard | ["a","b"] |
Array[String] |
$obj |
Force single value | ["a","b"] |
String |
| (none) | Standard | [{...},{...}] |
Array[Object] |
$obj |
Force single value | [{...},{...}] |
Object |
An Okyline document is a JSON object that MUST contain at least the $oky key.
Minimal valid document:
{
"$oky": {
"message": "Hello"
}
}Reserved root keys. The set of keys allowed at the document root is closed: every root key MUST be $oky or one of the $-prefixed directives defined by this specification and its annexes. Any unrecognized root key MUST cause schema loading to fail.
$okyThe central element containing the schema definition. This key is required.
Type: Object Content: Field definitions with optional constraints
$okylineVersionSpecifies the version of the Okyline specification used.
Type: String Format: Semantic versioning (e.g., "1.0", "1.2.3") Default: "1.0"
$versionVersion of the schema itself (not the Okyline language). Optional for standalone schemas; required for registry publication and external references ($deps, $import)
Type: String Example: "1.2.3"
$titleHuman-readable title for the schema.
Type: String Example: "User Profile Schema"
$descriptionDescription of what the schema validates.
Type: String Example: "Schema for user profile data including personal information and preferences"
$additionalPropertiesControls whether unknown fields are allowed in validated documents.
Type: Boolean Default: false (unknown attributes are not allowed)
$nullAsAbsentIfUndeclaredControls whether null values on non-nullable fields are treated as absent.
Type: Boolean Default: false (null on a non-nullable field is a type error)
$decimalScaleControls the decimal precision used by the validator for numeric comparisons and arithmetic. Affects:
(>X), (>=X), (<X), (<=X))(min..max))When $decimalScale is absent, the validator MUST behave as if 6 had been declared.
$idUnique identifier for the schema within a registry. The identifier may include a namespace using dot notation.
Type: String Format: [namespace.]name where: - namespace (optional): Logical grouping, can be hierarchical (e.g., sales, fr.company.team) - name: Schema name (part after the last .)
Validation Rules: - Must not start or end with . - Must not contain consecutive dots (..) - Each segment must start with a letter and contain only alphanumeric characters and underscores - Pattern: ^[a-zA-Z][a-zA-Z0-9_]*(\.[a-zA-Z][a-zA-Z0-9_]*)*$
Optional for standalone schemas; required for registry publication and external references ($deps, $import) Examples: - "common" - schema common in default namespace - "sales.orders" - schema orders in namespace sales - "fr.company.billing.invoices" - schema invoices in namespace fr.company.billing
$stateDeclares the contract’s lifecycle state, used for registry and packaging purposes.
Type: String - "DRAFT" or "FINAL" Default: "DRAFT" when absent Optional
A value other than "DRAFT" or "FINAL" MUST cause schema loading to fail. $state does not affect validation; its registry and packaging semantics are defined in Annex E §E.2.4.
{
"$okylineVersion": "1.0",
"$version": "1.2.3",
"$title": "User Profile",
"$description": "Schema for user profiles with contact information",
"$additionalProperties": false,
"$oky": {
"user": {
"id|@": 123,
"name|@ {2,50}": "Alice",
"email|~$Email~": "alice@example.com"
}
}
}$additionalPropertiesControls whether unknown (undeclared) fields are allowed in validated documents.
$additionalProperties MAY be defined at the root level of the Okyline document. When defined at the root, it applies globally to the entire JSON structure.
$additionalProperties MAY also be defined inside an object within $oky. When defined inside an object, the rule applies only to that specific object, and does not propagate recursively to its child objects.
Child objects inherit the global (root) rule only if they do not redefine it locally.
$additionalProperties is considered false (unknown fields are not allowed).$additionalProperties overrides the global rule for that object only.Global setting only:
{
"$additionalProperties": false,
"$oky": {
"user": {
"name|@": "Alice",
"age|@": 30
}
}
}→ Unknown fields anywhere are rejected.
Local override (non-recursive):
{
"$additionalProperties": false,
"$oky": {
"user": {
"$additionalProperties": true,
"name|@": "Alice",
"address": {
"street|@": "Main St"
}
}
}
}✅ user may include extra fields (e.g., "nickname") ❌ user.address may not include unknown fields (no recursive propagation)
Summary Table
| Level | Applies To | Inherited by Children | Default |
|---|---|---|---|
| Root | Entire document | ✅ (unless overridden) | false |
| Object (local) | That object only | ❌ (not recursive) | - |
$sequenceEnforces field order in validated objects. When active, the fields present in the validated data MUST appear in the order declared in the schema.
Same model as $additionalProperties:
$sequence MAY be defined at the root level (applies globally).$sequence MAY be defined inside an object within $oky (applies to that object only, not recursive).$sequence defaults to false.$sequence overrides the global setting for that object only.$nullAsAbsentIfUndeclaredControls whether null values on non-nullable fields are treated as if the field were absent rather than producing a type error.
This directive addresses legacy systems that emit null instead of omitting optional fields.
$nullAsAbsentIfUndeclared MAY be defined at the root level of the Okyline document.$nullAsAbsentIfUndeclared is considered false (null on a non-nullable field is a type error).null value on a non-nullable field (without ?) is treated as absent:
@ or $required), a missing field error is reported instead of a type error.?) are never affected: null on a ? field remains a valid, intentional value regardless of this setting.@), $required, $forbidden, $requiredIf*, $forbiddenIf*, $appliedIf* (triggers and payloads), $atLeastOne, $mutuallyExclusive, $exactlyOne, $allOrNone.Strict mode (default):
{
"$oky": {
"user": {
"name|@ {1,100}": "Alice",
"age": 30
}
}
}→ {"name": null, "age": 25} ❌ (null on non-nullable required field → type error) → {"name": "Bob", "age": null} ❌ (null on non-nullable field → type error)
Tolerant mode:
{
"$nullAsAbsentIfUndeclared": true,
"$oky": {
"user": {
"name|@ {1,100}": "Alice",
"age": 30,
"nickname|?": "Al"
}
}
}→ {"name": null, "age": 25} ❌ (null treated as absent → missing required field) → {"name": "Bob", "age": null} ✅ (null treated as absent → optional field, no error) → {"name": "Bob", "nickname": null} ✅ (nullable field → null is valid, unaffected by directive)
Values must match the type inferred from the example.
Valid:
// Schema: "age": 42 (Integer)
{"age": 30} ✅Invalid:
{"age": "30"} ❌ (string, not integer)
{"age": 30.5} ❌ (number, not integer)
{"age": null} ❌ (null, not integer - use ? constraint for nullable)Fields marked with @ must be present.
Valid:
// Schema: "name|@": "Alice"
{"name": "Bob"} ✅Invalid:
{} ❌ (missing required field 'name')Fields marked with ? can be null or absent.
Valid:
// Schema: "middleName|?": "John"
{"middleName": "Marie"} ✅
{"middleName": null} ✅
{} ✅Note: When
$nullAsAbsentIfUndeclaredistrue(see §7.3.6), anullvalue on a field without?is treated as absent instead of producing a type error. Fields marked with?are never affected by this setting -nullremains a valid value for them.
Strings must satisfy length constraints.
Valid:
// Schema: "username|{3,10}": "alice"
{"username": "bob"} ✅ (length 3)
{"username": "alexander"} ✅ (length 9)Invalid:
{"username": "jo"} ❌ (length 2, minimum is 3)
{"username": "verylongusername"} ❌ (length > 10)Values must satisfy range, enum, or comparison constraints.
Valid:
// Schema: "age|(18..65)": 30
{"age": 18} ✅
{"age": 42} ✅
{"age": 65} ✅Invalid:
{"age": 17} ❌ (below minimum)
{"age": 66} ❌ (above maximum)Arrays must satisfy size constraints.
Valid:
// Schema: "tags|[1,5]": ["eco"]
{"tags": ["a"]} ✅
{"tags": ["a","b","c"]} ✅
{"tags": ["a","b","c","d","e"]} ✅Invalid:
{"tags": []} ❌ (empty, minimum is 1)
{"tags": ["a","b","c","d","e","f"]} ❌ (6 items, maximum is 5)Lists marked with ! must have unique elements.
Scalar uniqueness:
// Schema: "codes|[1,5] -> !": ["A"]
// Valid:
{"codes": ["A", "B", "C"]} ✅
// Invalid:
{"codes": ["A", "B", "A"]} ❌ (duplicate "A")Object uniqueness (by key field):
// Schema:
"users|[*] -> !": [
{"id|#": "u1", "name": "Alice"}
]
// Valid:
{"users": [
{"id": "u1", "name": "Alice"},
{"id": "u2", "name": "Bob"}
]} ✅
// Invalid:
{"users": [
{"id": "u1", "name": "Alice"},
{"id": "u1", "name": "Charlie"}
]} ❌ (duplicate key "u1")Strings must match the specified pattern.
Valid:
// Schema: "code|~^[A-Z]{2}-\\d{4}$~": "AB-1234"
{"code": "XY-9999"} ✅Invalid:
{"code": "ab-1234"} ❌ (lowercase letters)
{"code": "A-1234"} ❌ (only one letter)
{"code": "AB-123"} ❌ (only 3 digits)By default, unknown fields are not allowed unless $additionalProperties: true.
Schema:
{
"$additionalProperties": false,
"$oky": {
"user": {
"name|@": "Alice"
}
}
}Valid:
{"user": {"name": "Bob"}} ✅Invalid:
{"user": {"name": "Bob", "age": 30}} ❌ (unknown field 'age')Implementations should provide clear error messages including: - Field path (e.g., user.address.zipCode) - Constraint violated (e.g., “string length”, “required field”) - Expected vs. actual value - Constraint details (e.g., “expected length 5-10, got 3”)
{
"$oky": {
"message|@ {1,100}": "Hello, Okyline!"
}
}Validates: - Required string field named “message” - Length between 1 and 100 characters
{
"$okylineVersion": "1.4.0",
"$version": "1.0.0",
"$title": "User Profile",
"$description": "Schema for user account information",
"$oky": {
"user": {
"id|@ (>0)|User identifier": 12345,
"username|@ {3,20}|Unique username": "alice_dev",
"email|@ ~$Email~|Email address": "alice@example.com",
"firstName|@ {1,50}|First name": "Alice",
"lastName|@ {1,50}|Last name": "Smith",
"dateOfBirth|~$Date~|Date of birth": "1990-05-15",
"isActive|@|Account status": true,
"roles|[1,5] -> ('admin','user','guest')!|User roles": ["user"],
"preferences|?|User preferences": {
"theme|('light','dark')": "light",
"language|('en','fr','es')": "en"
}
}
}
}{
"$version": "2.1.0",
"$id": "E-ORDER-001",
"$title": "Order Schema",
"$description": "Schema for e-commerce orders",
"$oky": {
"order": {
"orderId|@ #~$OrderId~|Unique order identifier": "ORD-12345678",
"customerId|@ (>0)|Customer ID": 42,
"orderDate|@ ~$DateTime~|Order timestamp": "2025-01-15T10:30:00Z",
"status|@ ($ORDER_STATUS)|Order status": "PENDING",
"items|@ [1,100] -> !|Order items": [
{
"sku|@ #~$Sku~|Product SKU": "SKU-ABC12345",
"name|@ {2,200}|Product name": "Wireless Mouse",
"quantity|@ (1..1000)|Quantity": 2,
"vat|(0.05,0.1,0.15,0.2)": 0.2,
"unitPrice|@ (>0)|Unit price": 50.0
}
],
"shippingAddress|@|Shipping address": {
"street|@ {5,100}": "123 Main Street",
"city|@ {2,50}": "Paris",
"postalCode|@ {5,10}": "75001",
"country|@ {2}": "FR"
},
"paymentMethod|@ ($PAYMENT_METHOD)|Payment method": "CARD",
"total|@ (>0)|Total gross amount": 120.00,
"$requiredIf status('SHIPPED','DELIVERED')": ["trackingNumber"],
"trackingNumber|{10,50}": "TRACK123456789",
"$appliedIf paymentMethod": {
"('CARD')": {
"cardLastFour|@ {4}|Last 4 digits": "1234"
},
"('PAYPAL')": {
"paypalEmail|@ ~$Email~|PayPal email": "user@example.com"
},
"('BANK_TRANSFER')": {
"bankReference|@ {10,50}|Bank reference": "REF1234567890"
}
}
}
},
"$nomenclature": {
"ORDER_STATUS": "PENDING,CONFIRMED,SHIPPED,DELIVERED,CANCELLED",
"PAYMENT_METHOD": "CARD,PAYPAL,BANK_TRANSFER"
},
"$format": {
"OrderId": "^ORD-[0-9]{8}$",
"Sku": "^SKU-[A-Z]{3}[0-9]{5}$"
}
}{
"$okylineVersion": "1.4.0",
"$version": "1.0.5",
"$title": "Shared Vegetable Garden",
"$description": "Schema for shared vegetable garden crop monitoring",
"$nomenclature": {
"VEGGIES": "Carrot,Tomato,Lettuce,Zucchini,Pepper,Cucumber",
"METHODS": "Permaculture,Direct_sowing,Greenhouse,Container",
"WEATHER": "SUNNY,CLOUDY,RAINY,WINDY"
},
"$format": {
"SessionCode": "^[A-Z]{2}-\\d{4}$",
"IsoDate": "^[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\\d|3[01])$"
},
"$oky": {
"gardener|@|Gardener information": {
"id|@ #(>0)|Gardener identifier": 101,
"name|@ {2,100}|Gardener name": "Julie Martin",
"email|{6,100}~$Email~|Contact email": "julie@eco-garden.org",
"status|@ ('ACTIVE','INACTIVE')|Membership status": "ACTIVE"
},
"session|@|Gardening session": {
"code|@ #~$SessionCode~|Unique session code": "SR-0012",
"date|@ ~$IsoDate~|Session date": "2025-04-10",
"participantNames|[1,12] -> {2,50}|Participant names": ["Paul", "Léa", "Marc"],
"surface|@ (1..10,>20)|Planted area in m²": 25,
"weather|@ ($WEATHER)|Weather conditions": "SUNNY",
"notes|?{0,500}|Session notes": "Great session with new participants",
"plants|@ [1,10] -> !|Plants grown": [
{
"type|@ #($VEGGIES)|Vegetable type": "Carrot",
"method|@ [1,2] -> ($METHODS)!|Growing methods": ["Permaculture"],
"plantedAt|@ ~$IsoDate~|Planting date": "2025-03-20",
"quantity|(1..1000)|Number of plants": 50
},
{
"type|@ #($VEGGIES)|Vegetable type": "Tomato",
"method|@ [1,2] -> ($METHODS)!|Growing methods": ["Greenhouse"],
"plantedAt|@ ~$IsoDate~|Planting date": "2025-04-01",
"quantity|(1..1000)|Number of plants": 30
}
]
}
}
}Examples in this table illustrate values in validated JSON instance.
| Constraint | Applies To | Meaning | Example |
|---|---|---|---|
@ |
All | Required field | "name|@": "Alice" |
? |
All | Nullable field | "middle|?": null |
# |
Scalars | Key field (for uniqueness) | "id|#": 123 |
{...} |
String | Length constraint | "name|{2,50}": "Alice" |
(...) |
Scalars | Value constraints | "age|(18..65)": 30 |
~...~ |
String | Regex/format | "email|~$Email~": "a@b.c" |
[...] |
Array | Size constraint | "tags|[1,5]": ["a"] |
-> |
Array/Map | Element/value constraints | "tags|[*] -> {2,10}": ["eco"] |
! |
Array | Uniqueness | "codes|[*] -> !": ["A","B"] |
[...:...] |
Object (map) | Map constraints | "data|[*:10]": {...} |
% |
Scalars, scalar collections | Default value (informational) | "theme|%": "light" |
$oneOf |
Object/Array | Match exactly one | "pay|$oneOf": [...] |
$anyOf |
Object/Array | Match at least one | "notif|$anyOf": [...] |
| Format | Description | Example |
|---|---|---|
$Date |
ISO date (YYYY-MM-DD) | "2025-05-30" |
$DateTime |
ISO 8601 datetime | "2025-05-30T14:30:00Z" |
$Time |
Time (HH:mm or HH:mm:ss) | "23:59:00" |
$Email |
Email address | "user@example.com" |
$Uri |
URI with scheme | "https://example.com" |
$Ipv4 |
IPv4 address | "192.168.1.1" |
$Ipv6 |
IPv6 address | "2001:db8::1" |
$Uuid |
UUID v1-v5 | "f47ac10b-58cc-..." |
$Hostname |
DNS hostname | "api.example.com" |
| Key | Location | Purpose |
|---|---|---|
$oky |
Root | Required. Contains the schema definition |
$okylineVersion |
Root | Okyline spec version (e.g., "1.0") |
$version |
Root | Schema version |
$title |
Root | Schema title |
$description |
Root | Schema description |
$state |
Root | Contract lifecycle state: "DRAFT" (default) or "FINAL". Does not affect validation (see Annex E §E.2.4) |
$additionalProperties |
Root or object | Allow unknown fields (default: false). Local overrides global. |
$sequence |
Root or object | Enforce field order in validated objects (default: false). Local overrides global. |
$nullAsAbsentIfUndeclared |
Root | Treat null on non-? fields as absent (default: false) |
$decimalScale |
Root | Decimal precision for numeric comparisons, ranges and computes (default 6) |
$nomenclature |
Root | Reusable value registries |
$format |
Root | Reusable regex patterns |
//... |
Any block | Comment (attribute and subtree ignored) |
| Directive | Location | Purpose |
|---|---|---|
$requiredIf |
Object | Conditional required fields (if condition) |
$requiredIfNot |
Object | Required if condition NOT met |
$requiredIfExist |
Object | Required if another field exists |
$requiredIfNotExist |
Object | Required if field does NOT exist |
$forbiddenIf |
Object | Conditional forbidden fields (if condition) |
$forbiddenIfNot |
Object | Forbidden if condition NOT met |
$forbiddenIfExist |
Object | Forbidden if another field exists |
$forbiddenIfNotExist |
Object | Forbidden if field does NOT exist |
$appliedIf |
Object | Conditional structure (if/switch condition) |
$appliedIfExist |
Object | Conditional structure (if field exists) |
$appliedIfNotExist |
Object | Conditional structure (if field does NOT exists) |
$required |
Object | Unconditional required fields |
$forbidden |
Object | Unconditional forbidden fields |
$atLeastOne |
Object | At least one field from group must be present |
$mutuallyExclusive |
Object | At most one field from group may be present |
$exactlyOne |
Object | Exactly one field from group must be present |
$allOrNone |
Object | All fields in group or none of them |
Note: Conditions can use value expressions (ranges, comparisons, discrete values), Type Guards (
_String_,_Integer_,_ListOfObject_, etc.), Null Literal (null), or Path Expressions (info.type,parent.status,root.config.mode). See §6.3.18 for Type Guards, §6.3.19 for Null Literal, and §6.3.20 for Path Expressions.
| Example Value | Inferred Type |
|---|---|
"text" |
String |
42 |
Integer |
3.14 |
Number |
true / false |
Boolean |
["a", "b"] |
Array[String] |
[1, 2, 3] |
Array[Integer] |
{"key": "value"} |
Object |
Required email:
"email|@ ~$Email~": "user@example.com"Optional phone number with pattern:
"phone|~^\\+[0-9]{10,15}$~": "+33612345678"List of unique strings, 1-10 items, 2-20 chars each:
"tags|@ [1,10] -> {2,20}!": ["eco", "bio"]Enum from nomenclature:
{
"$oky": {
"status|@ ($STATUS)": "ACTIVE"
},
"$nomenclature": {
"STATUS": "ACTIVE,INACTIVE,PENDING"
}
}Age between 18 and 120:
"age|@ (18..120)": 30Map with pattern-constrained keys:
"translations|[~^[a-z]{2}$~:10] -> {1,100}": {
"en": "Hello",
"fr": "Bonjour"
}Polymorphic payment with oneOf:
"payment|@ $oneOf": [
{"type|@ ('card')": "card", "number|@ {16}": "1234567812345678"},
{"type|@ ('paypal')": "paypal", "email|@ ~$Email~": "user@example.com"}
]Specification Version: 1.7.0 Date: mai 2026 Status: Draft License: CC BY-SA 4.0 Copyright: © Akwatype - 2025-2026
Changelog: - v1.7.0 (2026-05): Renamed root metadata directive $okySchemaVersion to $okylineVersion (§7.3); added $decimalScale directive (§7.3) controlling decimal precision for numeric comparisons, ranges and computes (default 6); added the $state lifecycle directive (§7.3); added unbounded string-length variants {min,*} and {*} (§5.1.3); the set of root keys is now closed - unknown root keys are rejected (§7.1). Conditional Directives (§6.3.5): structural-group and value-list conditional directives are now allowed inside $appliedIf* blocks, except $appliedIf* and $field which remain forbidden. See the per-annex changelogs for Annex C (Expression Language) and Annex D (Internal References), and the new Annex E (External Imports) for cross-schema composition and versioning. - v1.5.0 (2026-04): $nomenclature key-value form ("A:10,B:11") for data-driven mappings (§6.1.3); added the $sequence directive enforcing field order (§7.3.6). Expression Language (Annex C): list literal syntax [a, b, c]; map/filter/countIf/exists/notExists/sumIf now operate on scalar lists with it rebinding; index and size variables in aggregation lambdas; chars, split, join string ↔︎ list primitives; lookup function for key-value retrieval; inline variadic form of in() removed; toNum now preserves arbitrary precision via Numeric. - v1.4.0 (2026-04): Added unconditional directives $required and $forbidden (§6.3.12, §6.3.13); added structural group directives $atLeastOne, $mutuallyExclusive, $exactlyOne, $allOrNone (§6.3.14-§6.3.17); added $nullAsAbsentIfUndeclared directive (§7.3.6); added $notExist branch in $appliedIf switch-case (§6.3.5); $str support on list item constraints. Expression Language (Annex C): added list context navigation (origin, prev, next, first, last) and positional predicates (isOrigin, isFirst, isLast); added membership function in(); added aggregation functions sumIf, map, filter; added date functions dayOfWeek, dayOfYear, weekOfYear, quarter, semester, before, after, equals; added string functions substringBeforeLast, substringAfterLast, replaceFirst, replaceLast, ltrim, rtrim, removePrefix, removeSuffix, removeRange, indexOfFirst, isNull; short-circuit evaluation for &&/||; updated EBNF grammar with revised operator precedence - v1.2.0 (2026-01): Split Annex D into D (internal references) and E (external imports/versioning); terminology update (inclusion/composition instead of inheritance); moved computed expressions to Annex C; added Field Path Expressions in conditional directives (§6.3.20); added Null Literal in condition triggers (§6.3.19); added Annex F for Virtual Fields; added Comments syntax (§4.5) - v1.1.0 (2025-12): Draft update (normative clarifications, additionalProperties behavior, compute semantics, and example fixes) - v1.0 (2025-11): Initial specification release
Contributing: Feedback and contributions to this specification are welcome. Please ensure any derivative works are shared under the same CC BY-SA 4.0 license.
Contact: For questions or suggestions regarding this specification, please contact Akwatype at: pierre-michel.bret@akwatype.io
Okyline® and Akwatype® are registered trademarks of Akwatype.
End of Okyline Language Specification v1.7.0
Note: Annexes A (Conformance) and B (Terminology) will be published in the Final version of the specification.
This annex defines the Okyline Expression Language, a pure and deterministic language for expressing computed validations. It enables business invariants such as cross-field calculations (e.g., total == subtotal * (1 + taxRate)) and conditional logic based on field values. Expressions are declared in a $compute block and referenced in field constraints using the (%ExpressionName) syntax.
See Annex C - Expression Language
This annex defines the internal reference mechanism for composing and reusing schema fragments within a single document. It introduces $defs for declaring reusable templates, $ref for including them, and $override/$remove for adapting included structures. This enables DRY (Don’t Repeat Yourself) schema design while maintaining full control over structural composition.
See Annex D - Internal References
This annex extends Annex D to enable cross-schema composition in enterprise environments. It defines schema identity ($id, $version), versioned dependencies ($deps), and external imports ($import). Organizations can publish schemas to a registry and import definitions from other contracts while maintaining strict version control and compatibility guarantees.
See Annex E - External Imports
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.
Virtual fields are computed from $compute expressions, scoped to the declaring object, evaluated sequentially in declaration order, and can form chains where one field depends on another. They can be used as triggers in value-based conditional directives but MUST NOT be used in existence-based directives ($requiredIfExist, etc.).
End of Okyline Annex Language Specification v1.7.0