JSON-LD subset¶
ONE Record bodies are JSON-LD, but the standard uses a small, predictable
part of it. This package implements exactly that part, in JsonLd\Expander,
JsonLd\Writer and JsonLd\Comparer, and refuses everything else with a
clear error rather than guessing. There is no dependency on a general JSON-LD
processor.
What is supported¶
| Construct | Notes |
|---|---|
One @context object at the document root |
Prefix definitions ("cargo": "https://onerecord.iata.org/ns/cargo#"), @vocab, @base, @language, @version, and term definitions of the form {"@id": …, "@type": …} where @type is @id (values are IRIs) or a datatype IRI |
@id |
Absolute IRI, compact IRI, or blank node identifier (_:b0); a root without @id is a blank node (as in a POST body) |
@type |
One string or a list; terms, compact IRIs or absolute IRIs |
| Properties | Terms, compact IRIs (cargo:grossWeight) or absolute IRIs as keys |
| Native values | Strings (plain, or tagged with the context's @language), booleans (xsd:boolean), integers (xsd:integer), decimals (xsd:double) |
| Value objects | {"@value": …, "@type": …} and {"@value": …, "@language": …} |
| Arrays | Multi-valued properties (sets); null values are ignored |
| Embedded objects | Blank nodes, or nodes with their own @id (for example internal: embedded-object ids) |
| References | {"@id": …} and typed references {"@id": …, "@type": …} (the type is recorded as a fact about the referenced node) |
What is rejected, and why¶
| Construct | Reason |
|---|---|
| A remote context (a URL string) or an array of contexts | Would require fetching and merging contexts; IATA's documents inline one object |
@context inside an embedded object |
Scoped contexts change the meaning of keys below them |
@graph inside a node, @included |
Named graphs: a logistics object is one graph. A top-level @graph (the flattened form NE:ONE answers with) is read as one document whose root is the node asked for, else the one node nothing references |
@list |
Ordered lists have no place in the ONE Record data model |
@set, @nest, @index, @reverse, @json, @direction, @container |
Not used by the standard; silently accepting them would misread data |
| Nested arrays | List-of-lists semantics |
Undefined prefixes and bare terms without @vocab |
A general processor drops them silently; here api:Change with a forgotten api prefix is an error, so a typo cannot become a lost field. Only well-known schemes (http, https, urn, mailto, tel, did, internal, neone, local, …) are taken as absolute IRIs |
| IRIs containing whitespace or other forbidden characters | Not IRIs |
Errors are JsonLd\JsonLdException and name the path, e.g.
Ordered lists (@list) is outside the JSON-LD subset this package supports at cargo:pieces[0].cargo:x.@list.
Expansion¶
use LambdaTwelve\OneRecord\JsonLd\JsonLd;
$document = JsonLd::expand($jsonText); // or a decoded array
$document->graph; // Rdf\Graph of triples
$document->root; // the node the document is about (Iri or BlankNode)
$document->rootTypes(); // its rdf:type IRIs
$document->context; // the parsed context, to write a reply in the partner's terms
Blank nodes are labelled in document order, so expanding the same text twice
gives the same graph. Doubles take JSON-LD's canonical form (20.0 becomes
"2.0E1"^^xsd:double); strings, booleans and integers keep their lexical
form.
Writing¶
$json = JsonLd::compactToJson($graph, $root, $context);
The writer embeds every node that has triples of its own where it is first
referenced (keeping @id for identified nodes), writes later references as
{"@id": …}, writes nodes that only have types as typed references, sorts
keys and values, and uses native JSON values only where the lexical form
survives a round trip. Every @id is compacted against the context's
prefixes, so a code-list or named-individual reference comes out as
{"@id": "cargo:ACTUAL"} while object IRIs under a server stay absolute. Context coercions ("@type": "xsd:anyURI",
"@type": "@id") and the default @language are honoured, so a response can
be written in the same shape a partner used. Output always expands back to
the same graph.
Comparing graphs¶
use LambdaTwelve\OneRecord\JsonLd\Comparer;
$diff = (new Comparer())->compare($graphA, $graphB);
$diff->isEqual();
echo $diff->describe(); // "- <…>" / "+ <…>" lines in canonical form
Two graphs are equal when they are isomorphic: identical up to a relabelling
of blank nodes. By default IRIs under the internal: prefix are compared as
blank nodes, because servers mint their own embedded-object ids; pass
blankNodePrefixes: ['internal:', 'neone:'] to compare with NE:ONE. Numeric
literals are compared by value (20.0, "20.0"^^xsd:double and 2.0E1 are
the same) unless normaliseNumbers: false; ignoreLanguageTags: true treats
"x" and "x"@en-US as equal. Blank nodes get canonical labels by colour
refinement followed by an exhaustive individualisation search, so the labels
depend on structure alone; the search is bounded and a graph too symmetric to
canonicalise within the budget raises JsonLd\ComparisonBudgetExceeded rather
than a guess. ONE Record documents are far from that bound; ChangeBuilder
uses the comparer to match embedded objects, so the exception can surface from
diff() too.
Conformance against IATA's examples¶
Every example document published with the API specification (both editions)
is a fixture under tests/Fixtures/spec/<tag>/ and must read, expand, write
and re-expand to an isomorphic graph. The handful of defective upstream
examples are listed with their reason in SpecExamplesTest; see
Open specification questions, entry 13.