Repository navigation
JSON-LD parser silently drops list-valued entries under "@container": "@type" #3538
Description
Summary
When a context term declares "@container": "@type" and the type key's value is a list of node objects, rdflib produces no triples at all and raises no error. The same document parses correctly with pyld. A single node object (not a list) under the same term works fine, which localises the defect to list handling.
This is silent data loss: the parse succeeds, the graph is empty, and nothing indicates a problem.
Minimal reproducer
"""rdflib silently drops list-valued entries under an "@container": "@type" term.
"Expected" is what pyld, the JSON-LD reference implementation, produces for the same document.
"""
import json
import rdflib
from pyld import jsonld
CTX = {
"@version": 1.1,
"ex": "http://example.org/",
"byType": {"@container": "@type", "@id": "@graph"},
"Person": "ex:Person",
"name": "ex:name",
}
ALICE = {"@id": "ex:alice", "name": "Alice"}
BOB = {"@id": "ex:bob", "name": "Bob"}
CASES = [
("type key holds a LIST (two people)", {"@context": CTX, "byType": {"Person": [ALICE, BOB]}}),
("type key holds a DICT (one person)", {"@context": CTX, "byType": {"Person": ALICE}}),
]
print(f"rdflib {rdflib.__version__}")
for label, doc in CASES:
native = rdflib.Graph()
native.parse(data=json.dumps(doc), format="json-ld")
viapyld = rdflib.Graph()
viapyld.parse(data=json.dumps(jsonld.expand(doc)), format="json-ld")
verdict = "ok" if len(native) == len(viapyld) else "SILENT DATA LOSS"
print(f" {label}: rdflib {len(native)} triples, expected {len(viapyld)} {verdict}")Actual output
rdflib 7.6.0
type key holds a LIST (two people): rdflib 0 triples, expected 4 SILENT DATA LOSS
type key holds a DICT (one person): rdflib 2 triples, expected 2 ok
Expected
Both rows should agree with pyld. JSON-LD 1.1 §9.8 (Type Maps) permits a type map's value to be a node object or an array of node objects.
Versions affected
Identical result — 0 triples, expected 4 — on every released version tested:
| rdflib | list-valued type key |
|---|---|
| 6.3.2 | 0 triples (expected 4) |
| 7.0.0 | 0 triples (expected 4) |
| 7.1.4 | 0 triples (expected 4) |
| 7.6.0 (latest) | 0 triples (expected 4) |
Environment: CPython 3.12, macOS arm64, pyld 3.3.0 as the reference. Installed via uv run --with rdflib==<version>.
Where it appears to come from
rdflib/plugins/parsers/jsonld.py:489, the TYPE-container branch of _parse_container:
elif v11 and TYPE in term.container:
return [
(
self._add_type(
context,
(
{ID: context.expand(o) if term.type == VOCAB else o}
if isinstance(o, str)
else o
),
k,
)
if isinstance(o, (dict, str)) and k not in context.get_keys(NONE)
else o
)
for k, o in obj.items()
]The comprehension handles o being a dict or str. When o is a list, the guard isinstance(o, (dict, str)) is false and the list is returned unchanged as a single element, so a list of node objects is then handed downstream as though it were one node object and is dropped.
Note the dict case only works when the term also has "@id": "@graph"; a @container: @type term with no @id is rejected by pyld as invalid JSON-LD, so the reproducer above uses the valid form.
Why this matters in practice
The BIDS provenance specification under review (bids-standard/bids-specification#2099) declares exactly this shape:
"Records": {"@container": "@type", "@id": "@graph"}and every record group in it (Files, prov:Entity, Activities, Software) is a JSON array, including singletons. A conforming BIDS-Prov document therefore loses everything when read with rdflib. On a real 6.1 MB provenance graph, pyld expands to 78,681 triples while rdflib 7.6.0 returns 2 — the two metadata keys that happen to sit outside the container. No warning is emitted.
Workaround
Expanding with pyld first and handing the expanded document to rdflib recovers the full graph — 78,681 triples on the same file, matching pyld's own count:
g = rdflib.Graph()
g.parse(data=json.dumps(jsonld.expand(doc)), format="json-ld")Possibly related
- Run all JSON-LD 1.1 tests #1833 — Run all JSON-LD 1.1 tests
- JSON-LD version in RDFLib? #2996 — JSON-LD version in RDFLib?
- Use pyld as the json-ld parser for rdflib #1836 — Use pyld as the json-ld parser for rdflib
- apply fix-multiple-type-scoped-contexts #3422 — multiple type-scoped contexts (a different 1.1 feature; does not cover type-scoped containers)
I could not find an existing issue for this specific defect. Happy to open a PR if the fix in _parse_container is as narrow as it looks.
Activity