Skip to content

Commit 9ae742f

Browse files
committed
Fix complex property nesting in scoped contexts by preparing local and type-scoped contexts first for a nested node object.
1 parent 7aa493a commit 9ae742f

2 files changed

Lines changed: 69 additions & 23 deletions

File tree

lib/pyld/jsonld.py

Lines changed: 61 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2770,31 +2770,86 @@ def _expand_object(
27702770
code='invalid value object value',
27712771
)
27722772

2773-
# expand each nested key and scoped context
2773+
# Nested values merge into the current node, but their term and type
2774+
# scoped contexts must still be prepared as if expanding a node object.
27742775
for key, term_ctx in nests:
27752776
for nv in JsonLdProcessor.arrayify(element[key]):
2776-
if not _is_object(nv) or [
2777+
if not _is_object(nv):
2778+
raise JsonLdError(
2779+
'Invalid JSON-LD syntax; nested value must be a node object.',
2780+
'jsonld.SyntaxError',
2781+
{'value': nv},
2782+
code='invalid @nest value',
2783+
)
2784+
2785+
nested_active_ctx, nested_type_key, nested_type_scoped_ctx = (
2786+
self._prepare_nested_context(term_ctx, nv, options)
2787+
)
2788+
2789+
if [
27772790
k
27782791
for k, v in nv.items()
2779-
if self._expand_iri(term_ctx, k, vocab=True) == '@value'
2792+
if self._expand_iri(nested_active_ctx, k, vocab=True) == '@value'
27802793
]:
27812794
raise JsonLdError(
27822795
'Invalid JSON-LD syntax; nested value must be a node object.',
27832796
'jsonld.SyntaxError',
27842797
{'value': nv},
27852798
code='invalid @nest value',
27862799
)
2800+
27872801
self._expand_object(
2788-
term_ctx,
2802+
nested_active_ctx,
27892803
active_property,
27902804
expanded_active_property,
27912805
nv,
27922806
expanded_parent,
27932807
options,
27942808
inside_list=inside_list,
2795-
type_key=type_key,
2796-
type_scoped_ctx=type_scoped_ctx,
2809+
type_key=nested_type_key,
2810+
type_scoped_ctx=nested_type_scoped_ctx,
2811+
)
2812+
2813+
def _prepare_nested_context(self, active_ctx, element, options):
2814+
"""
2815+
Prepare local and type-scoped contexts for a nested node object.
2816+
2817+
`@nest` is semantically transparent in JSON-LD 1.1, so nested properties
2818+
are merged into the parent node. Context processing is not transparent:
2819+
a scoped context on the nest term must be active while discovering any
2820+
@type entries and applying the type-scoped contexts they reference.
2821+
"""
2822+
# Local contexts on the nested node apply before looking for @type,
2823+
# just like they do for an ordinary node object.
2824+
if '@context' in element:
2825+
active_ctx = self._process_context(
2826+
active_ctx, element['@context'], options
2827+
)
2828+
2829+
# Type terms are looked up against the context that was active before
2830+
# applying any type-scoped contexts discovered below.
2831+
type_scoped_ctx = active_ctx
2832+
type_key = None
2833+
for key, value in sorted(element.items()):
2834+
# The @type entry itself may be aliased by the nest term's scoped
2835+
# context, so use the nested active context to identify it.
2836+
if self._expand_iri(active_ctx, key, vocab=True) != '@type':
2837+
continue
2838+
2839+
type_key = type_key or key
2840+
types = [t for t in JsonLdProcessor.arrayify(value) if _is_string(t)]
2841+
for type_ in sorted(types):
2842+
ctx = JsonLdProcessor.get_context_value(
2843+
type_scoped_ctx, type_, '@context'
27972844
)
2845+
if ctx is not None and ctx is not False:
2846+
# Type-scoped contexts affect expansion of this nested node,
2847+
# but must not leak into sibling properties or parent nodes.
2848+
active_ctx = self._process_context(
2849+
active_ctx, ctx, options, propagate=False
2850+
)
2851+
2852+
return active_ctx, type_key, type_scoped_ctx
27982853

27992854
def _flatten(self, input):
28002855
"""

tests/test_jsonld.py

Lines changed: 8 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -350,30 +350,16 @@ def test_scoped_context_on_nest_term_expands_nested_type_scoped_context(self):
350350
A scoped context on a @nest term should be in effect when expanding the
351351
nested node, including when processing any type-scoped contexts found on
352352
that node.
353-
354-
JSON-LD 1.1 defines property nesting as semantically transparent: nested
355-
properties are removed during expansion and treated as if their contents
356-
were declared directly on the containing node object. It also defines
357-
scoped contexts as property-scoped when the term is used as a property
358-
and type-scoped when the term is used as a type. This test combines both
359-
rules deliberately:
360-
361-
* p1 is an @nest term with a property-scoped context.
362-
* That context defines Type and gives Type its own type-scoped context.
363-
* The nested node uses Type and then uses p2 from Type's scoped context.
364-
365-
If nested values are expanded by directly walking their keys instead of
366-
running the normal expansion setup for the nested node, Type and p2 fall
367-
back to the outer @vocab. The expected result proves that the @nest term
368-
context is active before @type is expanded and before Type's scoped
369-
context is applied.
370353
"""
371354
input = {
372355
"@context": {
373356
"@vocab": "http://example.org/outer#",
357+
# p1 is an @nest term with a property-scoped context. That context defines
358+
# Type and gives Type its own type-scoped context.
374359
"p1": {
375360
"@id": "@nest",
376361
"@context": {
362+
# The nested node uses Type and then uses p2 from Type's scoped context.
377363
"Type": {
378364
"@id": "http://example.org/ns#Type",
379365
"@context": {
@@ -389,8 +375,13 @@ def test_scoped_context_on_nest_term_expands_nested_type_scoped_context(self):
389375
},
390376
}
391377

378+
# The @nest term context is active before @type is expanded and before Type's scoped
379+
# context is applied.
392380
expected = [
393381
{
382+
# If nested values are expanded by directly walking their keys instead of
383+
# running the normal expansion setup for the nested node, Type and p2 fall
384+
# back to the outer @vocab.
394385
"@type": ["http://example.org/ns#Type"],
395386
"http://example.org/ns#P2": [
396387
{

0 commit comments

Comments
 (0)