Skip to content

rdf/turtle: emit @type:'@json' literal values (rdf:JSON datatype) — Phase B blocker for CID publicKeyJwk #390

@melvincarvalho

Description

@melvincarvalho

Surfaced during #388 (Phase A of #386) Copilot review.

Problem

JSON-LD 1.1 supports JSON-literal values via @type: '@json' — used for embedding plain objects (like a JWK) in RDF as rdf:JSON datatype literals. JSS's Turtle conneg layer doesn't currently emit them.

src/rdf/turtle.js:valueToTerm (around line 350) only handles object values that are one of:

  • { "@id": ... } — IRI reference
  • { "@value": ..., "@language": ... } — plain literal
  • { "@value": ..., "@type": ... } — typed literal
  • { "@value": ... } — plain literal

A plain object value (e.g. a JWK {kty: "EC", crv: "P-256", x: "...", y: "..."}) returns null and is silently dropped.

Why it matters

#388's @context defines publicKeyJwk with @type: '@json' so a Phase B "add my keys" app can PATCH:

{
  "verificationMethod": [{
    "id": "...#passkey-1",
    "type": "JsonWebKey",
    "controller": "...#me",
    "publicKeyJwk": { "kty": "EC", "crv": "P-256", "x": "...", "y": "..." }
  }]
}

Without @type:'@json' handling in turtle.js, the JWK gets dropped on Turtle conneg — clients negotiating Turtle would see no key bytes, breaking auth.

Fix shape

Extend valueToTerm to handle the @json case. Two recognition paths:

  1. Context-driven: when the property's context entry says @type: '@json', serialize the entire value (after JSON.stringify) as a single literal with datatype http://www.w3.org/1999/02/22-rdf-syntax-ns#JSON.
  2. Inline-typed value: { "@value": <object>, "@type": "@json" } — same output, value already typed inline.

Pseudocode addition in valueToTerm:

// @type:@json literal — JSON-LD 1.1 §4.2.2
if (isJsonType || (typeof value === 'object' && value['@type'] === '@json')) {
  const json = JSON.stringify(value['@value'] !== undefined ? value['@value'] : value);
  return literal(json, namedNode('http://www.w3.org/1999/02/22-rdf-syntax-ns#JSON'));
}

Where isJsonType is plumbed in from the context lookup (similar to existing isIdType).

Acceptance

  • A document with @context: { foo: { "@id": "ex:foo", "@type": "@json" } } and value foo: {a: 1, b: [2, 3]} round-trips to Turtle as a single rdf:JSON literal containing the canonical-ish JSON serialization
  • A document with bar: { "@value": {a: 1}, "@type": "@json" } (inline-typed form) also works
  • Existing valueToTerm paths (@id, @value+@language, @value+@type, @value) untouched
  • Test added for both forms in the Turtle conneg suite

Refs

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions