Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*******************************************************************************
* Copyright (c) 2025 Eclipse RDF4J contributors, Aduna, and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Distribution License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: BSD-3-Clause
*******************************************************************************/
package org.eclipse.rdf4j.rio.jsonld;

import org.eclipse.rdf4j.rio.RioSetting;

import jakarta.json.JsonObjectBuilder;

/**
* Interface for refining a JSON-LD context.
*
* Instances can be set to a {@link JSONLDWriter} using the {@link JSONLDSettings#CONTEXT_PROVIDER} (see
* {@link JSONLDWriter#set(RioSetting, Object)}).
*
* @author Andreas Schwarte
*/
public interface JSONLDContextProvider {

/**
* Provide additional elements to the JSON-LD context.
* <p>
* It can be used to further shorten abbreviated IRIs resulting from defined namespaces.
* </p>
* <p>
* Example:
* </p>
*
* <pre>
* context.add("title", "dcterms:title");
* </pre>
*
* @param context the {@link JsonObjectBuilder}
*/
public void refineContext(JsonObjectBuilder context);

}
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,14 @@ public class JSONLDSettings {
"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.",
Boolean.TRUE);

/**
* {@link RioSetting} to register a {@link JSONLDContextProvider} to a {@link JSONLDWriter}.
*/
public static final RioSetting<JSONLDContextProvider> CONTEXT_PROVIDER = new RioSettingImpl<>(
"org.eclipse.rdf4j.rio.jsonld_context_provider",
"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'.",
null);

/**
* Private default constructor.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,12 @@ public int size() {
context.add(namespace.getPrefix(), namespace.getName());
}
}

var customContextProvider = writerConfig.get(JSONLDSettings.CONTEXT_PROVIDER);
if (customContextProvider != null) {
customContextProvider.refineContext(context);
}

jsonld = JsonLd.compact(JsonDocument.of(jsonld), JsonDocument.of(context.build())).options(opts).get();
break;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,12 @@
import org.eclipse.rdf4j.model.Model;
import org.eclipse.rdf4j.model.Statement;
import org.eclipse.rdf4j.model.impl.LinkedHashModel;
import org.eclipse.rdf4j.model.util.ModelBuilder;
import org.eclipse.rdf4j.model.util.Values;
import org.eclipse.rdf4j.model.vocabulary.DCTERMS;
import org.eclipse.rdf4j.model.vocabulary.FOAF;
import org.eclipse.rdf4j.model.vocabulary.RDF;
import org.eclipse.rdf4j.model.vocabulary.RDFS;
import org.eclipse.rdf4j.model.vocabulary.XSD;
import org.eclipse.rdf4j.rio.ParserConfig;
import org.eclipse.rdf4j.rio.RDFFormat;
Expand All @@ -39,9 +44,12 @@
import org.eclipse.rdf4j.rio.helpers.BasicParserSettings;
import org.eclipse.rdf4j.rio.helpers.BasicWriterSettings;
import org.eclipse.rdf4j.rio.helpers.StatementCollector;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;

import jakarta.json.Json;
import jakarta.json.JsonObjectBuilder;
import no.hasmac.jsonld.JsonLdError;
import no.hasmac.jsonld.document.Document;
import no.hasmac.jsonld.document.JsonDocument;
Expand Down Expand Up @@ -238,6 +246,71 @@ public void testFraming() throws IOException, JsonLdError {

}

@Test
public void testContextRefine() throws Exception {

Model model = new ModelBuilder()
.subject(Values.iri("https://example.com/bob"))
.add(RDF.TYPE, FOAF.PERSON)
.add(RDFS.LABEL, Values.literal("Bob", "en"))
.add(FOAF.NAME, Values.literal("Bob"))
.build();

model.setNamespace("rdfs", RDFS.NAMESPACE);
model.setNamespace("foaf", FOAF.NAMESPACE);
model.setNamespace("", "https://example.com/");

StringWriter sw = new StringWriter();

JSONLDWriter mpJsonLd = new JSONLDWriter(sw);
mpJsonLd.set(JSONLDSettings.JSONLD_MODE, JSONLDMode.COMPACT);
mpJsonLd.set(BasicWriterSettings.PRETTY_PRINT, true);
mpJsonLd.set(JSONLDSettings.CONTEXT_PROVIDER, new JSONLDContextProvider() {

@Override
public void refineContext(JsonObjectBuilder context) {

JsonObjectBuilder labelObjectBuilder = Json.createObjectBuilder();
labelObjectBuilder.add("@id", "rdfs:label");
labelObjectBuilder.add("@container", "@language");
context.add("label", labelObjectBuilder);

context.add("name", "foaf:name");

context.add("Person", "foaf:Person");

}
});

Rio.write(model, mpJsonLd);

Assertions.assertEquals(
"{\n"
+ " \"@id\": \"https://example.com/bob\",\n"
+ " \"@type\": \"Person\",\n"
+ " \"label\": {\n"
+ " \"en\": \"Bob\"\n"
+ " },\n"
+ " \"name\": \"Bob\",\n"
+ " \"@context\": {\n"
+ " \"rdfs\": \"http://www.w3.org/2000/01/rdf-schema#\",\n"
+ " \"foaf\": \"http://xmlns.com/foaf/0.1/\",\n"
+ " \"@vocab\": \"https://example.com/\",\n"
+ " \"label\": {\n"
+ " \"@id\": \"rdfs:label\",\n"
+ " \"@container\": \"@language\"\n"
+ " },\n"
+ " \"name\": \"foaf:name\",\n"
+ " \"Person\": \"foaf:Person\"\n"
+ " }\n"
+ "}",
sw.toString());

// test round-trip
Model parsed = Rio.parse(new StringReader(sw.toString()), RDFFormat.JSONLD);
Assertions.assertEquals(model, parsed);
}

@Override
protected RioSetting<?>[] getExpectedSupportedSettings() {
return new RioSetting[] {
Expand Down
Loading