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()