Skip to content

Commit bd78ef2

Browse files
gkelloggdavidlehn
authored andcommitted
Support nested property expansion and compaction.
1 parent 0a2b190 commit bd78ef2

File tree

5 files changed

+266
-152
lines changed

5 files changed

+266
-152
lines changed

CHANGELOG.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,7 @@
5353

5454
### Added
5555
- Expansion and Compaction using scoped contexts on property and `@type` terms.
56-
57-
### Added
56+
- Expansion and Compaction of nested properties.
5857
- Index graph containers using `@id` and `@index`, with `@set` variations.
5958
- Index node objects using `@id` and `@type`, with `@set` variations.
6059

lib/compact.js

Lines changed: 51 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -143,8 +143,7 @@ api.compact = ({
143143

144144
// process element keys in order
145145
const keys = Object.keys(element).sort();
146-
for(let ki = 0; ki < keys.length; ++ki) {
147-
const expandedProperty = keys[ki];
146+
for(let expandedProperty of keys) {
148147
const expandedValue = element[expandedProperty];
149148

150149
// compact @id and @type(s)
@@ -161,9 +160,9 @@ api.compact = ({
161160
} else {
162161
// expanded value must be a @type array
163162
compactedValue = [];
164-
for(let vi = 0; vi < expandedValue.length; ++vi) {
163+
for(let expandedIri of expandedValue) {
165164
const compactedType = api.compactIri(
166-
{activeCtx, iri: expandedValue[vi], relativeTo: {vocab: true}})
165+
{activeCtx, iri: expandedIri, relativeTo: {vocab: true}})
167166

168167
// Use any scoped context defined on this value
169168
const ctx = _getContextValue(activeCtx, compactedType, '@context');
@@ -257,14 +256,21 @@ api.compact = ({
257256
relativeTo: {vocab: true},
258257
reverse: insideReverse
259258
});
259+
const nestProperty = (itemActiveProperty in activeCtx.mappings) ? activeCtx.mappings[itemActiveProperty]['@nest'] : null;
260+
let nestResult = rval;
261+
if(nestProperty) {
262+
_checkNestProperty(activeCtx, nestProperty);
263+
if(!_isObject(rval[nestProperty])) {
264+
rval[nestProperty] = {};
265+
}
266+
nestResult = rval[nestProperty];
267+
}
260268
_addValue(
261-
rval, itemActiveProperty, expandedValue, {propertyIsArray: true});
269+
nestResult, itemActiveProperty, expandedValue, {propertyIsArray: true});
262270
}
263271

264272
// recusively process array values
265-
for(let vi = 0; vi < expandedValue.length; ++vi) {
266-
const expandedItem = expandedValue[vi];
267-
273+
for(let expandedItem of expandedValue) {
268274
// compact property and get container type
269275
const itemActiveProperty = api.compactIri({
270276
activeCtx,
@@ -273,6 +279,18 @@ api.compact = ({
273279
relativeTo: {vocab: true},
274280
reverse: insideReverse
275281
});
282+
283+
// if itemActiveProperty is a @nest property, add values to nestResult, otherwise rval
284+
const nestProperty = (itemActiveProperty in activeCtx.mappings) ? activeCtx.mappings[itemActiveProperty]['@nest'] : null;
285+
let nestResult = rval;
286+
if(nestProperty) {
287+
_checkNestProperty(activeCtx, nestProperty);
288+
if(!_isObject(rval[nestProperty])) {
289+
rval[nestProperty] = {};
290+
}
291+
nestResult = rval[nestProperty];
292+
}
293+
276294
const container = _getContextValue(
277295
activeCtx, itemActiveProperty, '@container') || [];
278296

@@ -313,7 +331,7 @@ api.compact = ({
313331
compactedItem[api.compactIri({activeCtx, iri: '@index', relativeTo: {vocab: true}})] =
314332
expandedItem['@index'];
315333
}
316-
} else if(itemActiveProperty in rval) {
334+
} else if(itemActiveProperty in nestResult) {
317335
// can't use @list container for more than 1 list
318336
throw new JsonLdError(
319337
'JSON-LD compact error; property has a "@list" @container ' +
@@ -330,10 +348,10 @@ api.compact = ({
330348
(container.includes('@id') || container.includes('@index') && _isSimpleGraph(expandedItem))) {
331349
// get or create the map object
332350
let mapObject;
333-
if(itemActiveProperty in rval) {
334-
mapObject = rval[itemActiveProperty];
351+
if(itemActiveProperty in nestResult) {
352+
mapObject = nestResult[itemActiveProperty];
335353
} else {
336-
rval[itemActiveProperty] = mapObject = {};
354+
nestResult[itemActiveProperty] = mapObject = {};
337355
}
338356

339357
// index on @id or @index or alias of @none
@@ -347,7 +365,7 @@ api.compact = ({
347365
// container includes @graph but not @id or @index and value is a simple graph object
348366
// add compact value
349367
_addValue(
350-
rval, itemActiveProperty, compactedItem,
368+
nestResult, itemActiveProperty, compactedItem,
351369
{propertyIsArray: (!options.compactArrays || container.includes('@set'))});
352370
} else {
353371
// wrap using @graph alias
@@ -367,18 +385,18 @@ api.compact = ({
367385
expandedItem['@index'];
368386
}
369387
_addValue(
370-
rval, itemActiveProperty, compactedItem,
388+
nestResult, itemActiveProperty, compactedItem,
371389
{propertyIsArray: (!options.compactArrays || container.includes('@set'))});
372390
}
373391
} else if(container.includes('@language') || container.includes('@index') ||
374392
container.includes('@id') || container.includes('@type')) {
375393
// handle language and index maps
376394
// get or create the map object
377395
let mapObject;
378-
if(itemActiveProperty in rval) {
379-
mapObject = rval[itemActiveProperty];
396+
if(itemActiveProperty in nestResult) {
397+
mapObject = nestResult[itemActiveProperty];
380398
} else {
381-
rval[itemActiveProperty] = mapObject = {};
399+
nestResult[itemActiveProperty] = mapObject = {};
382400
}
383401

384402
let key;
@@ -431,7 +449,7 @@ api.compact = ({
431449

432450
// add compact value
433451
_addValue(
434-
rval, itemActiveProperty, compactedItem,
452+
nestResult, itemActiveProperty, compactedItem,
435453
{propertyIsArray: isArray});
436454
}
437455
}
@@ -907,3 +925,18 @@ function _selectTerm(
907925

908926
return null;
909927
}
928+
929+
/**
930+
* The value of `@nest` in the term definition must either be `@nest`, or a term
931+
* which resolves to `@nest`.
932+
*
933+
* @param activeCtx the active context.
934+
* @param nestProperty a term in the active context or `@nest`.
935+
*/
936+
function _checkNestProperty(activeCtx, nestProperty) {
937+
if(_expandIri(activeCtx, nestProperty, {vocab: true}) !== '@nest') {
938+
throw new JsonLdError(
939+
'JSON-LD compact error; nested property must have an @nest value resolving to @nest.',
940+
'jsonld.SyntaxError', {code: 'invalid @nest value'});
941+
}
942+
}

lib/context.js

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -263,7 +263,7 @@ api.createTermDefinition = (activeCtx, localCtx, term, defined) => {
263263

264264
// JSON-LD 1.1 support
265265
if(api.processingMode(activeCtx, 1.1)) {
266-
validKeys.push('@context', '@prefix');
266+
validKeys.push('@context', '@nest', '@prefix');
267267
}
268268

269269
for(let kw in value) {
@@ -287,6 +287,12 @@ api.createTermDefinition = (activeCtx, localCtx, term, defined) => {
287287
'contain @id.', 'jsonld.SyntaxError',
288288
{code: 'invalid reverse property', context: localCtx});
289289
}
290+
if('@nest' in value) {
291+
throw new JsonLdError(
292+
'Invalid JSON-LD syntax; a @reverse term definition must not ' +
293+
'contain @nest.', 'jsonld.SyntaxError',
294+
{code: 'invalid reverse property', context: localCtx});
295+
}
290296
const reverse = value['@reverse'];
291297
if(!_isString(reverse)) {
292298
throw new JsonLdError(
@@ -502,6 +508,17 @@ api.createTermDefinition = (activeCtx, localCtx, term, defined) => {
502508
}
503509
}
504510

511+
if('@nest' in value) {
512+
let nest = value['@nest'];
513+
if(!_isString(nest) || (nest !== '@nest' && nest.indexOf('@') === 0)) {
514+
throw new JsonLdError(
515+
'Invalid JSON-LD syntax; @context @nest value must be ' +
516+
'a string which is not a keyword other than @nest.', 'jsonld.SyntaxError',
517+
{code: 'invalid @nest value', context: localCtx});
518+
}
519+
mapping['@nest'] = nest;
520+
}
521+
505522
// disallow aliasing @context and @preserve
506523
const id = mapping['@id'];
507524
if(id === '@context' || id === '@preserve') {
@@ -883,6 +900,7 @@ api.isKeyword = v => {
883900
case '@index':
884901
case '@language':
885902
case '@list':
903+
case '@nest':
886904
case '@none':
887905
case '@omitDefault':
888906
case '@prefix':

0 commit comments

Comments
 (0)