Skip to content

Commit bc62083

Browse files
committed
GH-5573: support to refine the @context of a JSON-LD document
This change introduces a new setting for the JSONLDWriter, that allows to configure a JSONLDContextProvider. With such JSONLDContextProvider it is possible to refine the JSON-LD @context in an application.
1 parent a12d9b1 commit bc62083

File tree

4 files changed

+131
-0
lines changed

4 files changed

+131
-0
lines changed
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2025 Eclipse RDF4J contributors, Aduna, and others.
3+
*
4+
* All rights reserved. This program and the accompanying materials
5+
* are made available under the terms of the Eclipse Distribution License v1.0
6+
* which accompanies this distribution, and is available at
7+
* http://www.eclipse.org/org/documents/edl-v10.php.
8+
*
9+
* SPDX-License-Identifier: BSD-3-Clause
10+
*******************************************************************************/
11+
package org.eclipse.rdf4j.rio.jsonld;
12+
13+
import org.eclipse.rdf4j.rio.RioSetting;
14+
15+
import jakarta.json.JsonObjectBuilder;
16+
17+
/**
18+
* Interface for refining a JSON-LD context.
19+
*
20+
* Instances can be set to a {@link JSONLDWriter} using the {@link JSONLDSettings#CONTEXT_PROVIDER} (see
21+
* {@link JSONLDWriter#set(RioSetting, Object)}).
22+
*
23+
* @author Andreas Schwarte
24+
*/
25+
public interface JSONLDContextProvider {
26+
27+
/**
28+
* Provide additional elements to the JSON-LD context.
29+
* <p>
30+
* It can be used to further shorten abbreviated IRIs resulting from defined namespaces.
31+
* </p>
32+
* <p>
33+
* Example:
34+
* </p>
35+
*
36+
* <pre>
37+
* context.add("title", "dcterms:title");
38+
* </pre>
39+
*
40+
* @param context the {@link JsonObjectBuilder}
41+
*/
42+
public void refineContext(JsonObjectBuilder context);
43+
44+
}

core/rio/jsonld/src/main/java/org/eclipse/rdf4j/rio/jsonld/JSONLDSettings.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,14 @@ public class JSONLDSettings {
195195
"The document loader cache is enabled by default. All loaded documents, such as remote contexts, are cached for 1 hour, or until the cache is full. The cache holds up to 1000 documents. The cache is shared between all JSONLDParsers. The cache can be disabled by setting this value to false.",
196196
Boolean.TRUE);
197197

198+
/**
199+
* {@link RioSetting} to register a {@link JSONLDContextProvider} to a {@link JSONLDWriter}.
200+
*/
201+
public static final RioSetting<JSONLDContextProvider> CONTEXT_PROVIDER = new RioSettingImpl<>(
202+
"org.eclipse.rdf4j.rio.jsonld_context_provider",
203+
"Custom context provider for JSON-LD. Allows to modify the JSON-LD @context, e.g., to use more compact serializations. Can be used with JSONLD Mode 'COMPACT'.",
204+
null);
205+
198206
/**
199207
* Private default constructor.
200208
*/

core/rio/jsonld/src/main/java/org/eclipse/rdf4j/rio/jsonld/JSONLDWriter.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,12 @@ public int size() {
275275
context.add(namespace.getPrefix(), namespace.getName());
276276
}
277277
}
278+
279+
var customContextProvider = writerConfig.get(JSONLDSettings.CONTEXT_PROVIDER);
280+
if (customContextProvider != null) {
281+
customContextProvider.refineContext(context);
282+
}
283+
278284
jsonld = JsonLd.compact(JsonDocument.of(jsonld), JsonDocument.of(context.build())).options(opts).get();
279285
break;
280286
}

core/rio/jsonld/src/test/java/org/eclipse/rdf4j/rio/jsonld/JSONLDWriterTest.java

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,12 @@
2424
import org.eclipse.rdf4j.model.Model;
2525
import org.eclipse.rdf4j.model.Statement;
2626
import org.eclipse.rdf4j.model.impl.LinkedHashModel;
27+
import org.eclipse.rdf4j.model.util.ModelBuilder;
28+
import org.eclipse.rdf4j.model.util.Values;
2729
import org.eclipse.rdf4j.model.vocabulary.DCTERMS;
30+
import org.eclipse.rdf4j.model.vocabulary.FOAF;
31+
import org.eclipse.rdf4j.model.vocabulary.RDF;
32+
import org.eclipse.rdf4j.model.vocabulary.RDFS;
2833
import org.eclipse.rdf4j.model.vocabulary.XSD;
2934
import org.eclipse.rdf4j.rio.ParserConfig;
3035
import org.eclipse.rdf4j.rio.RDFFormat;
@@ -39,9 +44,12 @@
3944
import org.eclipse.rdf4j.rio.helpers.BasicParserSettings;
4045
import org.eclipse.rdf4j.rio.helpers.BasicWriterSettings;
4146
import org.eclipse.rdf4j.rio.helpers.StatementCollector;
47+
import org.junit.jupiter.api.Assertions;
4248
import org.junit.jupiter.api.Disabled;
4349
import org.junit.jupiter.api.Test;
4450

51+
import jakarta.json.Json;
52+
import jakarta.json.JsonObjectBuilder;
4553
import no.hasmac.jsonld.JsonLdError;
4654
import no.hasmac.jsonld.document.Document;
4755
import no.hasmac.jsonld.document.JsonDocument;
@@ -238,6 +246,71 @@ public void testFraming() throws IOException, JsonLdError {
238246

239247
}
240248

249+
@Test
250+
public void testContextRefine() throws Exception {
251+
252+
Model model = new ModelBuilder()
253+
.subject(Values.iri("https://example.com/bob"))
254+
.add(RDF.TYPE, FOAF.PERSON)
255+
.add(RDFS.LABEL, Values.literal("Bob", "en"))
256+
.add(FOAF.NAME, Values.literal("Bob"))
257+
.build();
258+
259+
model.setNamespace("rdfs", RDFS.NAMESPACE);
260+
model.setNamespace("foaf", FOAF.NAMESPACE);
261+
model.setNamespace("", "https://example.com/");
262+
263+
StringWriter sw = new StringWriter();
264+
265+
JSONLDWriter mpJsonLd = new JSONLDWriter(sw);
266+
mpJsonLd.set(JSONLDSettings.JSONLD_MODE, JSONLDMode.COMPACT);
267+
mpJsonLd.set(BasicWriterSettings.PRETTY_PRINT, true);
268+
mpJsonLd.set(JSONLDSettings.CONTEXT_PROVIDER, new JSONLDContextProvider() {
269+
270+
@Override
271+
public void refineContext(JsonObjectBuilder context) {
272+
273+
JsonObjectBuilder labelObjectBuilder = Json.createObjectBuilder();
274+
labelObjectBuilder.add("@id", "rdfs:label");
275+
labelObjectBuilder.add("@container", "@language");
276+
context.add("label", labelObjectBuilder);
277+
278+
context.add("name", "foaf:name");
279+
280+
context.add("Person", "foaf:Person");
281+
282+
}
283+
});
284+
285+
Rio.write(model, mpJsonLd);
286+
287+
Assertions.assertEquals(
288+
"{\n"
289+
+ " \"@id\": \"https://example.com/bob\",\n"
290+
+ " \"@type\": \"Person\",\n"
291+
+ " \"label\": {\n"
292+
+ " \"en\": \"Bob\"\n"
293+
+ " },\n"
294+
+ " \"name\": \"Bob\",\n"
295+
+ " \"@context\": {\n"
296+
+ " \"rdfs\": \"http://www.w3.org/2000/01/rdf-schema#\",\n"
297+
+ " \"foaf\": \"http://xmlns.com/foaf/0.1/\",\n"
298+
+ " \"@vocab\": \"https://example.com/\",\n"
299+
+ " \"label\": {\n"
300+
+ " \"@id\": \"rdfs:label\",\n"
301+
+ " \"@container\": \"@language\"\n"
302+
+ " },\n"
303+
+ " \"name\": \"foaf:name\",\n"
304+
+ " \"Person\": \"foaf:Person\"\n"
305+
+ " }\n"
306+
+ "}",
307+
sw.toString());
308+
309+
// test round-trip
310+
Model parsed = Rio.parse(new StringReader(sw.toString()), RDFFormat.JSONLD);
311+
Assertions.assertEquals(model, parsed);
312+
}
313+
241314
@Override
242315
protected RioSetting<?>[] getExpectedSupportedSettings() {
243316
return new RioSetting[] {

0 commit comments

Comments
 (0)