@@ -153,15 +153,56 @@ 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
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 } "
165
206
166
207
# @st.composite
167
208
# def advanced_indices(draw, *, shape):
@@ -187,27 +228,6 @@ async def test_roundtrip_array_metadata(
187
228
# assert_array_equal(nparray, zarray[:])
188
229
189
230
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
231
@given (npst .from_dtype (dtype = np .dtype ("float64" ), allow_nan = True , allow_infinity = True ))
212
232
def test_v2meta_nan_and_infinity (fill_value ):
213
233
metadata = ArrayV2Metadata (
0 commit comments