OCaml Zarr jsont codecs for v2/v3 and common conventions
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat: Attrs with convention composition and auto-registration

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

+199
+126
src/zarr_jsont.ml
··· 1054 1054 |> Jsont.Object.finish 1055 1055 end 1056 1056 end 1057 + 1058 + module Attrs = struct 1059 + type t = { 1060 + conventions : Conv.Meta.t list; 1061 + proj : Conv.Proj.t option; 1062 + spatial : Conv.Spatial.t option; 1063 + multiscales : Conv.Multiscales.t option; 1064 + unknown : Jsont.json; 1065 + } 1066 + let conventions t = t.conventions 1067 + let proj t = t.proj 1068 + let spatial t = t.spatial 1069 + let multiscales t = t.multiscales 1070 + let unknown t = t.unknown 1071 + let empty = { 1072 + conventions = []; proj = None; spatial = None; multiscales = None; 1073 + unknown = Jsont.Json.object' []; 1074 + } 1075 + end 1076 + 1077 + let attrs_jsont : Attrs.t Jsont.t = 1078 + Jsont.map ~kind:"Attrs" 1079 + ~dec:(fun json -> 1080 + let mems = match json with 1081 + | Jsont.Object (mems, _) -> mems 1082 + | _ -> [] 1083 + in 1084 + let find_mem name = 1085 + List.find_map (fun ((k, _), v) -> 1086 + if k = name then Some v else None) mems 1087 + in 1088 + let is_known ((k, _), _) = 1089 + k = "zarr_conventions" || k = "multiscales" || 1090 + (String.length k > 5 && String.sub k 0 5 = "proj:") || 1091 + (String.length k > 8 && String.sub k 0 8 = "spatial:") 1092 + in 1093 + (* zarr_conventions *) 1094 + let convs = match find_mem "zarr_conventions" with 1095 + | Some arr -> 1096 + (match Jsont.Json.decode (Jsont.list Conv.Meta.jsont) arr with 1097 + | Ok cs -> cs | Error _ -> []) 1098 + | None -> [] 1099 + in 1100 + (* proj: try decoding if any proj: key exists *) 1101 + let has_proj = List.exists (fun ((k, _), _) -> 1102 + String.length k > 5 && String.sub k 0 5 = "proj:") mems 1103 + in 1104 + let proj_val = 1105 + if has_proj then 1106 + match Jsont.Json.decode Conv.Proj.jsont json with 1107 + | Ok p when Conv.Proj.code p <> None || Conv.Proj.wkt2 p <> None || Conv.Proj.projjson p <> None -> Some p 1108 + | _ -> None 1109 + else None 1110 + in 1111 + (* spatial: need spatial:dimensions to be present (it's required) *) 1112 + let has_spatial = List.exists (fun ((k, _), _) -> k = "spatial:dimensions") mems in 1113 + let spatial_val = 1114 + if has_spatial then 1115 + match Jsont.Json.decode Conv.Spatial.jsont json with 1116 + | Ok s -> Some s | Error _ -> None 1117 + else None 1118 + in 1119 + (* multiscales: it's a nested object at key "multiscales" *) 1120 + let multiscales_val = match find_mem "multiscales" with 1121 + | Some ms_json -> 1122 + (match Jsont.Json.decode Conv.Multiscales.jsont ms_json with 1123 + | Ok m -> Some m | Error _ -> None) 1124 + | None -> None 1125 + in 1126 + (* unknown: everything that's not a known convention key *) 1127 + let unknown_mems = List.filter (fun m -> not (is_known m)) mems in 1128 + let unknown_val = Jsont.Json.object' unknown_mems in 1129 + { Attrs. 1130 + conventions = convs; 1131 + proj = proj_val; 1132 + spatial = spatial_val; 1133 + multiscales = multiscales_val; 1134 + unknown = unknown_val; 1135 + }) 1136 + ~enc:(fun (t : Attrs.t) -> 1137 + let mems = ref [] in 1138 + let add m = mems := m :: !mems in 1139 + (* Auto-populate zarr_conventions *) 1140 + let conv_metas = 1141 + (match t.proj with Some _ -> [Conv.Proj.meta] | None -> []) @ 1142 + (match t.spatial with Some _ -> [Conv.Spatial.meta] | None -> []) @ 1143 + (match t.multiscales with Some _ -> [Conv.Multiscales.meta] | None -> []) 1144 + in 1145 + if conv_metas <> [] then begin 1146 + match Jsont.Json.encode (Jsont.list Conv.Meta.jsont) conv_metas with 1147 + | Ok arr -> add (("zarr_conventions", Jsont.Meta.none), arr) 1148 + | Error _ -> () 1149 + end; 1150 + (* proj *) 1151 + (match t.proj with 1152 + | Some p -> 1153 + (match Jsont.Json.encode Conv.Proj.jsont p with 1154 + | Ok (Jsont.Object (proj_mems, _)) -> 1155 + List.iter (fun ((k, _), _ as m) -> 1156 + if String.length k > 5 && String.sub k 0 5 = "proj:" then add m 1157 + ) proj_mems 1158 + | _ -> ()) 1159 + | None -> ()); 1160 + (* spatial *) 1161 + (match t.spatial with 1162 + | Some s -> 1163 + (match Jsont.Json.encode Conv.Spatial.jsont s with 1164 + | Ok (Jsont.Object (sp_mems, _)) -> 1165 + List.iter (fun ((k, _), _ as m) -> 1166 + if String.length k > 8 && String.sub k 0 8 = "spatial:" then add m 1167 + ) sp_mems 1168 + | _ -> ()) 1169 + | None -> ()); 1170 + (* multiscales *) 1171 + (match t.multiscales with 1172 + | Some m -> 1173 + (match Jsont.Json.encode Conv.Multiscales.jsont m with 1174 + | Ok j -> add (("multiscales", Jsont.Meta.none), j) 1175 + | _ -> ()) 1176 + | None -> ()); 1177 + (* unknown *) 1178 + (match t.unknown with 1179 + | Jsont.Object (unk_mems, _) -> List.iter add unk_mems 1180 + | _ -> ()); 1181 + Jsont.Json.object' (List.rev !mems)) 1182 + Jsont.json
+36
src/zarr_jsont.mli
··· 337 337 val jsont : t Jsont.t 338 338 end 339 339 end 340 + 341 + (** Composable attributes layer for Zarr nodes. 342 + 343 + Decodes convention-namespaced keys from a flat JSON object, plus a 344 + [zarr_conventions] registration array. Convention keys are recognised by 345 + prefix: [proj:*], [spatial:*], and the nested [multiscales] object. 346 + All remaining keys are preserved in [unknown]. *) 347 + module Attrs : sig 348 + type t 349 + 350 + val conventions : t -> Conv.Meta.t list 351 + (** The [zarr_conventions] registration entries decoded from the object. *) 352 + 353 + val proj : t -> Conv.Proj.t option 354 + (** Projection convention attributes, present if any [proj:*] keys exist. *) 355 + 356 + val spatial : t -> Conv.Spatial.t option 357 + (** Spatial convention attributes, present if [spatial:dimensions] exists. *) 358 + 359 + val multiscales : t -> Conv.Multiscales.t option 360 + (** Multiscales convention attributes, present if [multiscales] key exists. *) 361 + 362 + val unknown : t -> Jsont.json 363 + (** Remaining keys not belonging to any known convention. *) 364 + 365 + val empty : t 366 + (** An empty attributes value with no conventions and no unknown keys. *) 367 + end 368 + 369 + val attrs_jsont : Attrs.t Jsont.t 370 + (** Codec for {!Attrs.t}. 371 + 372 + Decodes a flat JSON object by routing convention-prefixed keys to the 373 + appropriate sub-codec. On encode, auto-populates [zarr_conventions] from 374 + whichever conventions are [Some], then merges their flat members back into 375 + the object alongside [multiscales] and unknown keys. *)
+37
test/test_zarr_jsont.ml
··· 313 313 assert (Zarr_jsont.Conv.Multiscales.Layout_item.resampling_method item1 = Some "average"); 314 314 print_endline "test_conv_multiscales: ok" 315 315 316 + let test_attrs () = 317 + let json = {|{ 318 + "zarr_conventions": [ 319 + {"uuid": "f17cb550-5864-4468-aeb7-f3180cfb622f", "name": "proj:", "description": "CRS info"} 320 + ], 321 + "proj:code": "EPSG:3857", 322 + "spatial:dimensions": ["Y", "X"], 323 + "spatial:transform": [10.0, 0.0, 0.0, 0.0, -10.0, 100.0], 324 + "custom_key": "custom_value" 325 + }|} in 326 + let v = decode Zarr_jsont.attrs_jsont json in 327 + (match Zarr_jsont.Attrs.proj v with 328 + | Some p -> assert (Zarr_jsont.Conv.Proj.code p = Some "EPSG:3857") 329 + | None -> assert false); 330 + (match Zarr_jsont.Attrs.spatial v with 331 + | Some s -> assert (Zarr_jsont.Conv.Spatial.dimensions s = ["Y"; "X"]) 332 + | None -> assert false); 333 + assert (Zarr_jsont.Attrs.multiscales v = None); 334 + assert (List.length (Zarr_jsont.Attrs.conventions v) >= 1); 335 + (* roundtrip *) 336 + let json' = encode Zarr_jsont.attrs_jsont v in 337 + let v' = decode Zarr_jsont.attrs_jsont json' in 338 + (match Zarr_jsont.Attrs.proj v' with 339 + | Some p -> assert (Zarr_jsont.Conv.Proj.code p = Some "EPSG:3857") 340 + | None -> assert false); 341 + (match Zarr_jsont.Attrs.spatial v' with 342 + | Some s -> assert (Zarr_jsont.Conv.Spatial.dimensions s = ["Y"; "X"]) 343 + | None -> assert false); 344 + (* empty attrs *) 345 + let e = Zarr_jsont.Attrs.empty in 346 + assert (Zarr_jsont.Attrs.conventions e = []); 347 + assert (Zarr_jsont.Attrs.proj e = None); 348 + assert (Zarr_jsont.Attrs.spatial e = None); 349 + assert (Zarr_jsont.Attrs.multiscales e = None); 350 + print_endline "test_attrs: ok" 351 + 316 352 let () = test_other_codec () 317 353 let () = test_other_ext () 318 354 let () = test_fill_value () ··· 326 362 let () = test_conv_proj () 327 363 let () = test_conv_spatial () 328 364 let () = test_conv_multiscales () 365 + let () = test_attrs ()