Skip to content

Commit

Permalink
Added support for JSON-LD 1.1 @JSON @type
Browse files Browse the repository at this point in the history
  • Loading branch information
umbreak committed Feb 28, 2021
1 parent 506d761 commit a312c2b
Show file tree
Hide file tree
Showing 135 changed files with 1,499 additions and 9 deletions.
3 changes: 2 additions & 1 deletion core/src/main/java/com/github/jsonldjava/core/Context.java
Expand Up @@ -374,7 +374,8 @@ private void createTermDefinition(Map<String, Object> context, String term,
// least not here!)
if (JsonLdConsts.ID.equals(type) || JsonLdConsts.VOCAB.equals(type)
|| (!type.startsWith(JsonLdConsts.BLANK_NODE_PREFIX)
&& JsonLdUtils.isAbsoluteIri(type))) {
&& JsonLdUtils.isAbsoluteIri(type)) ||
options.isProcessingMode11() && JsonLdConsts.JSON.equals(type)) {
definition.put(JsonLdConsts.TYPE, type);
} else {
throw new JsonLdError(Error.INVALID_TYPE_MAPPING, type);
Expand Down
39 changes: 35 additions & 4 deletions core/src/main/java/com/github/jsonldjava/core/JsonLdApi.java
Expand Up @@ -8,6 +8,7 @@
import static com.github.jsonldjava.core.JsonLdUtils.isKeyword;
import static com.github.jsonldjava.utils.Obj.newMap;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
Expand All @@ -20,6 +21,7 @@
import java.util.Set;
import java.util.TreeMap;

import com.github.jsonldjava.utils.JsonUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand Down Expand Up @@ -188,7 +190,9 @@ public Object compact(Context activeCtx, String activeProperty, Object element,
// 4
if (elem.containsKey(JsonLdConsts.VALUE) || elem.containsKey(JsonLdConsts.ID)) {
final Object compactedValue = activeCtx.compactValue(activeProperty, elem);
if (!(compactedValue instanceof Map || compactedValue instanceof List)) {
if (!(compactedValue instanceof Map || compactedValue instanceof List) ||
opts.isProcessingMode11() &&
JsonLdConsts.JSON.equals(activeCtx.getTypeMapping(activeProperty))) {
return compactedValue;
}
}
Expand Down Expand Up @@ -645,7 +649,8 @@ else if (JsonLdConsts.GRAPH.equals(expandedProperty)) {
}
// 7.4.6)
else if (JsonLdConsts.VALUE.equals(expandedProperty)) {
if (value != null && (value instanceof Map || value instanceof List)) {
if (value != null && (value instanceof Map || value instanceof List) &&
!opts.isProcessingMode11()) {
throw new JsonLdError(Error.INVALID_VALUE_OBJECT_VALUE,
"value of " + expandedProperty + " must be a scalar or null");
}
Expand Down Expand Up @@ -785,6 +790,12 @@ else if (frameExpansion && (JsonLdConsts.EXPLICIT.equals(expandedProperty)
// 7.4.13)
continue;
}
// 13.6 (from the Json-LD 1.1 spec)
else if (opts.isProcessingMode11() && JsonLdConsts.JSON.equals(activeCtx.getTypeMapping(key))) {
Map<String, Object> temp = newMap(JsonLdConsts.TYPE, JsonLdConsts.JSON);
temp.put(JsonLdConsts.VALUE, value);
expandedValue = temp;
}
// 7.5
else if (JsonLdConsts.LANGUAGE.equals(activeCtx.getContainer(key))
&& value instanceof Map) {
Expand Down Expand Up @@ -926,17 +937,23 @@ else if (JsonLdConsts.INDEX.equals(activeCtx.getContainer(key))
keySet.remove(JsonLdConsts.INDEX);
final boolean langremoved = keySet.remove(JsonLdConsts.LANGUAGE);
final boolean typeremoved = keySet.remove(JsonLdConsts.TYPE);
final boolean isJsonType = JsonLdConsts.JSON.equals(result.get(JsonLdConsts.TYPE));
if ((langremoved && typeremoved) || !keySet.isEmpty()) {
throw new JsonLdError(Error.INVALID_VALUE_OBJECT,
"value object has unknown keys");
}
// 8.2)
final Object rval = result.get(JsonLdConsts.VALUE);
if (rval == null) {
if (rval == null && !opts.isProcessingMode11()) {
// nothing else is possible with result if we set it to
// null, so simply return it
return null;
}
// 13.4.7.1 (from the Json-LD 1.1. spec)
if(opts.isProcessingMode11() && (rval instanceof Map || rval instanceof List) && !isJsonType) {
throw new JsonLdError(Error.INVALID_VALUE_OBJECT_VALUE,
"value of " + activeProperty + " must be a scalar or null");
}
// 8.3)
if (!(rval instanceof String) && result.containsKey(JsonLdConsts.LANGUAGE)) {
throw new JsonLdError(Error.INVALID_LANGUAGE_TAGGED_VALUE,
Expand All @@ -947,7 +964,9 @@ else if (result.containsKey(JsonLdConsts.TYPE)) {
// TODO: is this enough for "is an IRI"
if (!(result.get(JsonLdConsts.TYPE) instanceof String)
|| ((String) result.get(JsonLdConsts.TYPE)).startsWith("_:")
|| !((String) result.get(JsonLdConsts.TYPE)).contains(":")) {
|| (!((String) result.get(JsonLdConsts.TYPE)).contains(":") &&
JsonLdConsts.JSON.equals(result.get(JsonLdConsts.TYPE)) &&
!opts.isProcessingMode11())) {
throw new JsonLdError(Error.INVALID_TYPED_VALUE,
"value of @type must be an IRI");
}
Expand Down Expand Up @@ -1999,6 +2018,18 @@ public List<Object> fromRDF(final RDFDataset dataset, boolean noDuplicatesInData
nodeMap.computeIfAbsent(object.getValue(), k -> new NodeMapNode(k));
}

if (opts.isProcessingMode11() && JsonLdConsts.RDF_JSON.equals(object.getDatatype())) {
try {
final Object json = JsonUtils.fromString(object.getValue());
Map<String, Object> entries = newMap(JsonLdConsts.TYPE, JsonLdConsts.JSON);
entries.put(JsonLdConsts.VALUE, json);
JsonLdUtils.mergeValue(node, predicate, entries);
} catch (IOException e) {
throw new JsonLdError(JsonLdError.Error.INVALID_JSON_LITERAL);
}
continue;
}

// 3.5.4)
if (RDF_TYPE.equals(predicate) && (object.isIRI() || object.isBlankNode())
&& !opts.getUseRdfType() &&
Expand Down
Expand Up @@ -27,6 +27,7 @@ public final class JsonLdConsts {
public static final String RDF_OBJECT = RDF_SYNTAX_NS + "object";
public static final String RDF_LANGSTRING = RDF_SYNTAX_NS + "langString";
public static final String RDF_LIST = RDF_SYNTAX_NS + "List";
public static final String RDF_JSON = RDF_SYNTAX_NS + "JSON";

public static final String TEXT_TURTLE = "text/turtle";
public static final String APPLICATION_NQUADS = "application/n-quads"; // https://www.w3.org/TR/n-quads/#sec-mediatype
Expand Down Expand Up @@ -57,6 +58,7 @@ public final class JsonLdConsts {
public static final String BLANK_NODE_PREFIX = "_:";
public static final String VOCAB = "@vocab";
public static final String BASE = "@base";
public static final String JSON = "@json";
public static final String REQUIRE_ALL = "@requireAll";

public enum Embed {
Expand Down
Expand Up @@ -113,7 +113,9 @@ public enum Error {

PARSE_ERROR("parse error"),

UNKNOWN_ERROR("unknown error");
UNKNOWN_ERROR("unknown error"),

INVALID_JSON_LITERAL("invalid JSON literal");

private final String error;

Expand Down
Expand Up @@ -245,6 +245,10 @@ public String getProcessingMode() {
return processingMode;
}

public boolean isProcessingMode11() {
return JSON_LD_1_1.equals(getProcessingMode());
}

public void setProcessingMode(String processingMode) {
this.processingMode = processingMode;
if (processingMode.equals(JSON_LD_1_1)) {
Expand Down
Expand Up @@ -32,7 +32,7 @@ static boolean isKeyword(Object key) {
|| "@language".equals(key) || "@list".equals(key) || "@omitDefault".equals(key)
|| "@reverse".equals(key) || "@preserve".equals(key) || "@set".equals(key)
|| "@type".equals(key) || "@value".equals(key) || "@vocab".equals(key)
|| "@requireAll".equals(key);
|| "@requireAll".equals(key) || "@json".equals(key);
}

public static Boolean deepCompare(Object v1, Object v2, Boolean listOrderMatters) {
Expand Down
15 changes: 13 additions & 2 deletions core/src/main/java/com/github/jsonldjava/core/RDFDataset.java
@@ -1,5 +1,7 @@
package com.github.jsonldjava.core;

import com.github.jsonldjava.utils.JsonUtils;

import static com.github.jsonldjava.core.JsonLdConsts.RDF_FIRST;
import static com.github.jsonldjava.core.JsonLdConsts.RDF_LANGSTRING;
import static com.github.jsonldjava.core.JsonLdConsts.RDF_NIL;
Expand All @@ -10,13 +12,15 @@
import static com.github.jsonldjava.core.JsonLdConsts.XSD_DOUBLE;
import static com.github.jsonldjava.core.JsonLdConsts.XSD_INTEGER;
import static com.github.jsonldjava.core.JsonLdConsts.XSD_STRING;
import static com.github.jsonldjava.core.JsonLdConsts.RDF_JSON;
import static com.github.jsonldjava.core.JsonLdUtils.isKeyword;
import static com.github.jsonldjava.core.JsonLdUtils.isList;
import static com.github.jsonldjava.core.JsonLdUtils.isObject;
import static com.github.jsonldjava.core.JsonLdUtils.isString;
import static com.github.jsonldjava.core.JsonLdUtils.isValue;
import static com.github.jsonldjava.utils.Obj.newMap;

import java.io.IOException;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.util.ArrayList;
Expand Down Expand Up @@ -645,14 +649,21 @@ else if (JsonLdUtils.isRelativeIri(property)) {
* the JSON-LD value or node object.
* @return the RDF literal or RDF resource.
*/
private Node objectToRDF(Object item) {
private Node objectToRDF(Object item) throws JsonLdError {
// convert value object to RDF
if (isValue(item)) {
final Object value = ((Map<String, Object>) item).get("@value");
final Object datatype = ((Map<String, Object>) item).get("@type");

// convert to XSD datatypes as appropriate
if (value instanceof Boolean || value instanceof Number) {
if(api.opts.isProcessingMode11() && "@json".equals(datatype)) {
try {
return new Literal(JsonUtils.toString(value), RDF_JSON, null);
} catch (IOException e) {
throw new JsonLdError(JsonLdError.Error.INVALID_JSON_LITERAL);
}
}
else if (value instanceof Boolean || value instanceof Number) {
// convert to XSD datatype
if (value instanceof Boolean) {
return new Literal(value.toString(),
Expand Down
@@ -0,0 +1,5 @@
{
"@context": {
"e": {"@id": "http://example.org/vocab#bool", "@type": "@json"}
}
}
3 changes: 3 additions & 0 deletions core/src/test/resources/json-ld.org/compact-js01-in.jsonld
@@ -0,0 +1,3 @@
[{
"http://example.org/vocab#bool": [{"@value": true, "@type": "@json"}]
}]
6 changes: 6 additions & 0 deletions core/src/test/resources/json-ld.org/compact-js01-out.jsonld
@@ -0,0 +1,6 @@
{
"@context": {
"e": {"@id": "http://example.org/vocab#bool", "@type": "@json"}
},
"e": true
}
@@ -0,0 +1,5 @@
{
"@context": {
"e": {"@id": "http://example.org/vocab#bool", "@type": "@json"}
}
}
3 changes: 3 additions & 0 deletions core/src/test/resources/json-ld.org/compact-js02-in.jsonld
@@ -0,0 +1,3 @@
[{
"http://example.org/vocab#bool": [{"@value": false, "@type": "@json"}]
}]
6 changes: 6 additions & 0 deletions core/src/test/resources/json-ld.org/compact-js02-out.jsonld
@@ -0,0 +1,6 @@
{
"@context": {
"e": {"@id": "http://example.org/vocab#bool", "@type": "@json"}
},
"e": false
}
@@ -0,0 +1,5 @@
{
"@context": {
"e": {"@id": "http://example.org/vocab#double", "@type": "@json"}
}
}
3 changes: 3 additions & 0 deletions core/src/test/resources/json-ld.org/compact-js03-in.jsonld
@@ -0,0 +1,3 @@
[{
"http://example.org/vocab#double": [{"@value": 1.23, "@type": "@json"}]
}]
6 changes: 6 additions & 0 deletions core/src/test/resources/json-ld.org/compact-js03-out.jsonld
@@ -0,0 +1,6 @@
{
"@context": {
"e": {"@id": "http://example.org/vocab#double", "@type": "@json"}
},
"e": 1.23
}
@@ -0,0 +1,5 @@
{
"@context": {
"e": {"@id": "http://example.org/vocab#double", "@type": "@json"}
}
}
3 changes: 3 additions & 0 deletions core/src/test/resources/json-ld.org/compact-js04-in.jsonld
@@ -0,0 +1,3 @@
[{
"http://example.org/vocab#double": [{"@value": 0.0e0, "@type": "@json"}]
}]
6 changes: 6 additions & 0 deletions core/src/test/resources/json-ld.org/compact-js04-out.jsonld
@@ -0,0 +1,6 @@
{
"@context": {
"e": {"@id": "http://example.org/vocab#double", "@type": "@json"}
},
"e": 0.0e0
}
@@ -0,0 +1,5 @@
{
"@context": {
"e": {"@id": "http://example.org/vocab#integer", "@type": "@json"}
}
}
3 changes: 3 additions & 0 deletions core/src/test/resources/json-ld.org/compact-js05-in.jsonld
@@ -0,0 +1,3 @@
[{
"http://example.org/vocab#integer": [{"@value": 123, "@type": "@json"}]
}]
6 changes: 6 additions & 0 deletions core/src/test/resources/json-ld.org/compact-js05-out.jsonld
@@ -0,0 +1,6 @@
{
"@context": {
"e": {"@id": "http://example.org/vocab#integer", "@type": "@json"}
},
"e": 123
}
@@ -0,0 +1,5 @@
{
"@context": {
"e": {"@id": "http://example.org/vocab#object", "@type": "@json"}
}
}
3 changes: 3 additions & 0 deletions core/src/test/resources/json-ld.org/compact-js06-in.jsonld
@@ -0,0 +1,3 @@
[{
"http://example.org/vocab#object": [{"@value": {"foo": "bar"}, "@type": "@json"}]
}]
6 changes: 6 additions & 0 deletions core/src/test/resources/json-ld.org/compact-js06-out.jsonld
@@ -0,0 +1,6 @@
{
"@context": {
"e": {"@id": "http://example.org/vocab#object", "@type": "@json"}
},
"e": {"foo": "bar"}
}
@@ -0,0 +1,5 @@
{
"@context": {
"e": {"@id": "http://example.org/vocab#array", "@type": "@json", "@container": "@set"}
}
}
3 changes: 3 additions & 0 deletions core/src/test/resources/json-ld.org/compact-js07-in.jsonld
@@ -0,0 +1,3 @@
[{
"http://example.org/vocab#array": [{"@value": [{"foo": "bar"}], "@type": "@json"}]
}]
6 changes: 6 additions & 0 deletions core/src/test/resources/json-ld.org/compact-js07-out.jsonld
@@ -0,0 +1,6 @@
{
"@context": {
"e": {"@id": "http://example.org/vocab#array", "@type": "@json", "@container": "@set"}
},
"e": [{"foo": "bar"}]
}
@@ -0,0 +1,2 @@
{
}
3 changes: 3 additions & 0 deletions core/src/test/resources/json-ld.org/compact-js08-in.jsonld
@@ -0,0 +1,3 @@
[{
"http://example.org/vocab#object": [{"@value": {"foo": "bar"}, "@type": "@json"}]
}]
3 changes: 3 additions & 0 deletions core/src/test/resources/json-ld.org/compact-js08-out.jsonld
@@ -0,0 +1,3 @@
{
"http://example.org/vocab#object": {"@value": {"foo": "bar"}, "@type": "@json"}
}
@@ -0,0 +1,3 @@
{
"@context": {"value": "@value", "type": "@type", "json": "@json"}
}
3 changes: 3 additions & 0 deletions core/src/test/resources/json-ld.org/compact-js09-in.jsonld
@@ -0,0 +1,3 @@
[{
"http://example.org/vocab#object": [{"@value": {"foo": "bar"}, "@type": "@json"}]
}]
4 changes: 4 additions & 0 deletions core/src/test/resources/json-ld.org/compact-js09-out.jsonld
@@ -0,0 +1,4 @@
{
"@context": {"value": "@value", "type": "@type", "json": "@json"},
"http://example.org/vocab#object": {"value": {"foo": "bar"}, "type": "json"}
}
@@ -0,0 +1,5 @@
{
"@context": {
"e": {"@id": "http://example.org/vocab#string", "@type": "@json"}
}
}
6 changes: 6 additions & 0 deletions core/src/test/resources/json-ld.org/compact-js10-in.jsonld
@@ -0,0 +1,6 @@
[{
"http://example.org/vocab#string": [{
"@value": "string",
"@type": "@json"
}]
}]

0 comments on commit a312c2b

Please sign in to comment.