socialweb/json-ld is a PHP processor for JSON-LD 1.1. It does two things:
- it expands a JSON-LD document against its contexts, following the JSON-LD 1.1 Processing Algorithms and API, and
- it converts a JSON-LD document to an RDF dataset, as a
SocialWeb\Rdf\Datasetfrom the socialweb/rdf package, so the result can be serialized as N-Quads or canonicalized and hashed.
The processor passes the W3C expand and toRdf test suites for JSON-LD 1.1,
apart from the one toRdf entry that requires generalized RDF. It is strict by
default: data that the JSON-LD algorithms would silently drop, such as a
property that no context defines, is an error unless the caller turns strict
mode off. It never uses the network; the contexts that a document references
by URL come from a bundled set of pinned W3C and w3id.org contexts, which the
caller can extend. It does bounded work on untrusted input, and it depends on
no package other than socialweb/rdf.
Not included, by design: compaction, flattening, framing, conversion from RDF, a document loader that fetches contexts over the network, JSON-LD in HTML script elements, generalized RDF, and the JSON-LD 1.2 drafts.
This project adheres to a code of conduct. By participating in this project and its community, you are expected to uphold this code.
Install this package as a dependency using Composer.
composer require socialweb/json-ldConvert the document to a dataset, canonicalize the dataset with socialweb/rdf, and hash the canonical N-Quads. Two documents that mean the same thing, with different contexts, key order, or blank node labels, produce the same bytes.
use SocialWeb\JsonLd\Processor;
use SocialWeb\Rdf\Canonicalization\Canonicalizer;
$document = <<<'JSON'
{
"@context": {"@vocab": "https://schema.org/"},
"@id": "https://example.com/alice",
"@type": "Person",
"name": "Alice",
"knows": {"@type": "Person", "name": "Bob"}
}
JSON;
$dataset = (new Processor())->toRdf($document);
$canonical = (new Canonicalizer())->canonicalize($dataset)->toNQuads();
$hash = hash('sha256', $canonical);
echo $canonical;This prints:
<https://example.com/alice> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <https://schema.org/Person> .
<https://example.com/alice> <https://schema.org/knows> _:c14n0 .
<https://example.com/alice> <https://schema.org/name> "Alice" .
_:c14n0 <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <https://schema.org/Person> .
_:c14n0 <https://schema.org/name> "Bob" .
toRdf() accepts a JSON string, a stdClass tree as json_decode() returns
by default, nested arrays, in which a PHP list is a JSON array and any other
array is a JSON object, or an ExpandedDocument from expand().
A JSON-LD list becomes a chain of blank nodes in RDF. A list in which the same
value appears twice in a row, such as [1, 2, 2, 3], can make two blank nodes
that the canonicalizer cannot distinguish by their neighbors alone, and with
its default work factor of 1 it then raises
SocialWeb\Rdf\Exception\IterationLimitExceeded. A work factor of 2 allows
such lists:
$canonical = (new Canonicalizer(workFactor: 2))->canonicalize($dataset)->toNQuads();Processor takes an Options object, every option of which has a default. The
options include base, for resolving relative references; limits, which
bound the depth and the size of a document and the work its contexts may take;
and restrictions, which refuse named graphs, @included blocks, reverse
properties, and documents with more than one top-level node, for a caller that
hashes a dataset but reads the JSON tree it came from.
The processor never fetches a context. A document that references a context by
URL gets it from the DocumentLoader in the options, and the default loader,
BundledDocumentLoader, knows these contexts, each by its https URL and its
http alias:
| Context | URL |
|---|---|
| ActivityStreams 2.0 | https://www.w3.org/ns/activitystreams |
| Security Vocabulary v1 | https://w3id.org/security/v1 |
| Security Vocabulary v2 | https://w3id.org/security/v2 |
| Identity Credentials v1 | https://w3id.org/identity/v1 |
| Data Integrity v1 | https://w3id.org/security/data-integrity/v1 |
| Data Integrity v2 | https://w3id.org/security/data-integrity/v2 |
| Multikey v1 | https://w3id.org/security/multikey/v1 |
| Controlled Identifiers v1 | https://www.w3.org/ns/cid/v1 |
| Decentralized Identifiers v1 | https://www.w3.org/ns/did/v1 |
Any other URL raises SocialWeb\JsonLd\Exception\JsonLdError with the code
loading remote context failed. To add a context, give the loader the URL and
the context document, as a JSON string, a stdClass tree, or nested arrays.
with() returns a new loader; an entry it adds replaces a bundled context with
the same URL.
use SocialWeb\JsonLd\Context\BundledDocumentLoader;
use SocialWeb\JsonLd\Options;
use SocialWeb\JsonLd\Processor;
$context = <<<'JSON'
{
"@context": {
"@vocab": "https://schema.org/",
"knows": {"@type": "@id"}
}
}
JSON;
$document = <<<'JSON'
{
"@context": "https://example.com/contexts/v1",
"@id": "https://example.com/alice",
"name": "Alice",
"knows": "https://example.com/bob"
}
JSON;
$loader = (new BundledDocumentLoader())->with('https://example.com/contexts/v1', $context);
$processor = new Processor(new Options(documentLoader: $loader));
echo $processor->expand($document)->toJson(), "\n";This prints:
[{"@id":"https://example.com/alice","https://schema.org/knows":[{"@id":"https://example.com/bob"}],"https://schema.org/name":[{"@value":"Alice"}]}]
A loader of your own implements SocialWeb\JsonLd\Context\DocumentLoader,
whose one method, load(string $url): LoadedDocument, returns the decoded
context document and the URL it counts as loaded from.
expand() returns an ExpandedDocument. Its toJson() method gives the
expanded document as JSON text, and jsonSerialize() gives it as a list of
node objects, with JSON objects as stdClass. Here the document has a
property that its context does not define. The JSON-LD algorithms drop such a
property, so in strict mode the processor refuses the document, and with
strict mode off it expands the rest:
use SocialWeb\JsonLd\Exception\DataLoss;
use SocialWeb\JsonLd\Options;
use SocialWeb\JsonLd\Processor;
$document = <<<'JSON'
{
"@context": {"name": "https://schema.org/name"},
"@id": "https://example.com/alice",
"name": "Alice",
"nickname": "Ally"
}
JSON;
try {
(new Processor())->expand($document);
} catch (DataLoss $e) {
echo $e->getMessage(), "\n";
}
$expanded = (new Processor(new Options(strict: false)))->expand($document);
echo $expanded->toJson(), "\n";This prints:
Data loss detected (undefined property): nickname
[{"@id":"https://example.com/alice","https://schema.org/name":[{"@value":"Alice"}]}]
DataLoss carries the condition as a DataLossCondition case in its
condition property and the term, value, or IRI that would have been dropped
in its detail property. Every exception the package throws implements
SocialWeb\JsonLd\Exception\JsonLdException, so one catch clause covers them
all.
Contributions are welcome! To contribute, please familiarize yourself with CONTRIBUTING.md.
Keeping user information safe and secure is a top priority, and we welcome the contribution of external security researchers. If you believe you've found a security issue in software that is maintained in this repository, please read SECURITY.md for instructions on submitting a vulnerability report.
socialweb/json-ld is copyright © socialweb/json-ld Contributors and licensed for use under the terms of the GNU Lesser General Public License (LGPL-3.0-or-later) as published by the Free Software Foundation. Please see COPYING.LESSER, COPYING, and NOTICE for more information.