accredifysg / php-json-ld
A PHP implementation of the JSON-LD 1.1 specification.
Requires
- php: ^8.2
- psr/http-client: ^1.0
- psr/http-factory: ^1.0
- psr/http-message: ^1.1 || ^2.0
Requires (Dev)
- guzzlehttp/guzzle: ^7.0
- guzzlehttp/psr7: ^2.0
- laravel/pint: ^1.0
- mockery/mockery: ^1.6
- pestphp/pest: ^2.14
- phpstan/phpstan: ^2.1
Suggests
- psr/http-client-implementation: A PSR-18 client (e.g. guzzlehttp/guzzle) is required if you use HttpDocumentLoader for resolving remote @context URLs.
- psr/http-factory-implementation: A PSR-17 request factory (e.g. guzzlehttp/psr7) is required if you use HttpDocumentLoader.
Provides
None
Conflicts
None
Replaces
None
- dev-main
- v2.2.0
- v2.1.1
- v2.1.0
- v2.0.0
- v1.0.1
- v1.0.0
- v0.69.0
- v0.68.0
- v0.67.0
- v0.66.0
- v0.65.0
- v0.64.0
- v0.63.0
- v0.62.0
- v0.61.0
- v0.60.0
- v0.59.0
- v0.58.0
- v0.57.0
- v0.56.0
- v0.55.0
- v0.54.0
- v0.53.0
- v0.52.0
- v0.51.0
- v0.50.0
- v0.49.0
- v0.48.0
- v0.47.0
- v0.46.0
- v0.45.0
- v0.44.0
- v0.43.0
- v0.42.0
- v0.41.0
- v0.40.0
- v0.39.0
- v0.38.0
- v0.37.0
- v0.36.0
- v0.35.0
- v0.34.0
- v0.33.0
- v0.32.0
- v0.31.0
- v0.30.0
- v0.29.0
- v0.28.0
- v0.27.0
- v0.26.0
- v0.25.0
- v0.24.0
- v0.23.0
- v0.22.0
- v0.21.0
- v0.20.0
- v0.19.0
- v0.18.0
- v0.17.0
- v0.16.0
- v0.15.0
- v0.14.0
- v0.13.0
- v0.12.0
- v0.11.0
- v0.10.0
- v0.9.0
- v0.8.0
- v0.7.0
- v0.6.0
- v0.5.0
- v0.4.0
- v0.3.0
- v0.2.0
- v0.1.1
- v0.1.0
- dev-fix/scoped-contexts-inherit-default-direction
- dev-fix/expansion-preserves-xsd-string-type
- dev-devops/add_sonar_badges
This package is auto-updated.
Last update: 2026-09-21 01:09:28 UTC
README
A PHP implementation of the JSON-LD 1.1 specification.
PHP-JSON-LD implements all six JSON-LD 1.1 processing algorithms —
Expansion, Compaction, Flattening, Serialize to RDF (toRdf),
RDF to JSON-LD (fromRdf), and Framing — validated against the
official W3C test suites (1,287 of 1,302 tests passing, with Compaction
and Flattening fully conformant) and cross-checked against reference
implementations for identical canonical RDF output. The public API is stable
and the project follows Semantic Versioning.
Highlights
- Complete algorithm coverage of the JSON-LD 1.1 Processing Algorithms and API and JSON-LD 1.1 Framing.
- Conformance gated in CI against the official W3C suites: any regression — or a documented blocker that silently starts passing — fails the build.
- Cross-implementation interoperability: RDF output is verified against
RDFC-1.0 canonical N-Quads goldens generated by jsonld.js over realistic
verifiable-credential documents, so signatures computed over this library's
output verify in other conformant processors (see
tests/Interop). - Pluggable document loading: implement one
DocumentLoaderinterface to serve known@contextURLs from local resources — recommended for verifiable-credential pipelines — or use the bundled PSR-18 HTTP loader. - No mandatory HTTP dependency: bring your own PSR-18 client, or none.
Installation
composer require accredifysg/php-json-ld
Requires PHP 8.2+. The bundled HttpDocumentLoader needs a PSR-18 HTTP client
and PSR-17 request factory (e.g. guzzlehttp/guzzle + guzzlehttp/psr7);
alternatively, implement Accredify\JsonLd\Contracts\DocumentLoader yourself
and no HTTP client is required.
Usage
use Accredify\JsonLd\JsonLdProcessor; use Accredify\JsonLd\Loaders\CachingDocumentLoader; use Accredify\JsonLd\Loaders\HttpDocumentLoader; use GuzzleHttp\Client; use GuzzleHttp\Psr7\HttpFactory; $loader = new CachingDocumentLoader( new HttpDocumentLoader(new Client, new HttpFactory), ); $processor = new JsonLdProcessor($loader); $expanded = $processor->expand($document); $compacted = $processor->compact($expanded->toArray(), $context)->toArray(); $nQuads = $processor->toRdf($document)->toNQuads();
To serve known contexts from local files (recommended for verifiable
credentials), implement Accredify\JsonLd\Contracts\DocumentLoader. See
tests/Interop/Support/FixtureDocumentLoader.php
for a compact example.
Framing note: a frame's
{}wildcard cannot be represented in PHP's associative-array model (it decodes identically to[]), so the framing API treats an empty frame value asmatch noneand accepts the documentedExpansion::FRAME_WILDCARDsentinel for a wildcard.
Safe mode
JSON-LD processing is lossy by design: an undefined term, a relative IRI, or a malformed value is silently dropped rather than reported. For documents that feed RDF canonicalization and signing, that is a security hole — W3C VC-DATA-INTEGRITY 1.0 §2.4.3 "Securing Data Losslessly" makes failing closed a normative requirement:
Implementations that use JSON-LD processing, such as RDF Dataset Canonicalization [RDF-CANON], MUST throw an error, which SHOULD be
DATA_LOSS_DETECTION_ERROR, when data is dropped by a JSON-LD processor, such as when an undefined term is detected in an input document.
Pass safe: true (mirroring jsonld.js's safe option) and every drop site
throws Accredify\JsonLd\Exceptions\DataLossException instead:
use Accredify\JsonLd\Exceptions\DataLossException; use Accredify\JsonLd\JsonLdOptions; try { $nQuads = $processor->toRdf($document, new JsonLdOptions(safe: true))->toNQuads(); } catch (DataLossException $e) { // e.g. "Safe mode: term 'alumniOf' does not expand to an absolute IRI // or keyword (invalid property)" $e->eventCode; // 'invalid property' — jsonld.js-compatible event code $e->details; // ['term' => 'alumniOf', …] — the dropped datum }
The default (safe: false) is byte-identical to previous releases and to the
spec-mandated lossy behaviour the W3C suites expect. Canonicalization/signing
pipelines should always opt in: an empty-output check catches only the
total-drop case, while safe mode also catches partial drops — an undefined
credentialSubject claim that would otherwise sign, verify, and remain
attacker-editable.
Event codes use jsonld.js's safe-mode catalog (lib/events.js) where an
equivalent site exists:
invalid property (undefined / null-mapped / keyword-shaped terms),
free-floating scalar, object with only @value, object with only @list,
object with only @id, object with only @language, empty object,
null @value value, reserved term, reserved @id value,
reserved @reverse value, relative @vocab reference (document-level and
scoped), relative @id reference, relative @type reference (at expansion
and serialization), relative subject reference,
relative predicate reference, relative object reference,
relative graph reference,
blank node predicate (suppressed by produceGeneralizedRdf: true),
invalid @language value (malformed BCP47 tags — at expansion, toRdf, and
fromRdf, like jsonld.js), rdfDirection not set.
Drop sites specific to this implementation carry their own codes:
context load failed (a scoped context needs a DocumentLoader none is
wired), invalid scoped term definition,
invalid @direction value (a malformed @direction in hand-built expanded
input handed directly to the serializer — the public pipeline rejects it at
expansion as an unconditional error), invalid @id value (non-string @id relabelled
as a blank node), invalid @value serialization (non-scalar @value coerced
to ""), invalid @json serialization (NaN/Infinity in a @json literal),
invalid map key (PHP decodes numeric-string JSON keys to integers, which
jsonld.js would process), and dropped object (residual malformed shapes).
Not flagged, by design: null property/entry values (the spec's removal
semantics, not data loss), a scoped '@language': null / '@direction': null
reset (this processor's output for it is already correct), @index entries
that carry no RDF statement (spec-correct), frame match patterns during
frame() (wildcards and @value: null are queries, not data), and
spec-mandated duplicate collapsing in the node map.
One deliberate divergence from jsonld.js: a genuine keyword used as a node
entry where it is meaningless (@default / @vocab / a frame keyword outside
a frame) throws invalid property here, because its value IS dropped from the
expanded output — jsonld.js safe mode stays silent on those.
Conformance
The package is tested against the
official W3C JSON-LD 1.1 test suite
(git submodule at tests/w3c/; harness documented in
tests/W3c/README.md).
| Algorithm | Spec | W3C suite | Passing | Conformance |
|---|---|---|---|---|
| Expansion | §5.5 | 385 | 381 | 4 documented blockers |
| Compaction | §5.6 | 246 | 246 | 100% |
Serialize to RDF (toRdf) |
§7 | 467 | 463 | 4 documented blockers |
| Flattening | §4.6 | 58 | 58 | 100% |
RDF to JSON-LD (fromRdf) |
§4.9 | 54 | 50 | 4 documented blockers |
| Framing | framing §4 | 92 | 89 | 3 documented blockers |
Totals: 1,287 / 1,302 passing. The 15 residual non-conformances are
negative-test, non-normative, or environment limits, carried as an explicit
expected-failure allowlist
(tests/W3c/KnownBlockers.php) that gates CI in
both directions — a new regression fails the build, and so does a listed
blocker that starts passing:
#tc032/#tc033— unused embedded contexts aren't validated (negative tests)#ter56— redefining the@contextkeyword isn't rejected (negative test)#t0122(expand only) — keyword-shaped (@) IRIs are dropped rather than kept as{@id: null}(non-normative)#tjs10(toRdf only) — JSON-literal structural canonicalization differs (PHP cannot distinguish{}from[])#t0008/#tli03(fromRdf) — list-of-lists conversion; single-level lists are fully supported#tdi11/#tdi12(fromRdf, non-normative) — compound-literal direction folding#t0010(framing) — compaction safe-mode rejectsdcterms:creatoras an IRI confused with thedctermsprefix (the reference processor errors here too)#t0045(framing) —@languagecase-normalization (expansion preserves case, which the signature-criticaltoRdfbytes depend on)#t0059(framing) — the legacy@embed: @lastmode (the@oncedefault is implemented)
composer test # unit tests (includes the interop corpus) composer test:w3c # the W3C conformance suites
Implementation report
A W3C EARL implementation report is generated for each release and submitted to the JSON-LD 1.1 implementation report. To regenerate it:
vendor/bin/pest --testsuite W3C --log-junit w3c-junit.xml php scripts/generate-earl-report.php w3c-junit.xml <version> > reports/php-json-ld-earl.ttl
Tests marked specVersion: json-ld-1.0 are excluded from the report, matching
the consolidated report's JSON-LD 1.1 scope.
Interoperability
The W3C suite alone cannot catch a processor that is consistently wrong:
internal sign/verify round-trips recompute the same dataset on both sides, and
a divergence only surfaces when another implementation checks the signature.
tests/Interop closes that gap with realistic VC 2.0 and
Open Badges v3 documents whose RDFC-1.0 canonical N-Quads — the exact bytes an
eddsa-rdfc-2022 proof is computed over — are pinned to goldens generated by
jsonld.js and compared as datasets on every CI run.
Development notes
tests/Algorithms/Characterization/ holds JSON snapshots of the expander's
output over sample verifiable-credential documents. They are not a
spec-conformance reference; they pin behaviour so any change to expansion
output lands as a reviewable diff, to be paired with matching updates in
downstream consumers (e.g. signed-credential fixtures).
License
MIT © Accredify