Overview
Dydra is an excellent cloud-based RDF triple store, but in some cases its JSON-LD serialization may produce output that differs from expectations. This blog post explains the observed behavior and the workaround we implemented.
Observed Behavior
Expected Output
In the JSON-LD specification, URI references are commonly output in object form as follows:
{
"@id": "https://example.com/item/1",
"@type": ["prov:Entity"],
"prov:wasAttributedTo": {
"@id": "https://sepolia.etherscan.io/address/0x1234..."
},
"prov:wasGeneratedBy": {
"@id": "https://sepolia.etherscan.io/tx/0xabcd..."
}
}
Output Observed in Dydra
In Dydra’s JSON-LD endpoint, some URI references were observed to be output as plain strings:
{
"@id": "https://example.com/item/1",
"@type": ["prov:Entity"],
"prov:wasAttributedTo": "https://sepolia.etherscan.io/address/0x1234...",
"prov:wasGeneratedBy": "https://sepolia.etherscan.io/tx/0xabcd..."
}
Note: This behavior does not occur for all properties and may vary depending on the @context definition and property types.
Impact of Behavioral Differences
| Format | JSON-LD Parser Interpretation |
|---|---|
{ "@id": "..." } | URI reference (link to another node) |
"..." | Literal string |
This difference can cause the following impacts:
- Affects graph structure traversal
- Affects some SPARQL query results
- Affects JSON-LD framing processing
Regarding Typed Literals
Similarly, type information may be omitted for typed literals such as xsd:dateTime.
Expected output:
{
"prov:startedAtTime": {
"@value": "2025-01-15T10:30:00Z",
"@type": "xsd:dateTime"
}
}
Observed output:
{
"prov:startedAtTime": "2025-01-15T10:30:00Z"
}
Workaround
Approach: Fetch in TTL Format and Build JSON-LD
Since Dydra serializes Turtle (TTL) format accurately, the following strategy was adopted:
[Client]
|
| Accept: text/turtle
v
[Dydra SPARQL Endpoint]
|
| Returns in TTL format
v
[n3 Parser]
|
| Converts to Quads
v
[JSON-LD Construction Logic]
|
| Correct JSON-LD
v
[Application]
Implementation
import { Parser } from "n3";
/**
* Parse TTL and convert to JSON-LD
* Workaround for Dydra's JSON-LD serialization behavior
*/
function turtleToJsonLd(turtle: string): RDFGraph {
const parser = new Parser();
const quads = parser.parse(turtle);
// Group triples by subject
const subjects = new Map<string, Map<string, unknown[]>>();
for (const quad of quads) {
const subjectId = quad.subject.value;
if (!subjects.has(subjectId)) {
subjects.set(subjectId, new Map());
}
const predicates = subjects.get(subjectId)!;
const predicateId = quad.predicate.value;
if (!predicates.has(predicateId)) {
predicates.set(predicateId, []);
}
// Build object values with type information
let objectValue: unknown;
if (quad.object.termType === "NamedNode") {
// URI reference: { "@id": "..." } <- This is the key point
objectValue = { "@id": quad.object.value };
} else if (quad.object.termType === "Literal") {
const literal = quad.object;
if (literal.language) {
// Language-tagged literal
objectValue = { "@value": literal.value, "@language": literal.language };
} else if (literal.datatype &&
literal.datatype.value !== "http://www.w3.org/2001/XMLSchema#string") {
// Typed literal (other than xsd:string)
objectValue = { "@value": literal.value, "@type": literal.datatype.value };
} else {
// Plain literal
objectValue = literal.value;
}
} else if (quad.object.termType === "BlankNode") {
objectValue = { "@id": `_:${quad.object.value}` };
} else {
objectValue = quad.object.value;
}
predicates.get(predicateId)!.push(objectValue);
}
// Build JSON-LD @graph
const graph: Array<Record<string, unknown>> = [];
for (const [subjectId, predicates] of subjects) {
const node: Record<string, unknown> = { "@id": subjectId };
for (const [predicateId, objects] of predicates) {
if (predicateId === "http://www.w3.org/1999/02/22-rdf-syntax-ns#type") {
// Special handling for @type
node["@type"] = objects.map((o) => {
if (typeof o === "object" && o !== null && "@id" in o) {
return (o as { "@id": string })["@id"];
}
return o;
});
} else {
// Extract from array for single values
node[predicateId] = objects.length === 1 ? objects[0] : objects;
}
}
graph.push(node);
}
return {
"@context": JSONLD_CONTEXT,
"@graph": graph,
};
}
Usage Example
// Fetch in TTL format and convert
const response = await fetch(`${DYDRA_ENDPOINT}/sparql`, {
method: "POST",
headers: {
"Accept": "text/turtle", // Fetch as TTL
},
body: query,
});
const turtle = await response.text();
const jsonld = turtleToJsonLd(turtle); // Convert to JSON-LD
Required Dependencies
This workaround requires the n3 library:
npm install n3
npm install --save-dev @types/n3
Summary
| Item | Details |
|---|---|
| Observed behavior | Some URI references become strings instead of { "@id": "..." } |
| Occurrence conditions | Not all cases; varies by context and properties |
| Workaround | Fetch in TTL format, parse with n3, and build JSON-LD |
| Additional dependency | n3 |
When using Dydra, if the JSON-LD output format differs from expectations, fetching in TTL format and converting on the client side is an effective approach.
Reference Links
- Dydra - Cloud RDF Store
- JSON-LD 1.1 - W3C Specification
- N3.js - JavaScript RDF Parser
- RDF 1.1 Turtle - Turtle Syntax Specification
Last updated: 2025-12-29