From d8a2ff76f6cfe0ffd4f920e064656bc740018e1f Mon Sep 17 00:00:00 2001 From: Miel Vander Sande Date: Thu, 7 May 2026 09:02:18 +0200 Subject: [PATCH 1/3] Expand index mapping before re-compacting it when not @index. --- lib/pyld/jsonld.py | 31 ++++++++++++++++++++++++------- tests/runtests.py | 1 - 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/lib/pyld/jsonld.py b/lib/pyld/jsonld.py index 672dce63..88080c7e 100644 --- a/lib/pyld/jsonld.py +++ b/lib/pyld/jsonld.py @@ -1893,10 +1893,10 @@ def _compact(self, active_ctx, active_property, element, options): ) if not index_key: index_key = '@index' - container_key = self._compact_iri( - active_ctx, index_key, vocab=True - ) if index_key == '@index': + container_key = self._compact_iri( + active_ctx, index_key, vocab=True + ) key = expanded_item.get('@index') if ( _is_object(compacted_item) @@ -1904,10 +1904,27 @@ def _compact(self, active_ctx, active_property, element, options): ): del compacted_item[container_key] else: + # Expand a term or compact IRI index mapping before re-compacting it. + expanded_index_key = self._expand_iri( + active_ctx, index_key, vocab=True + ) + # Term selection for the index property can depend on its value. + index_value = JsonLdProcessor.arrayify( + expanded_item.get(expanded_index_key, []) + ) + # Index maps use the first value as the map key. + index_value = index_value[0] if index_value else None + # Re-compact with the value so terms like predicate beat rdf:predicate. + container_key = self._compact_iri( + active_ctx, + expanded_index_key, + index_value, + vocab=True, + ) indexes = [] if _is_object(compacted_item): indexes = JsonLdProcessor.arrayify( - compacted_item.get(index_key, []) + compacted_item.get(container_key, []) ) if not indexes or not _is_string(indexes[0]): key = None @@ -1919,11 +1936,11 @@ def _compact(self, active_ctx, active_property, element, options): and len(indexes) == 0 and _is_object(compacted_item) ): - del compacted_item[index_key] + del compacted_item[container_key] elif len(indexes) == 1: - compacted_item[index_key] = indexes[0] + compacted_item[container_key] = indexes[0] else: - compacted_item[index_key] = indexes + compacted_item[container_key] = indexes elif '@id' in container: id_key = self._compact_iri( active_ctx, '@id', base=options.get('base', '') diff --git a/tests/runtests.py b/tests/runtests.py index 68354c7a..5d3f1e5e 100644 --- a/tests/runtests.py +++ b/tests/runtests.py @@ -906,7 +906,6 @@ def write(self, filename): 'specVersion': ['json-ld-1.0'], 'idRegex': [ # uncategorized - '.*compact-manifest#t0112$', '.*compact-manifest#tm023$', '.*compact-manifest#t0113$', '.*compact-manifest#tc028$', From 728c733b196cf4b487b1dec7b7202ced9c3648b2 Mon Sep 17 00:00:00 2001 From: Miel Vander Sande Date: Thu, 7 May 2026 09:03:39 +0200 Subject: [PATCH 2/3] Add regression tests for @index mapping. --- tests/test_jsonld.py | 90 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) diff --git a/tests/test_jsonld.py b/tests/test_jsonld.py index 8b2681dd..e5c3eee9 100644 --- a/tests/test_jsonld.py +++ b/tests/test_jsonld.py @@ -777,6 +777,96 @@ def test_compact_same_length_uses_lexicographic_tiebreak(self): assert "name" in result assert "nick" not in result + def test_index_map_with_compact_iri_index_round_trips(self): + """ + When an @index container uses a compact IRI as its @index mapping, + compaction should use the indexed property value as the map key and + preserve the expanded representation on round-trip. + """ + context = { + "@context": { + "ex": "http://example.com/", + "items": { + "@id": "ex:items", + "@container": "@index", + "@index": "ex:rank", + }, + } + } + expanded = [ + { + "http://example.com/items": [ + { + "http://example.com/rank": [{"@value": "first"}], + "http://example.com/name": [{"@value": "Alice"}], + } + ] + } + ] + + compacted = jsonld.compact(expanded, context, {"skipExpansion": True}) + + assert compacted == { + "@context": context["@context"], + "items": {"first": {"ex:name": "Alice"}}, + } + assert jsonld.expand(compacted) == expanded + + def test_reverse_index_map_with_term_index_uses_property_value_as_key(self): + """ + When an @index container uses a term as its @index mapping, compaction + should still find the property key selected using the indexed value. + """ + context = { + "@context": { + "@version": 1.1, + "@base": "https://example.org/", + "@vocab": "https://example.net/ns#", + "rdf": "http://www.w3.org/1999/02/22-rdf-syntax-ns#", + "statement": { + "@reverse": "rdf:subject", + "@container": "@index", + "@index": "predicate", + }, + "predicate": {"@id": "rdf:predicate", "@type": "@vocab"}, + "term": {"@id": "rdf:object", "@type": "@vocab"}, + "addedIn": {"@type": "@id"}, + } + } + expanded = [ + { + "@id": "https://example.org/item/1", + "@reverse": { + "http://www.w3.org/1999/02/22-rdf-syntax-ns#subject": [ + { + "https://example.net/ns#addedIn": [ + {"@id": "https://example.org/v1"} + ], + "http://www.w3.org/1999/02/22-rdf-syntax-ns#object": [ + {"@id": "https://example.net/ns#A"} + ], + "http://www.w3.org/1999/02/22-rdf-syntax-ns#predicate": [ + { + "@id": ( + "http://www.w3.org/1999/02/22-rdf-syntax-ns#" + "type" + ) + } + ], + } + ] + }, + } + ] + + compacted = jsonld.compact(expanded, context, {"skipExpansion": True}) + + assert compacted == { + "@context": context["@context"], + "@id": "item/1", + "statement": {"rdf:type": {"term": "A", "addedIn": "v1"}}, + } + # Issue 91 def test_empty_context(self): """ From 00af43f0e201e2f241691f622bc5a7575602d1bb Mon Sep 17 00:00:00 2001 From: Miel Vander Sande Date: Thu, 7 May 2026 09:33:34 +0200 Subject: [PATCH 3/3] Also re-enable t113 --- tests/runtests.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/runtests.py b/tests/runtests.py index 5d3f1e5e..43e56cdb 100644 --- a/tests/runtests.py +++ b/tests/runtests.py @@ -907,7 +907,6 @@ def write(self, filename): 'idRegex': [ # uncategorized '.*compact-manifest#tm023$', - '.*compact-manifest#t0113$', '.*compact-manifest#tc028$', ], },