@@ -153,16 +153,59 @@ def test_vindex(data: st.DataObject) -> None:
153
153
154
154
155
155
@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 (
157
157
store : Store , meta : ArrayV2Metadata | ArrayV3Metadata
158
158
) -> 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
+ """
159
167
asdict = meta .to_buffer_dict (prototype = default_buffer_prototype ())
160
168
for key , expected in asdict .items ():
161
169
await store .set (f"0/{ key } " , expected )
162
170
actual = await store .get (f"0/{ key } " , prototype = default_buffer_prototype ())
163
171
assert actual == expected
164
172
165
173
174
+ @given (data = st .data (), zarr_format = zarr_formats )
175
+ def test_roundtrip_array_metadata_from_json (data : st .DataObject , zarr_format : int ) -> None :
176
+ """
177
+ Verify that JSON serialization and deserialization of metadata is lossless.
178
+
179
+ For Zarr v2:
180
+ - The metadata is split into two JSON documents (one for array data and one
181
+ for attributes). The test merges the attributes back before deserialization.
182
+ For Zarr v3:
183
+ - All metadata is stored in a single JSON document. No manual merger is necessary.
184
+
185
+ The test then converts both the original and round-tripped metadata objects
186
+ into dictionaries using `dataclasses.asdict` and uses a deep equality check
187
+ to verify that the roundtrip has preserved all fields (including special
188
+ cases like NaN, Infinity, complex numbers, and datetime values).
189
+ """
190
+ metadata = data .draw (array_metadata (zarr_formats = st .just (zarr_format )))
191
+ buffer_dict = metadata .to_buffer_dict (prototype = default_buffer_prototype ())
192
+
193
+ if zarr_format == 2 :
194
+ zarray_dict = json .loads (buffer_dict [ZARRAY_JSON ].to_bytes ().decode ())
195
+ zattrs_dict = json .loads (buffer_dict [ZATTRS_JSON ].to_bytes ().decode ())
196
+ # zattrs and zarray are separate in v2, we have to add attributes back prior to `from_dict`
197
+ zarray_dict ["attributes" ] = zattrs_dict
198
+ metadata_roundtripped = ArrayV2Metadata .from_dict (zarray_dict )
199
+ else :
200
+ zarray_dict = json .loads (buffer_dict [ZARR_JSON ].to_bytes ().decode ())
201
+ metadata_roundtripped = ArrayV3Metadata .from_dict (zarray_dict )
202
+
203
+ orig = dataclasses .asdict (metadata )
204
+ rt = dataclasses .asdict (metadata_roundtripped )
205
+
206
+ assert deep_equal (orig , rt ), f"Roundtrip mismatch:\n Original: { orig } \n Roundtripped: { rt } "
207
+
208
+
166
209
# @st.composite
167
210
# def advanced_indices(draw, *, shape):
168
211
# basic_idxr = draw(
@@ -187,27 +230,6 @@ async def test_roundtrip_array_metadata(
187
230
# assert_array_equal(nparray, zarray[:])
188
231
189
232
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
-
211
233
@given (npst .from_dtype (dtype = np .dtype ("float64" ), allow_nan = True , allow_infinity = True ))
212
234
def test_v2meta_nan_and_infinity (fill_value ):
213
235
metadata = ArrayV2Metadata (
0 commit comments