Skip to content

Commit 89bcd99

Browse files
committed
Support containers with both @graph and @id or @index.
For json-ld/json-ld.org#549.
1 parent 2308148 commit 89bcd99

File tree

6 files changed

+472
-23
lines changed

6 files changed

+472
-23
lines changed

README.md

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,86 @@ The value of `@container` in a term definition can include `@id` or `@type`, in
298298
}
299299
}
300300

301+
### @graph containers and maps
302+
A term can have `@container` set to include `@graph` optionally including `@id` or `@index` and `@set`. In the first form, with `@container` set to `@graph`, the value of a property is treated as a _simple graph object_, meaning that values treated as if they were contained in an object with `@graph`, creating _named graph_ with an anonymous name.
303+
304+
{
305+
"@context": {
306+
"@vocab": "http://example.org/",
307+
"input": {"@container": "@graph"}
308+
},
309+
"input": {
310+
"value": "x"
311+
}
312+
}
313+
314+
which expands to the following:
315+
316+
[{
317+
"http://example.org/input": [{
318+
"@graph": [{
319+
"http://example.org/value": [{"@value": "x"}]
320+
}]
321+
}]
322+
}]
323+
324+
Compaction reverses this process, optionally ensuring that a single value is contained within an array of `@container` also includes `@set`:
325+
326+
{
327+
"@context": {
328+
"@vocab": "http://example.org/",
329+
"input": {"@container": ["@graph", "@set"]}
330+
}
331+
}
332+
333+
A graph map uses the map form already existing for `@index`, `@language`, `@type`, and `@id` where the index is either an index value or an id.
334+
335+
{
336+
"@context": {
337+
"@vocab": "http://example.org/",
338+
"input": {"@container": ["@graph", "@index"]}
339+
},
340+
"input": {
341+
"g1": {"value": "x"}
342+
}
343+
}
344+
345+
treats "g1" as an index, and expands to the following:
346+
347+
[{
348+
"http://example.org/input": [{
349+
"@index": "g1",
350+
"@graph": [{
351+
"http://example.org/value": [{"@value": "x"}]
352+
}]
353+
}]
354+
}])
355+
356+
This can also include `@set` to ensure that, when compacting, a single value of an index will be in array form.
357+
358+
The _id_ version is similar:
359+
360+
{
361+
"@context": {
362+
"@vocab": "http://example.org/",
363+
"input": {"@container": ["@graph", "@id"]}
364+
},
365+
"input": {
366+
"http://example.com/g1": {"value": "x"}
367+
}
368+
}
369+
370+
which expands to:
371+
372+
[{
373+
"http://example.org/input": [{
374+
"@id": "http://example.com/g1",
375+
"@graph": [{
376+
"http://example.org/value": [{"@value": "x"}]
377+
}]
378+
}]
379+
}])
380+
301381
### Transparent Nesting
302382
Many JSON APIs separate properties from their entities using an intermediate object. For example, a set of possible labels may be grouped under a common property:
303383

lib/json/ld/compact.rb

Lines changed: 41 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,7 @@ def compact(element, property: nil)
166166

167167
value = case
168168
when list?(expanded_item) then expanded_item['@list']
169-
when simple_graph?(expanded_item) then expanded_item['@graph']
169+
when graph?(expanded_item) then expanded_item['@graph']
170170
else expanded_item
171171
end
172172

@@ -190,14 +190,46 @@ def compact(element, property: nil)
190190
end
191191
end
192192

193-
# handle simple @graph, not the value of a property with @content: @graph
194-
if simple_graph?(expanded_item) && !container.include?('@graph')
195-
compacted_item = [compacted_item] unless compacted_item.is_a?(Array)
196-
al = context.compact_iri('@graph', vocab: true, quiet: true)
197-
compacted_item = {al => compacted_item}
198-
end
199-
200-
if !(container & %w(@language @index @id @type)).empty?
193+
# Graph object compaction cases:
194+
if graph?(expanded_item)
195+
if container.include?('@graph') && container.include?('@id')
196+
# container includes @graph and @id
197+
map_object = nest_result[item_active_property] ||= {}
198+
map_key = expanded_item['@id']
199+
# If there is no @id, create a blank node identifier to use as an index
200+
map_key = map_key ? context.compact_iri(map_key, quiet: true) : namer.get_name
201+
compacted_item = [compacted_item] if as_array && !compacted_item.is_a?(Array)
202+
merge_compacted_value(map_object, map_key, compacted_item)
203+
elsif container.include?('@graph') && container.include?('@index') && simple_graph?(expanded_item)
204+
# container includes @graph and @index and value is a simple graph object
205+
map_object = nest_result[item_active_property] ||= {}
206+
# If there is no @index, use @none
207+
map_key = expanded_item['@index'] || '@none'
208+
compacted_item = [compacted_item] if as_array && !compacted_item.is_a?(Array)
209+
merge_compacted_value(map_object, map_key, compacted_item)
210+
elsif container.include?('@graph') && simple_graph?(expanded_item)
211+
# container includes @graph but not @id or @index and value is a simple graph object
212+
# Drop through, where compacted_value will be added
213+
compacted_item = [compacted_item] if
214+
!compacted_item.is_a?(Array) && (!@options[:compactArrays] || as_array)
215+
merge_compacted_value(nest_result, item_active_property, compacted_item)
216+
else
217+
# container does not include @graph or otherwise does not match one of the previous cases, redo compacted_item
218+
compacted_item = [compacted_item]
219+
al = context.compact_iri('@graph', vocab: true, quiet: true)
220+
compacted_item = {al => compacted_item}
221+
if expanded_item['@id']
222+
al = context.compact_iri('@id', vocab: true, quiet: true)
223+
compacted_item[al] = context.compact_iri(expanded_item['@id'], vocab: false, quiet: true).to_s
224+
end
225+
if expanded_item.has_key?('@index')
226+
key = context.compact_iri('@index', vocab: true, quiet: true)
227+
compacted_item[key] = expanded_item['@index']
228+
end
229+
compacted_item = [compacted_item] if !@options[:compactArrays] || as_array
230+
merge_compacted_value(nest_result, item_active_property, compacted_item)
231+
end
232+
elsif !(container & %w(@language @index @id @type)).empty? && !container.include?('@graph')
201233
map_object = nest_result[item_active_property] ||= {}
202234
compacted_item = case container
203235
when %w(@id)

lib/json/ld/expand.rb

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -402,6 +402,16 @@ def expand_object(input, active_property, context, output_object, ordered: false
402402
# Expand k vocabulary relative
403403
expanded_k = active_context.expand_iri(k, vocab: true, documentRelative: true, quiet: true).to_s
404404
item['@type'] = [expanded_k].concat(Array(item['@type']))
405+
when %w(@graph @index), %w(@graph @id)
406+
# Indexed graph by graph name
407+
if !graph?(item)
408+
item = [item] unless expanded_value.is_a?(Array)
409+
item = {'@graph' => item}
410+
end
411+
expanded_k = container.include?('@index') ? k :
412+
active_context.expand_iri(k, documentRelative: true, quiet: true).to_s
413+
# Expand k document relative
414+
item[container.include?('@index') ? '@index' : '@id'] ||= k
405415
end
406416

407417
# Append item to expanded value.

lib/json/ld/utils.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ def blank_node?(value)
5252
# Note: A value is a simple graph if all of these hold true:
5353
# 1. It is an object.
5454
# 2. It has an `@graph` key.
55-
# 3. It may have '@id' or '@index'
55+
# 3. It may have '@context', '@id' or '@index'
5656
#
5757
# @param [Object] value
5858
# @return [Boolean]
@@ -205,7 +205,7 @@ def has_value(subject, property, value)
205205
end
206206

207207
private
208-
UTIL_GRAPH_KEYS = %w(@id @index).freeze
208+
UTIL_GRAPH_KEYS = %w(@context @id @index).freeze
209209

210210
# Merge the last value into an array based for the specified key if hash is not null and value is not already in that array
211211
def merge_value(hash, key, value)

0 commit comments

Comments
 (0)