@@ -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 """
0 commit comments