From 1959c023919066c49b320b9cc65bd0c3f2efba39 Mon Sep 17 00:00:00 2001 From: Davis Vann Bennett Date: Fri, 6 Nov 2026 14:13:48 +0100 Subject: [PATCH 1/2] docs(zarr-metadata): say that to_json returns arrays as tuples The guide said `to_json` returns plain containers, while another section said it returns tuples. Each model's `to_json` docstring now says that every JSON array is a tuple, as the package's JSON types spell one, so the result does not compare equal to `json.loads` output, and points to comparing models instead. Prompted by refkv's report. Assisted-by: ClaudeCode:claude-opus-5-5 Co-Authored-By: Claude Opus 5.5 --- packages/zarr-metadata/changes/+to-json-tuples.doc.md | 1 + packages/zarr-metadata/docs/index.md | 9 ++++++++- .../zarr-metadata/src/zarr_metadata/model/_array_v2.py | 2 +- .../zarr-metadata/src/zarr_metadata/model/_array_v3.py | 2 +- .../zarr-metadata/src/zarr_metadata/model/_group_v2.py | 4 ++-- .../zarr-metadata/src/zarr_metadata/model/_group_v3.py | 4 ++-- 6 files changed, 15 insertions(+), 7 deletions(-) create mode 100644 packages/zarr-metadata/changes/+to-json-tuples.doc.md diff --git a/packages/zarr-metadata/changes/+to-json-tuples.doc.md b/packages/zarr-metadata/changes/+to-json-tuples.doc.md new file mode 100644 index 0000000000..c430838f35 --- /dev/null +++ b/packages/zarr-metadata/changes/+to-json-tuples.doc.md @@ -0,0 +1 @@ +The `to_json` docstrings and the guide now say what `to_json` returns: a copy in which each JSON array is a tuple, as the package's JSON types spell an array, so it does not compare equal to what `json.loads` returns. They point to comparing models instead. The guide had said `to_json` returns plain containers. diff --git a/packages/zarr-metadata/docs/index.md b/packages/zarr-metadata/docs/index.md index b139dfd307..904b9529b6 100644 --- a/packages/zarr-metadata/docs/index.md +++ b/packages/zarr-metadata/docs/index.md @@ -305,7 +305,14 @@ Every typed attribute is a view of that read: each declaration as the scope read it (an `AcceptedDeclaration` or an `UnclaimedDeclaration`), and `shape`, `attributes` and the members that hold no declaration as the read refined them. These views are read-only at every level: a list in the document is shown as a tuple, and an object -as a read-only mapping. `to_json` returns plain containers. +as a read-only mapping. `to_json` returns a copy the caller may change: +each object a new `dict`, and each array a new tuple, since the package's +JSON types spell a JSON array as a tuple. `json.dumps` writes it, but it +does not compare equal to what `json.loads` returns for the same document, +where arrays are lists. To ask whether a model holds a document, compare +models, `ZarrV3ArrayMetadata.from_json(document) == model`, which also +treats a default the document leaves out as equal to the default written +out. To change a model, call `update`. It replaces members of the document with the given JSON members and reads the result in the model's own scope, so diff --git a/packages/zarr-metadata/src/zarr_metadata/model/_array_v2.py b/packages/zarr-metadata/src/zarr_metadata/model/_array_v2.py index ecaaaa621f..1c1a17c859 100644 --- a/packages/zarr-metadata/src/zarr_metadata/model/_array_v2.py +++ b/packages/zarr-metadata/src/zarr_metadata/model/_array_v2.py @@ -584,7 +584,7 @@ def reading(self) -> ZarrV2ArrayMetadataReading: return self._reading def to_json(self) -> ZarrV2ArrayMetadataJSON: - """The merged document as written, refined. The result shares no objects with the model. + """The merged document as written, refined. The result shares no objects with the model. Each JSON array is a tuple, as the package's JSON types spell an array, and each object a new `dict`: `json.dumps` writes the result, but it does not compare equal to what `json.loads` returns. To compare a model with a document, compare models: `from_json(document) == model`. `attributes` is included when set, even empty. This is not the on-disk `.zarray`, which excludes them: `to_key_value` splits the diff --git a/packages/zarr-metadata/src/zarr_metadata/model/_array_v3.py b/packages/zarr-metadata/src/zarr_metadata/model/_array_v3.py index 061819fb3d..a01cd13740 100644 --- a/packages/zarr-metadata/src/zarr_metadata/model/_array_v3.py +++ b/packages/zarr-metadata/src/zarr_metadata/model/_array_v3.py @@ -581,7 +581,7 @@ def reading(self) -> ZarrV3ArrayMetadataReading: return self._reading def to_json(self) -> ZarrV3ArrayMetadataJSON: - """The document as written, refined. The result shares no objects with the model.""" + """The document as written, refined. The result shares no objects with the model. Each JSON array is a tuple, as the package's JSON types spell an array, and each object a new `dict`: `json.dumps` writes the result, but it does not compare equal to what `json.loads` returns. To compare a model with a document, compare models: `from_json(document) == model`.""" return cast("ZarrV3ArrayMetadataJSON", copied(self._document)) def to_key_value( diff --git a/packages/zarr-metadata/src/zarr_metadata/model/_group_v2.py b/packages/zarr-metadata/src/zarr_metadata/model/_group_v2.py index f5c7e8b405..d6eeaff174 100644 --- a/packages/zarr-metadata/src/zarr_metadata/model/_group_v2.py +++ b/packages/zarr-metadata/src/zarr_metadata/model/_group_v2.py @@ -332,7 +332,7 @@ def attributes(self) -> Mapping[str, JSONValue]: return self._shown def to_json(self) -> ZarrV2GroupMetadataJSON: - """The merged document as written, refined. The result shares no objects with the model. + """The merged document as written, refined. The result shares no objects with the model. Each JSON array is a tuple, as the package's JSON types spell an array, and each object a new `dict`: `json.dumps` writes the result, but it does not compare equal to what `json.loads` returns. To compare a model with a document, compare models: `from_json(document) == model`. `attributes` is included when set, even if empty. This is not the on-disk `.zgroup`, which excludes them: `to_key_value` splits the @@ -546,7 +546,7 @@ def metadata(self) -> Mapping[str, ZarrV2NodeMetadata]: return MappingProxyType(self._metadata) def to_json(self) -> dict[str, JSONValue]: - """The `.zmetadata` document as written, refined. The result shares no objects with the model.""" + """The `.zmetadata` document as written, refined. The result shares no objects with the model. Each JSON array is a tuple, as the package's JSON types spell an array, and each object a new `dict`: `json.dumps` writes the result, but it does not compare equal to what `json.loads` returns. To compare a model with a document, compare models: `from_json(document) == model`.""" return cast("dict[str, JSONValue]", copied(self._document)) def to_key_value( diff --git a/packages/zarr-metadata/src/zarr_metadata/model/_group_v3.py b/packages/zarr-metadata/src/zarr_metadata/model/_group_v3.py index 42f3e61357..96fda6b199 100644 --- a/packages/zarr-metadata/src/zarr_metadata/model/_group_v3.py +++ b/packages/zarr-metadata/src/zarr_metadata/model/_group_v3.py @@ -258,7 +258,7 @@ def reading(self) -> ZarrV3GroupMetadataReading: return self._reading def to_json(self) -> ZarrV3GroupMetadataJSON: - """The document as written, refined. The result shares no objects with the model.""" + """The document as written, refined. The result shares no objects with the model. Each JSON array is a tuple, as the package's JSON types spell an array, and each object a new `dict`: `json.dumps` writes the result, but it does not compare equal to what `json.loads` returns. To compare a model with a document, compare models: `from_json(document) == model`.""" return cast("ZarrV3GroupMetadataJSON", copied(self._document)) def to_key_value( @@ -566,7 +566,7 @@ def metadata(self) -> Mapping[str, ZarrV3NodeMetadata]: return MappingProxyType(self._metadata) def to_json(self) -> ZarrV3ConsolidatedMetadataJSON: - """The member as written, refined. The result shares no objects with the model.""" + """The member as written, refined. The result shares no objects with the model. Each JSON array is a tuple, as the package's JSON types spell an array, and each object a new `dict`: `json.dumps` writes the result, but it does not compare equal to what `json.loads` returns. To compare a model with a document, compare models: `from_json(document) == model`.""" return cast("ZarrV3ConsolidatedMetadataJSON", copied(self._document)) def __repr__(self) -> str: From 920e7baf8d388b21adf4f99eb852d155c67e0d4e Mon Sep 17 00:00:00 2001 From: Davis Vann Bennett Date: Fri, 6 Nov 2026 14:15:48 +0100 Subject: [PATCH 2/2] docs(zarr-metadata): number the changelog fragment for #441 Assisted-by: ClaudeCode:claude-opus-5-5 Co-Authored-By: Claude Opus 5.5 --- .../zarr-metadata/changes/{+to-json-tuples.doc.md => 441.doc.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename packages/zarr-metadata/changes/{+to-json-tuples.doc.md => 441.doc.md} (100%) diff --git a/packages/zarr-metadata/changes/+to-json-tuples.doc.md b/packages/zarr-metadata/changes/441.doc.md similarity index 100% rename from packages/zarr-metadata/changes/+to-json-tuples.doc.md rename to packages/zarr-metadata/changes/441.doc.md