Vocabulary specification for backward-compatible JSON-LD 1.1 extensions targeting AI/ML data exchange
Status: Draft Specification v0.1.0
Date: 2026-02-12
Part of: JSON-LD Extensions for AI/ML (jsonld-ex)
This document specifies the validation extensions for jsonld-ex. A shape-based constraint language — expressed entirely in JSON-LD-native syntax — enables processors to validate the structure, types, cardinalities, and relationships of nodes in a JSON-LD document without requiring external tools or languages.
The formal property definitions for all 22 validation keywords (IRIs, types, ranges) are in the Vocabulary specification, §16. This document defines the shape model, constraint semantics, inheritance resolution, validation algorithms, error reporting, and the relationship to existing constraint languages.
JSON-LD 1.1 has no built-in validation mechanism. A document that is syntactically valid JSON-LD may be semantically broken: a Person node with a numeric name, a Product with a negative price, or a date range where the start exceeds the end. Detecting these errors currently requires one of three approaches:
SHACL (Shapes Constraint Language) — a W3C Recommendation that operates on RDF graphs. SHACL is powerful but requires RDF materialization, introduces a separate constraint language with its own syntax, and has a steep learning curve for developers who work primarily with JSON.
ShEx (Shape Expressions) — a concise constraint language for RDF. Like SHACL, ShEx requires RDF-level thinking and tooling.
JSON Schema — validates JSON syntax but has no awareness of JSON-LD semantics. A JSON Schema can enforce that a field is a string, but cannot enforce that a term maps to a specific IRI or that a value object carries a valid @confidence annotation.
The jsonld-ex validation extensions address this gap with a constraint language that:
@-prefixed keywords — no new language to learn.@extends, enabling reuse and composition of constraint definitions.The key words “MUST”, “MUST NOT”, “SHOULD”, and “MAY” in this document are to be interpreted as described in RFC 2119.
@type, required properties, and per-property constraints."@minimum": 0).@type and/or @id).@value).{"@value": "Alice"}, the raw value is "Alice". For a plain scalar 42, the raw value is 42. For a list, the raw value is extracted from the first element.@extends references.A shape is a JSON object that constrains the structure of a JSON-LD node. Shapes are the top-level unit of validation: each shape targets a specific node type and declares constraints on its properties.
A shape has the following structure:
{
"@shape": {
"@type": "TypeName",
"@extends": "ParentShapeName",
"propertyA": { ... constraint object ... },
"propertyB": { ... constraint object ... }
}
}
The keys of a shape are:
| Key | Required | Description |
|---|---|---|
@type |
No | The expected @type of the node. If present, the node’s @type (or list of types) MUST include this value. |
@extends |
No | One or more parent shapes to inherit constraints from (see §8). |
| property names | No | Each non-@ key names a property to constrain. Its value is a property constraint object. |
Keys beginning with @ that are not recognized validation keywords are ignored. Non-dict values for property keys are ignored.
When a shape declares @type, the validator checks whether the node’s type set includes the declared type.
Algorithm:
@type is a string, the type set is [string]; if @type is an array, the type set is that array; if @type is absent, the type set is empty.@type and the shape’s type is NOT in the node’s type set, emit a type error.A node with multiple types (e.g., "@type": ["Person", "Employee"]) satisfies a shape that declares "@type": "Person" or a shape that declares "@type": "Employee".
Example:
// Shape
{
"@shape": {
"@type": "Person",
"name": {"@required": true}
}
}
// Valid node
{
"@type": ["Person", "Employee"],
"name": "Alice"
}
// Invalid node (missing type)
{
"@type": "Organization",
"name": "Acme Corp"
}
Each property in a shape maps to a constraint object — a JSON object containing one or more constraint keywords. The constraint keywords are evaluated against the property’s value in the node being validated.
Example:
{
"@shape": {
"@type": "Product",
"name": {
"@required": true,
"@type": "xsd:string",
"@minLength": 1,
"@maxLength": 200
},
"price": {
"@type": "xsd:double",
"@minimum": 0
}
}
}
In this shape, the name property has four constraints (required, type, minimum length, maximum length) and the price property has two (type, minimum value).
Before evaluating constraints, the validator extracts the raw value from the property:
null.@value), the raw value is the @value content.null.@), the raw value is null (treated as a structured node, not a scalar).This extraction ensures that constraints operate on the semantic value regardless of whether it is wrapped in a value object:
// Both of these yield raw value "Alice" for constraint evaluation:
{"name": "Alice"}
{"name": {"@value": "Alice", "@confidence": 0.95}}
Processing note: Cardinality constraints (@minCount, @maxCount) operate on the original property value before raw value extraction, since they need to count the number of values rather than inspect a single scalar. See §4 for details.
Atomic constraints evaluate a single property’s raw value against a single requirement. They are the building blocks of property constraint objects.
When @required is true, the property MUST be present in the node and its raw value MUST NOT be null.
If the raw value is null (property absent, empty list, or a plain dict without @value), the validator emits a violation.
When a @required constraint is violated, the remaining constraints on the same property are skipped — there is no value to evaluate them against.
SHACL equivalent: sh:minCount 1
Example:
// Shape
{"name": {"@required": true}}
// Valid
{"name": "Alice"}
{"name": {"@value": "Alice"}}
// Invalid (property absent)
{"email": "alice@example.com"}
// Invalid (null raw value from empty list)
{"name": []}
When @type is present in a property constraint object, the raw value MUST conform to the specified XSD datatype.
Supported datatypes:
| Datatype | Compact Form | Accepted Python/JSON Types |
|---|---|---|
http://www.w3.org/2001/XMLSchema#string |
xsd:string |
String |
http://www.w3.org/2001/XMLSchema#integer |
xsd:integer |
Integer (not boolean) |
http://www.w3.org/2001/XMLSchema#double |
xsd:double |
Integer or float (not boolean) |
http://www.w3.org/2001/XMLSchema#float |
xsd:float |
Integer or float (not boolean) |
http://www.w3.org/2001/XMLSchema#decimal |
xsd:decimal |
Integer or float (not boolean) |
http://www.w3.org/2001/XMLSchema#boolean |
xsd:boolean |
Boolean |
The compact form (e.g., xsd:string) is expanded by replacing the xsd: prefix with http://www.w3.org/2001/XMLSchema# before matching.
Booleans are explicitly excluded from numeric type checks. Although bool is a subclass of int in Python (and JSON booleans are sometimes coerced to numbers in other environments), a boolean value does NOT satisfy xsd:integer, xsd:double, xsd:float, or xsd:decimal.
If the raw value’s type does not match, the validator emits a type violation. If the declared type is not in the supported set, no type check is performed (the constraint is silently skipped, allowing forward compatibility with future type extensions).
SHACL equivalent: sh:datatype
Example:
// Shape
{
"name": {"@type": "xsd:string"},
"age": {"@type": "xsd:integer"},
"active": {"@type": "xsd:boolean"}
}
// Valid
{"name": "Alice", "age": 30, "active": true}
// Invalid: name is numeric
{"name": 12345, "age": 30, "active": true}
// Invalid: age is boolean (boolean excluded from integer)
{"name": "Alice", "age": true, "active": true}
@minimum specifies that the numeric raw value MUST be greater than or equal to the given number. @maximum specifies that the numeric raw value MUST be less than or equal to the given number.
These constraints apply only when the raw value is numeric (integer or float) and not a boolean. If the raw value is not numeric, the constraints are silently skipped.
Both constraints may be present on the same property to define a valid range.
SHACL equivalents: sh:minInclusive, sh:maxInclusive
Example:
// Shape
{
"age": {
"@type": "xsd:integer",
"@minimum": 0,
"@maximum": 150
},
"confidence": {
"@type": "xsd:double",
"@minimum": 0.0,
"@maximum": 1.0
}
}
// Valid
{"age": 30, "confidence": 0.85}
// Invalid: age below minimum
{"age": -1, "confidence": 0.85}
// Invalid: confidence above maximum
{"age": 30, "confidence": 1.5}
@minLength specifies that the string raw value MUST have at least the given number of characters. @maxLength specifies that the string raw value MUST have at most the given number of characters.
These constraints apply only when the raw value is a string. If the raw value is not a string, the constraints are silently skipped.
SHACL equivalents: sh:minLength, sh:maxLength
Example:
// Shape
{
"name": {
"@type": "xsd:string",
"@minLength": 1,
"@maxLength": 100
}
}
// Valid
{"name": "Alice"}
// Invalid: empty string below minLength
{"name": ""}
// Invalid: exceeds maxLength (if length > 100)
{"name": "A very long name..."}
@pattern specifies a regular expression that the string raw value MUST match. The match uses a search semantic (the pattern may match anywhere within the string), not a full-match semantic.
The constraint applies only when the raw value is a string. If the raw value is not a string, the constraint is silently skipped.
If the pattern is an invalid regular expression, the validator emits a pattern error indicating the regex syntax problem rather than silently passing.
SHACL equivalent: sh:pattern
Example:
// Shape
{
"email": {
"@pattern": "^[^@]+@[^@]+$"
},
"zipCode": {
"@pattern": "^\\d{5}(-\\d{4})?$"
}
}
// Valid
{"email": "alice@example.com", "zipCode": "90210"}
// Invalid: email missing @
{"email": "alice.example.com"}
@in specifies an array of allowed values. The raw value MUST be a member of the array (tested by equality).
SHACL equivalent: sh:in
Example:
// Shape
{
"status": {
"@in": ["active", "inactive", "pending"]
},
"priority": {
"@in": [1, 2, 3, 4, 5]
}
}
// Valid
{"status": "active", "priority": 3}
// Invalid: status not in allowed set
{"status": "archived"}
Cardinality constraints operate on the property value before raw value extraction. They count the number of values rather than inspecting a single scalar.
The count of values for a property is determined as follows:
| Property Value | Count |
|---|---|
Absent (null) |
0 |
| A list | Length of the list |
| Any other single value | 1 |
@minCount specifies the minimum number of values the property MUST have. If the count is less than @minCount, the validator emits a violation.
Setting @minCount to 1 is semantically similar to @required: true, but there is a behavioral difference: @required causes remaining constraints to be skipped on violation, while @minCount does not.
SHACL equivalent: sh:minCount
@maxCount specifies the maximum number of values the property MUST have. If the count exceeds @maxCount, the validator emits a violation.
SHACL equivalent: sh:maxCount
Example:
// Shape
{
"email": {
"@minCount": 1,
"@maxCount": 3,
"@pattern": "^[^@]+@[^@]+$"
}
}
// Valid: single value (count = 1)
{"email": "alice@example.com"}
// Valid: two values (count = 2)
{"email": ["alice@example.com", "alice@work.com"]}
// Invalid: no values (count = 0, below minCount)
{}
// Invalid: four values (count = 4, above maxCount)
{"email": ["a@b.com", "c@d.com", "e@f.com", "g@h.com"]}
Logical combinators compose constraints using boolean logic. They enable complex validation rules that cannot be expressed with atomic constraints alone.
All three combinators — @or, @and, @not — evaluate their sub-constraints by recursively invoking the same constraint evaluation mechanism used for atomic constraints. This means sub-constraints may themselves contain logical combinators, enabling arbitrary nesting.
@or specifies an array of constraint objects. The raw value MUST satisfy at least one of the branches. If no branch is satisfied, the validator emits a violation.
Evaluation is short-circuit: branches are tested in order, and evaluation stops at the first branch that produces no errors.
SHACL equivalent: sh:or
Example:
// Shape: value must be either a short code or a full name
{
"identifier": {
"@or": [
{"@type": "xsd:integer", "@minimum": 1000, "@maximum": 9999},
{"@type": "xsd:string", "@minLength": 3, "@maxLength": 50}
]
}
}
// Valid: satisfies first branch
{"identifier": 1234}
// Valid: satisfies second branch
{"identifier": "ACME-WIDGET-001"}
// Invalid: satisfies neither branch
{"identifier": 99}
@and specifies an array of constraint objects. The raw value MUST satisfy all of the branches. If any branch fails, the validator emits a violation.
Evaluation is short-circuit: branches are tested in order, and evaluation stops at the first branch that produces errors (fail-fast).
SHACL equivalent: sh:and
Example:
// Shape: value must be a string that is both a valid email and at least 5 characters
{
"email": {
"@and": [
{"@type": "xsd:string", "@minLength": 5},
{"@pattern": "^[^@]+@[^@]+$"}
]
}
}
// Valid: satisfies both branches
{"email": "alice@example.com"}
// Invalid: fails minLength branch (too short)
{"email": "a@b"}
@not specifies a single constraint object. The raw value MUST NOT satisfy the inner constraint. If the inner constraint produces no errors (i.e., the value satisfies it), the @not constraint emits a violation.
SHACL equivalent: sh:not
Example:
// Shape: status must not be "deleted"
{
"status": {
"@not": {"@in": ["deleted", "archived"]}
}
}
// Valid: "active" is not in the forbidden set
{"status": "active"}
// Invalid: "deleted" satisfies the inner @in, so @not fails
{"status": "deleted"}
Logical combinators may be nested arbitrarily:
// Shape: value is either (an integer >= 0) or (a string that is not empty)
{
"value": {
"@or": [
{"@and": [
{"@type": "xsd:integer"},
{"@minimum": 0}
]},
{"@and": [
{"@type": "xsd:string"},
{"@not": {"@maxLength": 0}}
]}
]
}
}
Logical combinators may also appear alongside atomic constraints in the same property constraint object. When they do, all constraints — atomic and logical — are evaluated independently, and all violations are collected:
{
"score": {
"@type": "xsd:double",
"@minimum": 0,
"@or": [
{"@maximum": 1.0},
{"@in": [-1]}
]
}
}
In this example, the @type and @minimum constraints are evaluated independently of the @or combinator. A value of 0.5 satisfies all three. A value of 2.0 satisfies @type and @minimum but fails the @or. A value of "hello" fails @type (the @minimum is silently skipped for non-numeric values).
Conditional constraints enable if-then-else logic within validation. They allow different constraints to apply depending on the value itself.
The @if, @then, and @else keywords form a conditional constraint:
@if constraint against the raw value.@if constraint is satisfied (produces no errors), evaluate the @then constraint. If @then produces errors, emit a conditional violation.@if constraint is not satisfied (produces errors), evaluate the @else constraint (if present). If @else produces errors, emit a conditional violation.@if is not satisfied and @else is absent, the conditional constraint is vacuously true — no violation is emitted.The @if keyword is required for conditional evaluation. @then and @else are each optional, but at least one SHOULD be present for the conditional to have any effect.
Example:
// Shape: if age >= 18, category must be "adult" or "senior";
// otherwise, category must be "minor"
{
"age": {
"@if": {"@minimum": 18},
"@then": {"@in": ["adult", "senior"]},
"@else": {"@in": ["minor"]}
}
}
Note that in this example, the @if, @then, and @else all evaluate against the same raw value (the value of the age property). This is a property-level conditional, not a node-level conditional. The constraint is useful when the acceptable values of a property depend on the value’s own characteristics (e.g., range-dependent enumeration).
Conditional constraints may appear alongside atomic constraints, logical combinators, and cross-property constraints in the same property constraint object. All constraints are evaluated independently:
{
"score": {
"@type": "xsd:double",
"@minimum": 0,
"@if": {"@minimum": 0.5},
"@then": {"@maximum": 1.0}
}
}
Here, @type and @minimum are always enforced. The conditional adds: if score >= 0.5, then score <= 1.0.
Cross-property constraints compare a property’s value to the value of another property on the same node. They enable relational validation that cannot be expressed with per-property constraints alone.
All cross-property constraints reference a sibling property by name. The sibling’s value is extracted using the same raw-value extraction rules (§2.4). If the sibling property is absent or its raw value is null, the cross-property constraint is silently skipped (no violation, no error).
If the comparison raises a type error (e.g., comparing a string to an integer), the validator emits a violation indicating that the types are incomparable.
The raw value of the constrained property MUST be strictly less than the raw value of the named sibling property.
SHACL equivalent: sh:lessThan
The raw value of the constrained property MUST be less than or equal to the raw value of the named sibling property.
SHACL equivalent: sh:lessThanOrEquals
The raw value of the constrained property MUST be equal to the raw value of the named sibling property.
SHACL equivalent: sh:equals
The raw value of the constrained property MUST differ from (not be equal to) the raw value of the named sibling property.
SHACL equivalent: sh:disjoint
// Shape: startDate must be before endDate, and confirmEmail must match email
{
"@shape": {
"@type": "Event",
"startDate": {
"@required": true,
"@lessThan": "endDate"
},
"endDate": {
"@required": true
},
"email": {
"@required": true,
"@pattern": "^[^@]+@[^@]+$"
},
"confirmEmail": {
"@equals": "email"
},
"alternateEmail": {
"@disjoint": "email"
}
}
}
// Valid
{
"@type": "Event",
"startDate": "2026-01-01",
"endDate": "2026-12-31",
"email": "organizer@example.com",
"confirmEmail": "organizer@example.com",
"alternateEmail": "backup@example.com"
}
// Invalid: startDate not less than endDate
{
"@type": "Event",
"startDate": "2026-12-31",
"endDate": "2026-01-01",
"email": "organizer@example.com",
"confirmEmail": "organizer@example.com",
"alternateEmail": "organizer@example.com"
}
In the invalid example, two violations are emitted: startDate is not less than endDate, and alternateEmail is not disjoint from email.
Shape inheritance enables constraint reuse and composition. A shape may declare one or more parent shapes via @extends, inheriting all of the parents’ property constraints.
@extends accepts:
Input: A shape with @extends, a shape registry (mapping names to shape definitions), and a set of already-visited shapes (initially empty, for cycle detection).
Procedure:
@extends value. Normalize it to an array if it is a single value.@extends (if any).
b. If the reference is a string, look it up in the shape registry. If found, recursively resolve its @extends. If not found, emit a warning ("unresolved") and skip the reference.
c. Other types are silently ignored.@extends) on top.@extends key from the merged result.When merging a source shape into a target shape:
@type): the source value overwrites the target value. Later sources (including the child) override earlier ones.@extends key in any source is ignored during merging (it has already been resolved).With multiple parents ["A", "B"], the merge order is:
This follows a left-to-right, child-wins resolution strategy.
If a shape’s inheritance chain contains a cycle (A extends B, B extends A), the cycle is detected via the visited set and broken by returning the shape at the point where the cycle is detected. No error is raised; the shape is resolved with the constraints accumulated up to the cycle point.
// Shape registry
{
"NamedEntity": {
"name": {
"@required": true,
"@type": "xsd:string",
"@minLength": 1
}
},
"Timestamped": {
"createdAt": {"@required": true},
"updatedAt": {"@required": true}
}
}
// Child shape extending both
{
"@shape": {
"@type": "Person",
"@extends": ["NamedEntity", "Timestamped"],
"name": {
"@maxLength": 200
},
"email": {
"@required": true,
"@pattern": "^[^@]+@[^@]+$"
}
}
}
// Resolved shape (after inheritance):
{
"@type": "Person",
"name": {
"@required": true,
"@type": "xsd:string",
"@minLength": 1,
"@maxLength": 200
},
"createdAt": {"@required": true},
"updatedAt": {"@required": true},
"email": {
"@required": true,
"@pattern": "^[^@]+@[^@]+$"
}
}
In this example, the child’s name constraint adds @maxLength to the inherited @required, @type, and @minLength from NamedEntity. The createdAt and updatedAt constraints are inherited from Timestamped unchanged. The email constraint is entirely new in the child.
The @severity keyword controls how constraint violations are classified. It enables shapes to distinguish between hard errors (which cause validation failure) and soft warnings (which are reported but do not prevent a document from being considered valid).
| Value | Behavior |
|---|---|
"error" |
Violation is recorded as a ValidationError. The node fails validation. This is the default. |
"warning" |
Violation is recorded as a ValidationWarning. The node does NOT fail validation. |
"info" |
Violation is recorded as a ValidationWarning. The node does NOT fail validation. |
Both "warning" and "info" route to the warnings list. The distinction is semantic: "warning" suggests the violation should be reviewed; "info" is purely informational.
@severity is set per property constraint object. All constraints within the same property constraint object share the same severity:
{
"nickname": {
"@severity": "warning",
"@type": "xsd:string",
"@maxLength": 50
}
}
If nickname is numeric (failing @type) or too long (failing @maxLength), both violations are reported as warnings rather than errors.
A ValidationResult is valid: true if and only if the errors list is empty. The warnings list does not affect validity:
// Result with warnings but no errors
{
"valid": true,
"errors": [],
"warnings": [
{"path": "nickname", "code": "type", "message": "Expected xsd:string, got int: 42"}
]
}
SHACL equivalent: sh:severity (with values sh:Violation, sh:Warning, sh:Info)
A property constraint object may contain a @shape key, indicating that the property’s value is itself a node that must conform to an inner shape. This enables recursive, hierarchical validation.
When @shape is present in a property constraint object:
address/street).@shape is the primary constraint, scalar constraints (@type, @minimum, etc.) in the same property constraint object are not evaluated — the @shape takes precedence and subsequent processing continues to the next property.// Shape with nested shape
{
"@shape": {
"@type": "Person",
"name": {"@required": true, "@type": "xsd:string"},
"address": {
"@shape": {
"@type": "PostalAddress",
"streetAddress": {"@required": true},
"postalCode": {"@pattern": "^\\d{5}$"}
}
}
}
}
// Valid
{
"@type": "Person",
"name": "Alice",
"address": {
"@type": "PostalAddress",
"streetAddress": "123 Main St",
"postalCode": "90210"
}
}
// Invalid: nested node fails
{
"@type": "Person",
"name": "Alice",
"address": {
"@type": "PostalAddress",
"postalCode": "ABCDE"
}
}
In the invalid example, two errors are emitted for the address property: streetAddress is required (missing), and postalCode does not match the pattern. The error paths are address/streetAddress and address/postalCode.
In addition to validating individual nodes, the validation extensions support document-level validation, which automatically matches shapes to nodes by type and validates all matching pairs.
Nodes are extracted from a document recursively:
@type key, it is a node — include it.
b. If it has an @graph key, recurse into the @graph value and collect nodes.For each extracted node, its type set is compared to the @type declared by each shape. If a shape’s @type is in the node’s type set, the node is validated against that shape.
A single node may match multiple shapes (if multiple shapes declare types that appear in the node’s type set). Each matching shape is evaluated independently, and all violations are collected.
When validating a document with multiple nodes, error paths are prefixed with the node’s @id (or "anonymous" if no @id is present) to distinguish which node produced the violation:
"http://example.org/alice/name" → required violation
"anonymous/email" → pattern violation
// Document with @graph
{
"@context": "https://schema.org/",
"@graph": [
{
"@id": "http://example.org/alice",
"@type": "Person",
"name": "Alice",
"email": "alice@example.com"
},
{
"@id": "http://example.org/acme",
"@type": "Organization",
"name": "Acme Corp"
},
{
"@type": "Person",
"email": "invalid-email"
}
]
}
// Shapes
[
{
"@type": "Person",
"name": {"@required": true, "@type": "xsd:string"},
"email": {"@pattern": "^[^@]+@[^@]+$"}
},
{
"@type": "Organization",
"name": {"@required": true}
}
]
Validation results:
http://example.org/alice — valid (satisfies Person shape).http://example.org/acme — valid (satisfies Organization shape).anonymous — two violations: name is required but absent; email does not match the pattern.The validation extensions define a structured error reporting model with three data types.
A ValidationError represents a constraint violation at error severity. Its fields are:
| Field | Type | Description |
|---|---|---|
path |
String | The property path where the violation occurred (e.g., "name", "address/street", "http://example.org/alice/email"). |
constraint |
String | The constraint keyword that was violated (e.g., "required", "minimum", "pattern", "type", "or", "and", "not", "conditional", "lessThan", "equals", "disjoint", "shape", "minCount", "maxCount"). |
message |
String | A human-readable description of the violation, including the actual value and the expected constraint. |
value |
Any | The value that caused the violation (may be null for required violations). |
A ValidationWarning represents a constraint violation at warning or info severity, or an informational notice from the validation process (e.g., unresolved shape references during inheritance). Its fields are:
| Field | Type | Description |
|---|---|---|
path |
String | The property path or context (e.g., "nickname", "@extends"). |
code |
String | A short identifier for the warning type (e.g., "type", "pattern", "unresolved"). |
message |
String | A human-readable description. |
A ValidationResult aggregates errors and warnings from a validation pass. Its fields are:
| Field | Type | Description |
|---|---|---|
valid |
Boolean | true if the errors list is empty; false otherwise. |
errors |
List of ValidationError |
All error-severity violations. |
warnings |
List of ValidationWarning |
All warning/info-severity violations and informational notices. |
Error messages SHOULD:
Examples:
Property "name" is required
Value 200 exceeds maximum 150
Length 0 below minimum 1
"invalid" does not match pattern "^[^@]+@[^@]+$"
Expected xsd:string, got int: 42
Value 'X' not in allowed set ['A', 'B', 'C']
Expected at least 2 value(s), found 1
Value 42 did not satisfy any @or branch
Value "2026-12-31" is not less than endDate="2026-01-01"
This section provides a consolidated description of the complete node validation algorithm, combining all mechanisms described in §§2–12.
Input: A node (JSON object), a shape (JSON object), and an optional shape registry (mapping of names to shapes).
Output: A ValidationResult.
Procedure:
@extends, resolve it using the shape registry (§8.2). Collect any resolution warnings.@type, extract the node’s type set. If the shape’s type is not in the type set, emit a type error.@ and whose values are dicts):
a. Retrieve the property value from the node.
b. Determine the severity from @severity (default: "error").
c. Cardinality. Evaluate @minCount and @maxCount against the value count (§4).
d. Extract raw value (§2.4).
e. Required check. If @required is true and the raw value is null, emit a required violation and skip to the next property.
f. If the raw value is null and the property is absent, skip to the next property.
g. Nested shape. If @shape is present, validate the property value as a nested node (§10). Skip scalar constraints and continue to the next property.
h. If the raw value is null (after extraction), skip to the next property.
i. Constraint evaluation. Evaluate all constraints — atomic (§3), logical (§5), conditional (§6), and cross-property (§7) — against the raw value. Collect all violations.
j. Severity routing. For each violation, route it to errors or warnings based on the property’s severity.ValidationResult with valid = (errors is empty), the collected errors, and the collected warnings.Input: A document (JSON value) and a list of shapes.
Output: A ValidationResult.
Procedure:
@type is in the node’s type set, validate the node against the shape (§13.1).@id (or "anonymous").ValidationResult.A standard JSON-LD 1.1 processor that does not implement the jsonld-ex validation extensions will:
@shape and all validation keywords — These keywords are not defined in JSON-LD 1.1. A non-extended processor will treat them as regular JSON properties. A document containing @shape will expand normally — the shape definition will be preserved as data rather than interpreted as validation instructions.The validation extensions degrade gracefully:
Adopting the validation extensions requires no changes to existing JSON-LD documents. Shapes are additive — they constrain existing data without modifying it. Organizations can:
SHACL is a W3C Recommendation for validating RDF graphs. It is the most comprehensive constraint language for linked data. The jsonld-ex validation extensions are designed to complement SHACL, not replace it.
| Aspect | SHACL | jsonld-ex Validation |
|---|---|---|
| Operates on | RDF graphs (triples) | JSON-LD node objects (JSON) |
| Requires RDF materialization | Yes | No |
| Syntax | RDF (Turtle, JSON-LD, etc.) | JSON-LD native (@-prefixed keywords) |
| Expressiveness | Full (SPARQL-based constraints, property paths, etc.) | Practical subset (covers ~80% of common validation needs) |
| Learning curve | Steep (requires RDF knowledge) | Low (JSON-native, familiar to web developers) |
| Constraint mapping | N/A | Every jsonld-ex constraint has a documented SHACL equivalent |
| Logical combinators | sh:or, sh:and, sh:not, sh:xone |
@or, @and, @not (no exclusive-or) |
| Conditional constraints | Via SPARQL-based constraints | @if / @then / @else |
| Cross-property constraints | sh:lessThan, sh:equals, sh:disjoint, etc. |
@lessThan, @lessThanOrEquals, @equals, @disjoint |
| Shape inheritance | rdfs:subClassOf and target declarations |
@extends with explicit merge semantics |
| Severity | sh:Violation, sh:Warning, sh:Info |
"error", "warning", "info" |
Design decision: The jsonld-ex validation extensions intentionally do not attempt to replicate SHACL’s full expressiveness. SPARQL-based constraints, complex property paths, and advanced target selection are out of scope. The goal is to cover the validation needs of the vast majority of JSON-LD consumers — type checking, required properties, numeric bounds, string patterns, cardinalities, and basic logical composition — in a syntax that requires no RDF knowledge.
For use cases that require SHACL’s full power, the clean constraint mapping (documented in the vocabulary specification) enables straightforward conversion from jsonld-ex shapes to SHACL shapes.
ShEx is a concise constraint language for RDF, focused on structural validation. Like SHACL, ShEx operates on RDF graphs and requires RDF-level thinking.
| Aspect | ShEx | jsonld-ex Validation |
|---|---|---|
| Operates on | RDF graphs | JSON-LD node objects |
| Syntax | ShExC (compact) or ShExJ (JSON) | JSON-LD native |
| Expressiveness | Structural validation with recursion | Similar structural coverage, plus cross-property constraints |
| Inheritance | EXTENDS keyword |
@extends keyword (similar semantics) |
JSON Schema validates JSON documents against structural schemas. It has wide adoption in the web development community.
| Aspect | JSON Schema | jsonld-ex Validation |
|---|---|---|
| Operates on | JSON documents | JSON-LD node objects |
| Semantic awareness | None (syntax-only) | JSON-LD aware (understands @value, @type, @id) |
| Value extraction | Direct property access | Extracts raw values from value objects |
| Linked Data interoperability | None | Maps to SHACL; operates within JSON-LD ecosystem |
| Conditional | if / then / else |
@if / @then / @else |
| Composition | anyOf, allOf, not, oneOf |
@or, @and, @not |
| Cross-property | Limited (via dependencies) |
@lessThan, @equals, @disjoint, etc. |
Key distinction: JSON Schema validates the syntactic structure of a JSON document. It cannot validate that a term maps to a specific IRI, that a value object’s @value is a valid integer, or that a @confidence annotation falls within [0, 1]. The jsonld-ex validation extensions understand JSON-LD’s data model and extract semantic values before validation.
The following table provides the complete mapping from jsonld-ex validation keywords to SHACL constraint components:
| jsonld-ex Keyword | SHACL Equivalent | Notes |
|---|---|---|
@required: true |
sh:minCount 1 |
|
@type (datatype) |
sh:datatype |
|
@minimum |
sh:minInclusive |
|
@maximum |
sh:maxInclusive |
|
@minLength |
sh:minLength |
|
@maxLength |
sh:maxLength |
|
@pattern |
sh:pattern |
jsonld-ex uses search semantics; SHACL uses full match |
@in |
sh:in |
|
@minCount |
sh:minCount |
|
@maxCount |
sh:maxCount |
|
@severity |
sh:severity |
"error" → sh:Violation, "warning" → sh:Warning, "info" → sh:Info |
@or |
sh:or |
|
@and |
sh:and |
|
@not |
sh:not |
|
@if/@then/@else |
SPARQL-based constraint | No direct SHACL equivalent; requires sh:sparql |
@lessThan |
sh:lessThan |
|
@lessThanOrEquals |
sh:lessThanOrEquals |
|
@equals |
sh:equals |
|
@disjoint |
sh:disjoint |
|
@extends |
Shape targeting + rdfs:subClassOf |
Different mechanism; similar outcome |
@shape (nested) |
Nested sh:node |
Pattern semantics note: The jsonld-ex @pattern uses a search semantic (the regex may match anywhere in the string), while SHACL’s sh:pattern uses a full-match semantic (the regex must match the entire string). When converting jsonld-ex patterns to SHACL, patterns that do not already have ^ and $ anchors should be wrapped in ^.* and .*$ to preserve the search behavior.
The reference implementation is in the jsonld_ex.validation module of the jsonld-ex Python package.
| Function | Spec Section | Description |
|---|---|---|
validate_node(node, shape, shape_registry=None) |
§13.1 | Validate a single JSON-LD node against a shape. Returns a ValidationResult. |
validate_document(doc, shapes) |
§13.2 | Validate all matching nodes in a document against a list of shapes. Returns a ValidationResult. |
| Type | Fields | Description |
|---|---|---|
ValidationResult |
valid, errors, warnings |
Aggregated validation outcome (§12.3). |
ValidationError |
path, constraint, message, value |
An error-severity violation (§12.1). |
ValidationWarning |
path, code, message |
A warning/info-severity violation or informational notice (§12.2). |
| Function | Spec Section |
|---|---|
_resolve_extends(shape, registry, _seen) |
§8.2 |
_merge_shape_into(target, source) |
§8.3 |
_check_constraints(prop, raw, constraint, node) |
§§3, 5, 6, 7 |
_emit(errors, warnings, severity, ...) |
§9 |
_extract_raw(value) |
§2.4 |
_count_values(value) |
§4.1 |
_get_types(node) |
§2.2 |
_extract_nodes(doc) |
§11.1 |
_validate_type(value, expected) |
§3.2 |