Skip to content

What's new in version 4

PyLD 4 moves the RDF datastructure to RDFLib and adds RDF Dataset Canonicalization 1.0 support. The JSON-LD document APIs remain the same, but RDF-facing code should review return types, canonicalization defaults, and N-Quads behavior before upgrading.

Development builds use uv

PyLD 4 uses uv for source checkout workflows. Run uv sync --locked or make install to create the locked development environment, then run commands through that environment, such as uv run --locked pytest or make docs-build.

Release artifacts are built with uv build --no-sources. The package build backend remains setuptools.build_meta as declared in pyproject.toml; uv is the project workflow for syncing, running, building, and publishing.

RDFLib datasets are now the native RDF model

jsonld.to_rdf() returns an rdflib.Dataset by default when format is not set. In PyLD 3.x and earlier, it returned a RDF.js-like nested dict.

Example to_rdf_dataset.py

from pyld import jsonld

doc = {
    "@context": {
        "name": "http://schema.org/name",
    },
    "@id": "http://dbpedia.org/resource/Earth",
    "name": "Earth",
}

dataset = jsonld.to_rdf(doc)

print(type(dataset).__name__)
print(dataset.serialize(format="nquads"))
Output
Dataset
<http://dbpedia.org/resource/Earth> <http://schema.org/name> "Earth"^^<http://www.w3.org/2001/XMLSchema#string>  .

Request N-Quads when you need a serialized string:

Example to_rdf.py

from pyld import jsonld

doc = {
    "@context": {
        "name": "http://schema.org/name",
    },
    "@id": "http://dbpedia.org/resource/Earth",
    "name": "Earth",
}

print(jsonld.to_rdf(doc, {"format": "application/n-quads"}))
Output
<http://dbpedia.org/resource/Earth> <http://schema.org/name> "Earth"^^<http://www.w3.org/2001/XMLSchema#string>  .

Use legacyMode when existing code still expects the PyLD 3.x dataset dict:

Example to_rdf_legacy.py

import json

from pyld import jsonld

doc = {
    "@context": {
        "name": "http://schema.org/name",
    },
    "@id": "http://dbpedia.org/resource/Earth",
    "name": "Earth",
}

legacy_dataset = jsonld.to_rdf(doc, {"legacyMode": True})

print(json.dumps(legacy_dataset, indent=2))
Output
{
  "@default": [
    {
      "subject": {
        "type": "IRI",
        "value": "http://dbpedia.org/resource/Earth"
      },
      "predicate": {
        "type": "IRI",
        "value": "http://schema.org/name"
      },
      "object": {
        "type": "literal",
        "value": "Earth",
        "datatype": "http://www.w3.org/2001/XMLSchema#string"
      }
    }
  ]
}

jsonld.from_rdf() accepts an rdflib.Dataset, an N-Quads string, or the legacy dataset dict. New code should prefer rdflib.Dataset for in-memory RDF work and application/n-quads for process or storage boundaries.

RDFC10 is available and is the normalization default

jsonld.normalize() now defaults to RDFC10, the RDF Dataset Canonicalization 1.0 algorithm. URDNA2015 and URGNA2012 remain available by setting algorithm explicitly.

Example normalize.py

from pyld import jsonld

doc = {
    "@type": "http://schema.org/Person",
    "http://schema.org/name": "Manu Sporny",
    "http://schema.org/url": {"@id": "http://manu.sporny.org/"},
    "http://schema.org/image": {
        "@id": "http://manu.sporny.org/images/manu.png"
    },
}

# Normalize with RDFC10
normalized = jsonld.normalize(
    doc,
    {"format": "application/n-quads"},
)

print(normalized)

# Normalize with URDNA2015
normalized = jsonld.normalize(
    doc,
    {"algorithm": "URDNA2015", "format": "application/n-quads"},
)

print(normalized)
Output
_:c14n0 <http://schema.org/image> <http://manu.sporny.org/images/manu.png> .
_:c14n0 <http://schema.org/name> "Manu Sporny" .
_:c14n0 <http://schema.org/url> <http://manu.sporny.org/> .
_:c14n0 <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://schema.org/Person> .

_:c14n0 <http://schema.org/image> <http://manu.sporny.org/images/manu.png> .
_:c14n0 <http://schema.org/name> "Manu Sporny" .
_:c14n0 <http://schema.org/url> <http://manu.sporny.org/> .
_:c14n0 <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://schema.org/Person> .

For RDFC 1.0 test vectors and integrations that need the canonical blank node identifier map, pass outputMap:

Example normalize_output_map.py

import json

from pyld import jsonld

doc = {
    "@type": "http://schema.org/Person",
    "http://schema.org/name": "Manu Sporny",
    "http://schema.org/url": {"@id": "http://manu.sporny.org/"},
    "http://schema.org/image": {
        "@id": "http://manu.sporny.org/images/manu.png"
    },
}

identifier_map = jsonld.normalize(
    doc,
    {"algorithm": "RDFC10", "outputMap": True},
)

print(json.dumps(identifier_map, indent=2))
Output
{
  "b0": "c14n0"
}

RDFC10 also accepts hashAlgorithm for test suites and specialized integrations. Most applications should keep the default SHA-256 behavior.

N-Quads parsing and serialization delegates to RDFLib

PyLD 4 removes the internal pyld.nquads parser and serializer module. Public JSON-LD APIs still accept and produce N-Quads through format: "application/n-quads", but imports from pyld.nquads need to be removed.

If you previously used pyld.nquads directly, replace it with one of these paths:

  • Use jsonld.from_rdf(nquads, {"format": "application/n-quads"}) to convert N-Quads to JSON-LD.
  • Use jsonld.to_rdf(doc, {"format": "application/n-quads"}) to serialize JSON-LD as N-Quads.
  • Use jsonld.parse_nquads(doc, {"legacyMode": True}) to convert nquads to a RDF.js-like nested dict from PyLD 3.x and earlier. Omit legacyMode to return an rdflib.Dataset. This method preserves blank node identifiers from the input document.
  • Use rdflib.Dataset().parse(data=nquads, format="nquads") or rdflib.plugins.parsers.nquads.NQuadsParser() for direct RDFLib parsing. Note that, opposed to jsonld.parse_nquads, this does NOT preserve blank node identifiers by default.

Compatibility helpers

PyLD 4 includes conversion helpers for applications that need to bridge between the old in-memory RDF.js-like nested dict and RDFLib:

Example legacy_helpers.py

import json

from pyld.util import from_legacy_dataset, to_legacy_dataset

legacy_dataset = {
        'http://example.org': [
            {
                'subject': {'type': 'blank node', 'value': '_:s1'},
                'predicate': {'type': 'IRI', 'value': 'p1'},
                'object': {'type': 'literal', 'value': 'o1'},
            }
        ]
}


rdflib_dataset = from_legacy_dataset(legacy_dataset)
print(rdflib_dataset.serialize(format="nquads"))

legacy_dataset = to_legacy_dataset(rdflib_dataset)
print(json.dumps(legacy_dataset, indent=2))
Output
_:s1 <p1> "o1" <http://example.org> .


{
  "@default": [],
  "http://example.org": [
    {
      "subject": {
        "type": "blank node",
        "value": "_:s1"
      },
      "predicate": {
        "type": "IRI",
        "value": "p1"
      },
      "object": {
        "type": "literal",
        "value": "o1"
      }
    }
  ]
}

Treat these helpers as migration aids. Prefer RDFLib terms and datasets in new code so RDF processing is compatible with the rest of the Python RDF ecosystem.

Behavior fixes to expect

The RDFLib migration also fixes several RDF conversion edge cases:

  • RDF literal lexical forms are preserved more carefully through RDFLib conversion, including canonical double output, large numeric values, and compound literals.
  • Invalid IRI and language values are skipped during jsonld.to_rdf() instead of producing invalid triples or crashing.
  • Query and fragment reconstruction in iri_resolver.unresolve() is corrected.
  • More W3C URDNA2015, URDNA2012, RDFC10, and JSON-LD toRdf tests run through the default test runner.

Upgrade checklist

  • Install uv and run uv sync --locked when working from a source checkout.
  • Add rdflib to application constraints if dependencies are pinned outside PyLD's package metadata.
  • Audit jsonld.to_rdf() call sites that do not pass format. Update them to handle rdflib.Dataset or temporarily pass legacyMode.
  • Remove imports of pyld.nquads.
  • Check normalization call sites that relied on the old default algorithm. Pass {"algorithm": "URDNA2015"} explicitly if that output must remain stable.
  • Compare N-Quads as RDF data or sorted lines in tests unless the test requires exact serializer ordering.