From b1984f68f842133d08a9a67e1e23f7598ba6e68e Mon Sep 17 00:00:00 2001 From: Davis Vann Bennett Date: Wed, 22 Apr 2026 21:11:34 +0200 Subject: [PATCH] feat: add JSONSerializable protocol with to_json on ArrayV3Metadata Introduces a generic `JSONSerializable[T_co]` protocol in `zarr.abc.serializable` parameterized on the JSON output type. Adds a `to_json` method to `ArrayV3Metadata` that returns an `ArrayMetadataJSON_V3` payload, demonstrating the protocol. Co-Authored-By: Claude Opus 4.7 (1M context) --- changes/3799.feature.md | 1 + src/zarr/abc/serializable.py | 9 +++++++++ src/zarr/core/metadata/v3.py | 6 ++++++ tests/test_abc/test_serializable.py | 30 +++++++++++++++++++++++++++++ 4 files changed, 46 insertions(+) create mode 100644 changes/3799.feature.md create mode 100644 src/zarr/abc/serializable.py create mode 100644 tests/test_abc/test_serializable.py diff --git a/changes/3799.feature.md b/changes/3799.feature.md new file mode 100644 index 0000000000..b693593194 --- /dev/null +++ b/changes/3799.feature.md @@ -0,0 +1 @@ +Added a `JSONSerializable` protocol in `zarr.abc.serializable`, parameterized on the type returned by `to_json`. `ArrayV3Metadata` now implements this protocol via a `to_json` method that returns an `ArrayMetadataJSON_V3` typed dictionary. diff --git a/src/zarr/abc/serializable.py b/src/zarr/abc/serializable.py new file mode 100644 index 0000000000..3c4d1b4b39 --- /dev/null +++ b/src/zarr/abc/serializable.py @@ -0,0 +1,9 @@ +from typing import Protocol + + +class JSONSerializable[T_co](Protocol): + def to_json(self) -> T_co: + """ + Serialize to a JSON-compatible Python object. + """ + ... diff --git a/src/zarr/core/metadata/v3.py b/src/zarr/core/metadata/v3.py index a8f2b05518..4e2f6fa71f 100644 --- a/src/zarr/core/metadata/v3.py +++ b/src/zarr/core/metadata/v3.py @@ -676,6 +676,12 @@ def to_dict(self) -> dict[str, JSON]: out_dict["data_type"] = dtype_meta.to_json(zarr_format=3) # type: ignore[unreachable] return out_dict + def to_json(self) -> ArrayMetadataJSON_V3: + """ + Serialize this array metadata to a JSON-compatible Python object. + """ + return cast(ArrayMetadataJSON_V3, self.to_dict()) + def update_shape(self, shape: tuple[int, ...]) -> Self: chunk_grid = self.chunk_grid if isinstance(chunk_grid, RectilinearChunkGridMetadata): diff --git a/tests/test_abc/test_serializable.py b/tests/test_abc/test_serializable.py new file mode 100644 index 0000000000..54ece4db61 --- /dev/null +++ b/tests/test_abc/test_serializable.py @@ -0,0 +1,30 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + +from zarr.core.dtype.npy.int import UInt8 +from zarr.core.metadata.v3 import ArrayV3Metadata + +if TYPE_CHECKING: + from zarr.abc.serializable import JSONSerializable + from zarr.core.metadata.v3 import ArrayMetadataJSON_V3 + + +def test_array_v3_metadata_to_json() -> None: + """ + ArrayV3Metadata satisfies the JSONSerializable protocol parameterized + on its JSON output type, and ``to_json`` returns the same payload as + ``to_dict``. + """ + metadata = ArrayV3Metadata( + shape=(10,), + data_type=UInt8(), + chunk_grid={"name": "regular", "configuration": {"chunk_shape": (10,)}}, + chunk_key_encoding={"name": "default", "configuration": {"separator": "/"}}, + fill_value=0, + codecs=({"name": "bytes", "configuration": {"endian": "little"}},), + attributes={}, + dimension_names=None, + ) + serializable: JSONSerializable[ArrayMetadataJSON_V3] = metadata + assert serializable.to_json() == metadata.to_dict()