@@ -153,15 +153,56 @@ def test_vindex(data: st.DataObject) -> None:
153153
154154
155155@given (store = stores , meta = array_metadata ()) # type: ignore[misc]
156- async def test_roundtrip_array_metadata (
156+ async def test_roundtrip_array_metadata_from_store (
157157 store : Store , meta : ArrayV2Metadata | ArrayV3Metadata
158158) -> None :
159+ """
160+ Verify that the I/O for metadata in a store are lossless.
161+
162+ This test serializes an ArrayV2Metadata or ArrayV3Metadata object to a dict
163+ of buffers via `to_buffer_dict`, writes each buffer to a store under keys
164+ prefixed with "0/", and then reads them back. The test asserts that each
165+ retrieved buffer exactly matches the original buffer.
166+ """
159167 asdict = meta .to_buffer_dict (prototype = default_buffer_prototype ())
160168 for key , expected in asdict .items ():
161169 await store .set (f"0/{ key } " , expected )
162170 actual = await store .get (f"0/{ key } " , prototype = default_buffer_prototype ())
163171 assert actual == expected
164172
173+ @given (data = st .data (), zarr_format = zarr_formats )
174+ def test_roundtrip_array_metadata_from_json (data : st .DataObject , zarr_format : int ) -> None :
175+ """
176+ Verify that JSON serialization and deserialization of metadata is lossless.
177+
178+ For Zarr v2:
179+ - The metadata is split into two JSON documents (one for array data and one
180+ for attributes). The test merges the attributes back before deserialization.
181+ For Zarr v3:
182+ - All metadata is stored in a single JSON document. No manual merger is necessary.
183+
184+ The test then converts both the original and round-tripped metadata objects
185+ into dictionaries using `dataclasses.asdict` and uses a deep equality check
186+ to verify that the roundtrip has preserved all fields (including special
187+ cases like NaN, Infinity, complex numbers, and datetime values).
188+ """
189+ metadata = data .draw (array_metadata (zarr_formats = st .just (zarr_format )))
190+ buffer_dict = metadata .to_buffer_dict (prototype = default_buffer_prototype ())
191+
192+ if zarr_format == 2 :
193+ zarray_dict = json .loads (buffer_dict [ZARRAY_JSON ].to_bytes ().decode ())
194+ zattrs_dict = json .loads (buffer_dict [ZATTRS_JSON ].to_bytes ().decode ())
195+ # zattrs and zarray are separate in v2, we have to add attributes back prior to `from_dict`
196+ zarray_dict ["attributes" ] = zattrs_dict
197+ metadata_roundtripped = ArrayV2Metadata .from_dict (zarray_dict )
198+ else :
199+ zarray_dict = json .loads (buffer_dict [ZARR_JSON ].to_bytes ().decode ())
200+ metadata_roundtripped = ArrayV3Metadata .from_dict (zarray_dict )
201+
202+ orig = dataclasses .asdict (metadata )
203+ rt = dataclasses .asdict (metadata_roundtripped )
204+
205+ assert deep_equal (orig , rt ), f"Roundtrip mismatch:\n Original: { orig } \n Roundtripped: { rt } "
165206
166207# @st.composite
167208# def advanced_indices(draw, *, shape):
@@ -187,27 +228,6 @@ async def test_roundtrip_array_metadata(
187228# assert_array_equal(nparray, zarray[:])
188229
189230
190- @given (data = st .data (), zarr_format = zarr_formats )
191- def test_meta_roundtrip (data : st .DataObject , zarr_format : int ) -> None :
192- metadata = data .draw (array_metadata (zarr_formats = st .just (zarr_format )))
193- buffer_dict = metadata .to_buffer_dict (prototype = default_buffer_prototype ())
194-
195- if zarr_format == 2 :
196- zarray_dict = json .loads (buffer_dict [ZARRAY_JSON ].to_bytes ().decode ())
197- zattrs_dict = json .loads (buffer_dict [ZATTRS_JSON ].to_bytes ().decode ())
198- # zattrs and zarray are separate in v2, we have to add attributes back prior to `from_dict`
199- zarray_dict ["attributes" ] = zattrs_dict
200- metadata_roundtripped = ArrayV2Metadata .from_dict (zarray_dict )
201- else :
202- zarray_dict = json .loads (buffer_dict [ZARR_JSON ].to_bytes ().decode ())
203- metadata_roundtripped = ArrayV3Metadata .from_dict (zarray_dict )
204-
205- orig = dataclasses .asdict (metadata )
206- rt = dataclasses .asdict (metadata_roundtripped )
207-
208- assert deep_equal (orig , rt ), f"Roundtrip mismatch:\n Original: { orig } \n Roundtripped: { rt } "
209-
210-
211231@given (npst .from_dtype (dtype = np .dtype ("float64" ), allow_nan = True , allow_infinity = True ))
212232def test_v2meta_nan_and_infinity (fill_value ):
213233 metadata = ArrayV2Metadata (
0 commit comments