···10541054 |> Jsont.Object.finish
10551055 end
10561056end
10571057+10581058+module Attrs = struct
10591059+ type t = {
10601060+ conventions : Conv.Meta.t list;
10611061+ proj : Conv.Proj.t option;
10621062+ spatial : Conv.Spatial.t option;
10631063+ multiscales : Conv.Multiscales.t option;
10641064+ unknown : Jsont.json;
10651065+ }
10661066+ let conventions t = t.conventions
10671067+ let proj t = t.proj
10681068+ let spatial t = t.spatial
10691069+ let multiscales t = t.multiscales
10701070+ let unknown t = t.unknown
10711071+ let empty = {
10721072+ conventions = []; proj = None; spatial = None; multiscales = None;
10731073+ unknown = Jsont.Json.object' [];
10741074+ }
10751075+end
10761076+10771077+let attrs_jsont : Attrs.t Jsont.t =
10781078+ Jsont.map ~kind:"Attrs"
10791079+ ~dec:(fun json ->
10801080+ let mems = match json with
10811081+ | Jsont.Object (mems, _) -> mems
10821082+ | _ -> []
10831083+ in
10841084+ let find_mem name =
10851085+ List.find_map (fun ((k, _), v) ->
10861086+ if k = name then Some v else None) mems
10871087+ in
10881088+ let is_known ((k, _), _) =
10891089+ k = "zarr_conventions" || k = "multiscales" ||
10901090+ (String.length k > 5 && String.sub k 0 5 = "proj:") ||
10911091+ (String.length k > 8 && String.sub k 0 8 = "spatial:")
10921092+ in
10931093+ (* zarr_conventions *)
10941094+ let convs = match find_mem "zarr_conventions" with
10951095+ | Some arr ->
10961096+ (match Jsont.Json.decode (Jsont.list Conv.Meta.jsont) arr with
10971097+ | Ok cs -> cs | Error _ -> [])
10981098+ | None -> []
10991099+ in
11001100+ (* proj: try decoding if any proj: key exists *)
11011101+ let has_proj = List.exists (fun ((k, _), _) ->
11021102+ String.length k > 5 && String.sub k 0 5 = "proj:") mems
11031103+ in
11041104+ let proj_val =
11051105+ if has_proj then
11061106+ match Jsont.Json.decode Conv.Proj.jsont json with
11071107+ | Ok p when Conv.Proj.code p <> None || Conv.Proj.wkt2 p <> None || Conv.Proj.projjson p <> None -> Some p
11081108+ | _ -> None
11091109+ else None
11101110+ in
11111111+ (* spatial: need spatial:dimensions to be present (it's required) *)
11121112+ let has_spatial = List.exists (fun ((k, _), _) -> k = "spatial:dimensions") mems in
11131113+ let spatial_val =
11141114+ if has_spatial then
11151115+ match Jsont.Json.decode Conv.Spatial.jsont json with
11161116+ | Ok s -> Some s | Error _ -> None
11171117+ else None
11181118+ in
11191119+ (* multiscales: it's a nested object at key "multiscales" *)
11201120+ let multiscales_val = match find_mem "multiscales" with
11211121+ | Some ms_json ->
11221122+ (match Jsont.Json.decode Conv.Multiscales.jsont ms_json with
11231123+ | Ok m -> Some m | Error _ -> None)
11241124+ | None -> None
11251125+ in
11261126+ (* unknown: everything that's not a known convention key *)
11271127+ let unknown_mems = List.filter (fun m -> not (is_known m)) mems in
11281128+ let unknown_val = Jsont.Json.object' unknown_mems in
11291129+ { Attrs.
11301130+ conventions = convs;
11311131+ proj = proj_val;
11321132+ spatial = spatial_val;
11331133+ multiscales = multiscales_val;
11341134+ unknown = unknown_val;
11351135+ })
11361136+ ~enc:(fun (t : Attrs.t) ->
11371137+ let mems = ref [] in
11381138+ let add m = mems := m :: !mems in
11391139+ (* Auto-populate zarr_conventions *)
11401140+ let conv_metas =
11411141+ (match t.proj with Some _ -> [Conv.Proj.meta] | None -> []) @
11421142+ (match t.spatial with Some _ -> [Conv.Spatial.meta] | None -> []) @
11431143+ (match t.multiscales with Some _ -> [Conv.Multiscales.meta] | None -> [])
11441144+ in
11451145+ if conv_metas <> [] then begin
11461146+ match Jsont.Json.encode (Jsont.list Conv.Meta.jsont) conv_metas with
11471147+ | Ok arr -> add (("zarr_conventions", Jsont.Meta.none), arr)
11481148+ | Error _ -> ()
11491149+ end;
11501150+ (* proj *)
11511151+ (match t.proj with
11521152+ | Some p ->
11531153+ (match Jsont.Json.encode Conv.Proj.jsont p with
11541154+ | Ok (Jsont.Object (proj_mems, _)) ->
11551155+ List.iter (fun ((k, _), _ as m) ->
11561156+ if String.length k > 5 && String.sub k 0 5 = "proj:" then add m
11571157+ ) proj_mems
11581158+ | _ -> ())
11591159+ | None -> ());
11601160+ (* spatial *)
11611161+ (match t.spatial with
11621162+ | Some s ->
11631163+ (match Jsont.Json.encode Conv.Spatial.jsont s with
11641164+ | Ok (Jsont.Object (sp_mems, _)) ->
11651165+ List.iter (fun ((k, _), _ as m) ->
11661166+ if String.length k > 8 && String.sub k 0 8 = "spatial:" then add m
11671167+ ) sp_mems
11681168+ | _ -> ())
11691169+ | None -> ());
11701170+ (* multiscales *)
11711171+ (match t.multiscales with
11721172+ | Some m ->
11731173+ (match Jsont.Json.encode Conv.Multiscales.jsont m with
11741174+ | Ok j -> add (("multiscales", Jsont.Meta.none), j)
11751175+ | _ -> ())
11761176+ | None -> ());
11771177+ (* unknown *)
11781178+ (match t.unknown with
11791179+ | Jsont.Object (unk_mems, _) -> List.iter add unk_mems
11801180+ | _ -> ());
11811181+ Jsont.Json.object' (List.rev !mems))
11821182+ Jsont.json
+36
src/zarr_jsont.mli
···337337 val jsont : t Jsont.t
338338 end
339339end
340340+341341+(** Composable attributes layer for Zarr nodes.
342342+343343+ Decodes convention-namespaced keys from a flat JSON object, plus a
344344+ [zarr_conventions] registration array. Convention keys are recognised by
345345+ prefix: [proj:*], [spatial:*], and the nested [multiscales] object.
346346+ All remaining keys are preserved in [unknown]. *)
347347+module Attrs : sig
348348+ type t
349349+350350+ val conventions : t -> Conv.Meta.t list
351351+ (** The [zarr_conventions] registration entries decoded from the object. *)
352352+353353+ val proj : t -> Conv.Proj.t option
354354+ (** Projection convention attributes, present if any [proj:*] keys exist. *)
355355+356356+ val spatial : t -> Conv.Spatial.t option
357357+ (** Spatial convention attributes, present if [spatial:dimensions] exists. *)
358358+359359+ val multiscales : t -> Conv.Multiscales.t option
360360+ (** Multiscales convention attributes, present if [multiscales] key exists. *)
361361+362362+ val unknown : t -> Jsont.json
363363+ (** Remaining keys not belonging to any known convention. *)
364364+365365+ val empty : t
366366+ (** An empty attributes value with no conventions and no unknown keys. *)
367367+end
368368+369369+val attrs_jsont : Attrs.t Jsont.t
370370+(** Codec for {!Attrs.t}.
371371+372372+ Decodes a flat JSON object by routing convention-prefixed keys to the
373373+ appropriate sub-codec. On encode, auto-populates [zarr_conventions] from
374374+ whichever conventions are [Some], then merges their flat members back into
375375+ the object alongside [multiscales] and unknown keys. *)
+37
test/test_zarr_jsont.ml
···313313 assert (Zarr_jsont.Conv.Multiscales.Layout_item.resampling_method item1 = Some "average");
314314 print_endline "test_conv_multiscales: ok"
315315316316+let test_attrs () =
317317+ let json = {|{
318318+ "zarr_conventions": [
319319+ {"uuid": "f17cb550-5864-4468-aeb7-f3180cfb622f", "name": "proj:", "description": "CRS info"}
320320+ ],
321321+ "proj:code": "EPSG:3857",
322322+ "spatial:dimensions": ["Y", "X"],
323323+ "spatial:transform": [10.0, 0.0, 0.0, 0.0, -10.0, 100.0],
324324+ "custom_key": "custom_value"
325325+ }|} in
326326+ let v = decode Zarr_jsont.attrs_jsont json in
327327+ (match Zarr_jsont.Attrs.proj v with
328328+ | Some p -> assert (Zarr_jsont.Conv.Proj.code p = Some "EPSG:3857")
329329+ | None -> assert false);
330330+ (match Zarr_jsont.Attrs.spatial v with
331331+ | Some s -> assert (Zarr_jsont.Conv.Spatial.dimensions s = ["Y"; "X"])
332332+ | None -> assert false);
333333+ assert (Zarr_jsont.Attrs.multiscales v = None);
334334+ assert (List.length (Zarr_jsont.Attrs.conventions v) >= 1);
335335+ (* roundtrip *)
336336+ let json' = encode Zarr_jsont.attrs_jsont v in
337337+ let v' = decode Zarr_jsont.attrs_jsont json' in
338338+ (match Zarr_jsont.Attrs.proj v' with
339339+ | Some p -> assert (Zarr_jsont.Conv.Proj.code p = Some "EPSG:3857")
340340+ | None -> assert false);
341341+ (match Zarr_jsont.Attrs.spatial v' with
342342+ | Some s -> assert (Zarr_jsont.Conv.Spatial.dimensions s = ["Y"; "X"])
343343+ | None -> assert false);
344344+ (* empty attrs *)
345345+ let e = Zarr_jsont.Attrs.empty in
346346+ assert (Zarr_jsont.Attrs.conventions e = []);
347347+ assert (Zarr_jsont.Attrs.proj e = None);
348348+ assert (Zarr_jsont.Attrs.spatial e = None);
349349+ assert (Zarr_jsont.Attrs.multiscales e = None);
350350+ print_endline "test_attrs: ok"
351351+316352let () = test_other_codec ()
317353let () = test_other_ext ()
318354let () = test_fill_value ()
···326362let () = test_conv_proj ()
327363let () = test_conv_spatial ()
328364let () = test_conv_multiscales ()
365365+let () = test_attrs ()