From 0499a61e0333aded8c3d4dcab80302ce5727dc4c Mon Sep 17 00:00:00 2001 From: "David I. Lehn" Date: Wed, 28 Aug 2019 17:01:33 -0400 Subject: [PATCH 1/5] Support literal JSON. --- CHANGELOG.md | 3 +++ lib/constants.js | 1 + lib/context.js | 2 +- lib/expand.js | 6 ++++++ lib/fromRdf.js | 5 +++++ lib/toRdf.js | 9 +++++++-- package.json | 1 + tests/test-common.js | 44 -------------------------------------------- 8 files changed, 24 insertions(+), 47 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cebdefb4..83a43f74 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ # jsonld ChangeLog +### Added +- Support literal JSON. + ## 1.7.0 - 2019-08-30 ### Added diff --git a/lib/constants.js b/lib/constants.js index 17511c75..d503e7c2 100644 --- a/lib/constants.js +++ b/lib/constants.js @@ -17,6 +17,7 @@ module.exports = { RDF_TYPE: RDF + 'type', RDF_PLAIN_LITERAL: RDF + 'PlainLiteral', RDF_XML_LITERAL: RDF + 'XMLLiteral', + RDF_JSON_LITERAL: RDF + 'JSON', RDF_OBJECT: RDF + 'object', RDF_LANGSTRING: RDF + 'langString', diff --git a/lib/context.js b/lib/context.js index 6143ed5a..781c8ec0 100644 --- a/lib/context.js +++ b/lib/context.js @@ -499,7 +499,7 @@ api.createTermDefinition = ( {code: 'invalid type mapping', context: localCtx}); } - if(type !== '@id' && type !== '@vocab') { + if(type !== '@id' && type !== '@vocab' && type !== '@json') { // expand @type to full IRI type = _expandIri( activeCtx, type, {vocab: true, base: false}, localCtx, defined, diff --git a/lib/expand.js b/lib/expand.js index 994debaf..95927aba 100644 --- a/lib/expand.js +++ b/lib/expand.js @@ -669,6 +669,12 @@ function _expandObject({ insideList: isList, expansionMap }); + } else if( + _getContextValue(activeCtx, key, '@type') === '@json') { + expandedValue = { + '@type': '@json', + '@value': value + }; } else { // recursively expand value with key as new active property expandedValue = api.expand({ diff --git a/lib/fromRdf.js b/lib/fromRdf.js index 5ce1ed2e..5a99aca5 100644 --- a/lib/fromRdf.js +++ b/lib/fromRdf.js @@ -17,6 +17,7 @@ const { RDF_TYPE, // RDF_PLAIN_LITERAL, // RDF_XML_LITERAL, + RDF_JSON_LITERAL, // RDF_OBJECT, // RDF_LANGSTRING, @@ -285,6 +286,10 @@ function _RDFToObject(o, useNativeTypes) { if(!type) { type = XSD_STRING; } + if(type === RDF_JSON_LITERAL) { + type = '@json'; + rval['@value'] = JSON.parse(rval['@value']); + } // use native types for certain xsd types if(useNativeTypes) { if(type === XSD_BOOLEAN) { diff --git a/lib/toRdf.js b/lib/toRdf.js index 634cc01d..e6a3281f 100644 --- a/lib/toRdf.js +++ b/lib/toRdf.js @@ -6,6 +6,7 @@ const {createNodeMap} = require('./nodeMap'); const {isKeyword} = require('./context'); const graphTypes = require('./graphTypes'); +const jsonCanonicalize = require('canonicalize'); const types = require('./types'); const util = require('./util'); @@ -18,6 +19,7 @@ const { RDF_TYPE, // RDF_PLAIN_LITERAL, // RDF_XML_LITERAL, + RDF_JSON_LITERAL, // RDF_OBJECT, RDF_LANGSTRING, @@ -224,8 +226,11 @@ function _objectToRDF(item, issuer, dataset, graphTerm) { let value = item['@value']; const datatype = item['@type'] || null; - // convert to XSD datatypes as appropriate - if(types.isBoolean(value)) { + // convert to XSD/JSON datatypes as appropriate + if(datatype === '@json') { + object.value = jsonCanonicalize(value); + object.datatype.value = RDF_JSON_LITERAL; + } else if(types.isBoolean(value)) { object.value = value.toString(); object.datatype.value = datatype || XSD_BOOLEAN; } else if(types.isDouble(value) || datatype === XSD_DOUBLE) { diff --git a/package.json b/package.json index c1659e17..c6199456 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,7 @@ "lib/**/*.js" ], "dependencies": { + "canonicalize": "^1.0.1", "rdf-canonize": "^1.0.2", "request": "^2.88.0", "semver": "^5.6.0", diff --git a/tests/test-common.js b/tests/test-common.js index fdec75f7..fd06d5a6 100644 --- a/tests/test-common.js +++ b/tests/test-common.js @@ -60,16 +60,6 @@ const TEST_TYPES = { /compact-manifest.jsonld#tpi04$/, /compact-manifest.jsonld#tpi05$/, /compact-manifest.jsonld#tpi06$/, - // JSON literals - /compact-manifest.jsonld#tjs01$/, - /compact-manifest.jsonld#tjs02$/, - /compact-manifest.jsonld#tjs03$/, - /compact-manifest.jsonld#tjs04$/, - /compact-manifest.jsonld#tjs05$/, - /compact-manifest.jsonld#tjs06$/, - /compact-manifest.jsonld#tjs07$/, - /compact-manifest.jsonld#tjs08$/, - /compact-manifest.jsonld#tjs09$/, // IRI confusion /compact-manifest.jsonld#te002$/, // @propogate @@ -171,18 +161,6 @@ const TEST_TYPES = { /expand-manifest.jsonld#tpi09$/, /expand-manifest.jsonld#tpi10$/, /expand-manifest.jsonld#tpi11$/, - // JSON literals - /expand-manifest.jsonld#tjs01$/, - /expand-manifest.jsonld#tjs02$/, - /expand-manifest.jsonld#tjs03$/, - /expand-manifest.jsonld#tjs04$/, - /expand-manifest.jsonld#tjs05$/, - /expand-manifest.jsonld#tjs06$/, - /expand-manifest.jsonld#tjs07$/, - /expand-manifest.jsonld#tjs08$/, - /expand-manifest.jsonld#tjs09$/, - /expand-manifest.jsonld#tjs10$/, - /expand-manifest.jsonld#tjs11$/, // misc /expand-manifest.jsonld#te043$/, /expand-manifest.jsonld#te044$/, @@ -336,14 +314,6 @@ const TEST_TYPES = { specVersion: ['json-ld-1.0'], // FIXME idRegex: [ - // JSON literals - /fromRdf-manifest.jsonld#tjs01$/, - /fromRdf-manifest.jsonld#tjs02$/, - /fromRdf-manifest.jsonld#tjs03$/, - /fromRdf-manifest.jsonld#tjs04$/, - /fromRdf-manifest.jsonld#tjs05$/, - /fromRdf-manifest.jsonld#tjs06$/, - /fromRdf-manifest.jsonld#tjs07$/, ] }, fn: 'fromRDF', @@ -406,20 +376,6 @@ const TEST_TYPES = { /html-manifest.jsonld#tr020$/, /html-manifest.jsonld#tr021$/, /html-manifest.jsonld#tr022$/, - // JSON literal - /toRdf-manifest.jsonld#tjs01$/, - /toRdf-manifest.jsonld#tjs02$/, - /toRdf-manifest.jsonld#tjs03$/, - /toRdf-manifest.jsonld#tjs04$/, - /toRdf-manifest.jsonld#tjs05$/, - /toRdf-manifest.jsonld#tjs06$/, - /toRdf-manifest.jsonld#tjs07$/, - /toRdf-manifest.jsonld#tjs08$/, - /toRdf-manifest.jsonld#tjs09$/, - /toRdf-manifest.jsonld#tjs10$/, - /toRdf-manifest.jsonld#tjs11$/, - /toRdf-manifest.jsonld#tjs12$/, - /toRdf-manifest.jsonld#tjs13$/, // number fixes /toRdf-manifest.jsonld#trt01$/, // IRI resolution From 927b4472195c2741e9c56696a62060e53d9568dc Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Tue, 3 Sep 2019 17:52:46 -0700 Subject: [PATCH 2/5] Expand JSON literals. --- lib/context.js | 1 + lib/expand.js | 28 ++++++++++++++++++---------- 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/lib/context.js b/lib/context.js index 781c8ec0..5f629050 100644 --- a/lib/context.js +++ b/lib/context.js @@ -1114,6 +1114,7 @@ api.isKeyword = v => { case '@graph': case '@id': case '@index': + case '@json': case '@language': case '@list': case '@nest': diff --git a/lib/expand.js b/lib/expand.js index 95927aba..08194022 100644 --- a/lib/expand.js +++ b/lib/expand.js @@ -23,7 +23,8 @@ const { expandIri: _expandIri, getContextValue: _getContextValue, isKeyword: _isKeyword, - process: _processContext + process: _processContext, + processingMode: _processingMode } = require('./context'); const { @@ -289,6 +290,8 @@ api.expand = ({ 'Invalid JSON-LD syntax; only strings may be language-tagged.', 'jsonld.SyntaxError', {code: 'invalid language-tagged value', element: rval}); + } else if(_processingMode(activeCtx, 1.1) && types.includes('@json') && types.length === 1) { + // Any value of @value is okay if @type: @json } else if(!types.every(t => (_isAbsoluteIri(t) && !(_isString(t) && t.indexOf('_:') === 0) || _isEmptyObject(t)))) { @@ -494,16 +497,7 @@ function _expandObject({ 'jsonld.SyntaxError', {code: 'invalid @graph value', value}); } - // @value must not be an object or an array (unless framing) if(expandedProperty === '@value') { - if((_isObject(value) || _isArray(value)) && !options.isFrame) { - throw new JsonLdError( - 'Invalid JSON-LD syntax; "@value" value must not be an ' + - 'object or an array.', - 'jsonld.SyntaxError', - {code: 'invalid value object value', value}); - } - _addValue( expandedParent, '@value', value, {propertyIsArray: options.isFrame}); continue; @@ -753,6 +747,20 @@ function _expandObject({ }); } + // @value must not be an object or an array (unless framing) or if @type is @json + if('@value' in element) { + const value = element['@value']; + if(element['@type'] === '@json' && _processingMode(activeCtx, 1.1)) { + // Sllow any value, to be verified when the object is fully expanded and the @type is @json. + } else if((_isObject(value) || _isArray(value)) && !options.isFrame) { + throw new JsonLdError( + 'Invalid JSON-LD syntax; "@value" value must not be an ' + + 'object or an array.', + 'jsonld.SyntaxError', + {code: 'invalid value object value', value}); + } + } + // expand each nested key for(const key of nests) { const nestedValues = _isArray(element[key]) ? element[key] : [element[key]]; From 8f07f018c373dd2b2c6a7e4eaacdfc02623290dc Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Wed, 4 Sep 2019 09:30:47 -0700 Subject: [PATCH 3/5] Update lib/expand.js Co-Authored-By: Dave Longley --- lib/expand.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/expand.js b/lib/expand.js index 08194022..56458e45 100644 --- a/lib/expand.js +++ b/lib/expand.js @@ -751,7 +751,7 @@ function _expandObject({ if('@value' in element) { const value = element['@value']; if(element['@type'] === '@json' && _processingMode(activeCtx, 1.1)) { - // Sllow any value, to be verified when the object is fully expanded and the @type is @json. + // allow any value, to be verified when the object is fully expanded and the @type is @json. } else if((_isObject(value) || _isArray(value)) && !options.isFrame) { throw new JsonLdError( 'Invalid JSON-LD syntax; "@value" value must not be an ' + From 81433c18586e864ee22cf6feb29083d387f501ad Mon Sep 17 00:00:00 2001 From: "David I. Lehn" Date: Fri, 6 Sep 2019 21:31:36 -0400 Subject: [PATCH 4/5] Wrap JSON literal parsing errors. --- lib/fromRdf.js | 10 +++++++++- tests/misc.js | 17 +++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/lib/fromRdf.js b/lib/fromRdf.js index 5a99aca5..02217763 100644 --- a/lib/fromRdf.js +++ b/lib/fromRdf.js @@ -3,6 +3,7 @@ */ 'use strict'; +const JsonLdError = require('./JsonLdError'); const graphTypes = require('./graphTypes'); const types = require('./types'); const util = require('./util'); @@ -288,7 +289,14 @@ function _RDFToObject(o, useNativeTypes) { } if(type === RDF_JSON_LITERAL) { type = '@json'; - rval['@value'] = JSON.parse(rval['@value']); + try { + rval['@value'] = JSON.parse(rval['@value']); + } catch(e) { + throw new JsonLdError( + 'JSON literal could not be parsed.', + 'jsonld.InvalidJsonLiteral', + {code: 'invalid JSON literal', value: rval['@value'], cause: e}); + } } // use native types for certain xsd types if(useNativeTypes) { diff --git a/tests/misc.js b/tests/misc.js index e8139f00..d65bf477 100644 --- a/tests/misc.js +++ b/tests/misc.js @@ -519,3 +519,20 @@ describe('js keywords', () => { assert.deepStrictEqual(e, ex); }); }); + +describe('literal JSON', () => { + it('handles error', done => { + const d = +'_:b0 "bogus"^^ .' +; + const p = jsonld.fromRDF(d); + assert(p instanceof Promise); + p.then(() => { + assert.fail(); + }).catch(e => { + assert(e); + assert.equal(e.name, 'jsonld.InvalidJsonLiteral'); + done(); + }); + }); +}); From e46e64a74f117aa2f8acc74ae61344aebd2d3d35 Mon Sep 17 00:00:00 2001 From: "David I. Lehn" Date: Fri, 6 Sep 2019 21:55:02 -0400 Subject: [PATCH 5/5] Update changelog. --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 83a43f74..3f9b554a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ ### Added - Support literal JSON. + - **NOTE**: The JSON serialization is based on the JSON Canonicalization + Scheme (JCS) drafts. Changes in the JCS algorithm could cause changes in + the `toRdf` output. ## 1.7.0 - 2019-08-30