Version: 1.2.0 Date: January 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 reative 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 F - Virtual Fields
This document is published as a Draft of the Okyline 1.2.0 specification. The term “Draft” refers solely to the ongoing formalization of the specification text.
Except for Annex E, Okyline is considered stable for practical use and is implemented by the reference implementation. Annex E - External Imports and Versioning - is at an advanced stage and will be published in the coming months, following final compatibility and consistency validation.
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 | Platform governance and versioning | Annex E | Shared schema ecosystems (in progress) |
| 6 | Virtual fields for conditional logic | Annex F | Derived values in conditions |
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
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
Examples:
"username|{3,10}": "Alice" // min 3, max 10 characters
"city|{50}": "Paris" // max 50 characters (no minimum)
"code|{5,5}": "ABC12" // exactly 5 charactersValidation: - 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: All types
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. This is the default behavior when multiple examples are provided.
A candidate schema matches if validating the instance against that schema produces no validation errors.
Applies to: Objects and arrays of objects
Example:
"notification|$anyOf": [
{"email|~$Email~": "user@example.com"},
{"sms|~^\\+[0-9]{10,15}$~": "+33612345678"}
]Validation: - Document can match one or more schemas - Useful for flexible validation
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
$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"
}
}
}
}
}$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.
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.
Example:
{
"$oky": {
"contact": {
"$appliedIfNotExist email": {
"phone|@": "+33612345678",
"phoneVerified|@": true
}
}
}
}email does not exist, then phone and
phoneVerified fields are requiredType 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 - Each array element is validated as a valid
object example
$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 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"
}
}$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, $xDefs)
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)
$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, $xDefs)
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
{
"$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) | - |
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} ✅
{} ✅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.2.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.2.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]": {...} |
% |
All | 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 |
$additionalProperties |
Root | Allow unknown fields (default: false) |
$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) |
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.12 for Type Guards, §6.3.13 for Null Literal, and §6.3.14 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.2.0 Date: January 2026 Status: Draft License: CC BY-SA 4.0 Copyright: © Akwatype - 2025-2026
Changelog: - 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.14); added Null Literal in condition triggers (§6.3.13); 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.2.0
This annex will be fully defined in the Final version of the specification. It will describe the conformance requirements for documents, validators, and producers.
This annex will be completed in the Final version. It will provide the formal definitions of key terms used throughout 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.
Defined in the external file “Okyline-Annex-C-Expression-language-v1.2.0.md”
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.
Defined in the external file “Okyline-Annex-D-Internal-References-v1.2.0.md”
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
($xDefs). Organizations can publish schemas to a registry
and import definitions from other contracts while maintaining strict
version control and compatibility guarantees.
Defined in the external file “Okyline-Annex-E-External-Imports-v1.2.0.md”
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.).
Defined in the external file “Okyline-Annex-F-Virtual-Fields-v1.2.0.md”
End of Okyline Annex Language Specification v1.2.0