10#include <initializer_list>
16#include "libzarr/detail/common.hpp"
29inline constexpr const char*
kMetaKey =
"zarr.json";
32[[nodiscard]]
inline std::string meta_key(
const std::string& path) {
41inline void check_members(
const json& j, std::initializer_list<std::string_view> known,
42 const std::string& ctx,
bool lenient) {
46 for (
const auto& item : j.items()) {
47 bool recognized =
false;
48 for (
const std::string_view name : known) {
49 recognized = recognized || item.key() == name;
54 if (item.value().is_object() && !item.value().value(
"must_understand",
true)) {
57 throw error(ctx +
": unknown metadata member '" + item.key() +
58 "' (v3 core requires rejecting unrecognized members; open in lenient mode to "
63inline const json& require(
const json& j,
const char* name,
const std::string& ctx) {
64 const auto it = j.find(name);
66 throw error(ctx +
": missing required member '" + name +
"'");
72inline std::uint32_t hex_digit(
char c) {
73 if (c >=
'0' && c <=
'9') {
74 return static_cast<std::uint32_t
>(c -
'0');
76 if (c >=
'a' && c <=
'f') {
77 return static_cast<std::uint32_t
>(c -
'a') + 10U;
79 if (c >=
'A' && c <=
'F') {
80 return static_cast<std::uint32_t
>(c -
'A') + 10U;
87inline std::optional<Bytes> parse_bit_string(
const std::string& s, std::uint32_t itemsize,
88 const std::string& ctx) {
89 const bool hex = detail::starts_with(s,
"0x");
90 const bool bin = detail::starts_with(s,
"0b");
94 const std::string_view digits = std::string_view(s).substr(2);
95 const std::size_t per_byte = hex ? 2 : 8;
96 const std::uint32_t base = hex ? 16U : 2U;
97 if (digits.size() != itemsize * per_byte) {
98 throw error(ctx +
": bit-pattern fill_value '" + s +
"' must have " +
99 std::to_string(itemsize * per_byte) + (hex ?
" hex" :
" binary") +
" digits");
102 for (std::uint32_t b = 0; b < itemsize; ++b) {
103 std::uint32_t value = 0;
104 for (std::size_t d = 0; d < per_byte; ++d) {
105 const std::uint32_t digit = hex_digit(digits[b * per_byte + d]);
107 std::string msg = ctx;
108 msg +=
": invalid digit in fill_value '";
113 value = value * base + digit;
115 out[b] =
static_cast<std::uint8_t
>(value);
122inline Bytes float_fill(
const json& v, DType kind, std::uint32_t itemsize,
const std::string& ctx) {
124 return detail::fill_from_double(v.get<
double>(), DataType{kind, itemsize}, ctx);
127 const auto s = v.get<std::string>();
129 return detail::quiet_nan_bytes(kind);
131 if (s ==
"Infinity") {
132 return detail::infinity_bytes(kind,
false);
134 if (s ==
"-Infinity") {
135 return detail::infinity_bytes(kind,
true);
137 if (
auto bits = parse_bit_string(s, itemsize, ctx)) {
140 if (detail::host_is_little_endian()) {
141 std::reverse(bits->begin(), bits->end());
143 return *std::move(bits);
145 throw error(ctx +
": cannot interpret float fill_value '" + s +
"'");
147 throw error(ctx +
": cannot interpret float fill_value " + v.dump());
153[[nodiscard]]
inline DataType parse_data_type(
const json& v,
const std::string& ctx) {
154 if (!v.is_string()) {
155 throw error(ctx +
": extension data_type objects are not supported");
157 const auto s = v.get<std::string>();
159 std::string_view name;
162 static constexpr std::array<NamedType, 14> kNames{{{
"bool", DType::boolean},
163 {
"int8", DType::int8},
164 {
"int16", DType::int16},
165 {
"int32", DType::int32},
166 {
"int64", DType::int64},
167 {
"uint8", DType::uint8},
168 {
"uint16", DType::uint16},
169 {
"uint32", DType::uint32},
170 {
"uint64", DType::uint64},
171 {
"float16", DType::float16},
172 {
"float32", DType::float32},
173 {
"float64", DType::float64},
174 {
"complex64", DType::complex64},
175 {
"complex128", DType::complex128}}};
176 for (
const NamedType& named : kNames) {
177 if (s == named.name) {
181 if (s.size() > 1 && s[0] ==
'r') {
182 std::uint64_t bits = 0;
183 for (std::size_t i = 1; i < s.size(); ++i) {
184 if (s[i] <
'0' || s[i] >
'9' || bits > 0xFFFFFF) {
188 bits = bits * 10 +
static_cast<std::uint64_t
>(s[i] -
'0');
190 if (bits == 0 || bits % 8 != 0) {
191 throw error(ctx +
": raw data_type '" + s +
"' must be r<bits> with bits a multiple of 8");
195 throw error(ctx +
": unknown data_type '" + s +
"'");
204 if (
auto bits = parse_bit_string(v.get<std::string>(), dt.
itemsize, ctx)) {
205 return *std::move(bits);
207 throw error(ctx +
": raw fill_value string must be a 0x/0b bit pattern");
209 if (v.is_array() && v.size() == dt.
itemsize) {
211 for (std::uint32_t i = 0; i < dt.
itemsize; ++i) {
212 const std::uint64_t
byte = detail::json_to_uint64(v[i], ctx +
": fill_value");
214 throw error(ctx +
": raw fill_value bytes must be 0..255");
216 out[i] =
static_cast<std::uint8_t
>(byte);
220 throw error(ctx +
": raw fill_value must be a bit-pattern string or an array of " +
221 std::to_string(dt.
itemsize) +
" byte values");
227[[nodiscard]]
inline std::optional<Bytes> parse_fill(
const json& v,
DataType dt,
228 const std::string& ctx,
bool lenient) {
234 throw error(ctx +
": v3 fill_value must not be null (open in lenient mode to read as zeros)");
238 if (!v.is_boolean()) {
239 throw error(ctx +
": bool fill_value must be true or false");
241 return detail::scalar_bytes<std::uint8_t>(v.get<
bool>() ? 1 : 0);
245 return detail_v3::float_fill(v, dt.
kind, dt.
itemsize, ctx);
246 case DType::complex64:
247 case DType::complex128: {
249 if (!v.is_array() || v.size() != 2) {
250 throw error(ctx +
": complex fill_value must be a [re, im] array");
252 const DType component = dt.
kind == DType::complex64 ? DType::float32 : DType::float64;
253 const std::uint32_t half = dt.
itemsize / 2;
254 Bytes out = detail_v3::float_fill(v[0], component, half, ctx);
255 const Bytes imag = detail_v3::float_fill(v[1], component, half, ctx);
256 out.insert(out.end(), imag.begin(), imag.end());
260 return detail_v3::raw_fill(v, dt, ctx);
262 if (v.is_number_unsigned()) {
263 return detail::fill_from_uint(v.get<std::uint64_t>(), dt, ctx);
265 if (v.is_number_integer()) {
266 return detail::fill_from_int(v.get<std::int64_t>(), dt, ctx);
268 if (v.is_number_float()) {
269 return detail::fill_from_double(v.get<
double>(), dt, ctx);
271 throw error(ctx +
": integer fill_value expected, got " + v.dump());
279inline CodecSpec parse_codec_entry(
const json& c, std::size_t rank,
const std::string& ctx) {
283 spec.
name = c.get<std::string>();
284 }
else if (c.is_object() && c.contains(
"name") && c[
"name"].is_string()) {
285 spec.
name = c[
"name"].get<std::string>();
286 spec.
configuration = c.value(
"configuration", json::object());
288 throw error(ctx +
": codec '" + spec.
name +
"' configuration must be an object");
291 throw error(ctx +
": each codec must be an object with a 'name'");
293 if (spec.
name ==
"endian") {
301 const auto order = spec.
configuration[
"order"].get<std::string>();
302 if (order !=
"C" && order !=
"F") {
303 std::string msg = ctx;
304 msg +=
": transpose order '";
306 msg += R
"(' is not "C", "F" or an array)";
309 json perm = json::array();
310 for (std::size_t d = 0; d < rank; ++d) {
311 perm.push_back(order ==
"F" ? rank - 1 - d : d);
319inline std::vector<CodecSpec> parse_codecs(
const json& v, std::size_t rank,
320 const std::string& ctx) {
322 throw error(ctx +
": 'codecs' must be an array");
324 std::vector<CodecSpec> out;
325 out.reserve(v.size());
326 for (
const json& c : v) {
327 out.push_back(parse_codec_entry(c, rank, ctx));
333inline void parse_chunk_key_encoding(
const json& cke, ArrayMeta& meta,
const std::string& ctx) {
334 if (!cke.is_object() || !cke.contains(
"name")) {
335 throw error(ctx +
": chunk_key_encoding must be an object with a 'name'");
337 if (cke[
"name"] ==
"default") {
338 meta.key_encoding = ChunkKeyKind::v3_default;
339 meta.dimension_separator =
'/';
340 }
else if (cke[
"name"] ==
"v2") {
341 meta.key_encoding = ChunkKeyKind::v2;
342 meta.dimension_separator =
'.';
344 throw error(ctx +
": unknown chunk_key_encoding '" + cke[
"name"].dump() +
"'");
346 const json config = cke.value(
"configuration", json::object());
347 const json separator = config.value(
"separator",
json());
348 if (!separator.is_null()) {
349 if (!separator.is_string() || (separator !=
"/" && separator !=
".")) {
350 throw error(ctx + R
"(: chunk_key_encoding separator must be "/" or ".")");
352 meta.dimension_separator = separator.get<std::string>()[0];
357inline void parse_dimension_names(
const json& j, ArrayMeta& meta,
const std::string& ctx) {
358 const auto it = j.find(
"dimension_names");
362 if (!it->is_array() || it->size() != meta.shape.size()) {
363 throw error(ctx +
": 'dimension_names' must be an array of rank length");
365 for (
const json& name : *it) {
366 if (!name.is_string() && !name.is_null()) {
367 throw error(ctx +
": dimension names must be strings or null");
370 meta.dimension_names = *it;
376inline void lower_sharding(ArrayMeta& meta,
const std::string& ctx) {
377 while (!meta.codecs.empty() && meta.codecs.front().name ==
"sharding_indexed") {
378 if (meta.codecs.size() != 1) {
383 ": sharding_indexed cannot be combined with other codecs at the same "
386 const json config = meta.codecs.front().configuration.is_object()
387 ? meta.codecs.front().configuration
389 const std::vector<std::uint64_t> inner_shape = detail::parse_extents(
390 require(config,
"chunk_shape", ctx +
": sharding_indexed"),
"chunk_shape", ctx);
391 if (inner_shape.size() != meta.chunk_shape.size()) {
392 throw error(ctx +
": sharding_indexed chunk_shape rank mismatch");
394 for (std::size_t d = 0; d < inner_shape.size(); ++d) {
396 if (inner_shape[d] == 0 || meta.chunk_shape[d] % inner_shape[d] != 0) {
397 throw error(ctx +
": sharding_indexed chunk_shape must evenly divide the shard shape");
402 level.shard_shape = meta.chunk_shape;
404 parse_codecs(require(config,
"index_codecs", ctx +
": sharding_indexed"), 1, ctx);
405 const json location = config.value(
"index_location",
json(
"end"));
406 if (location !=
"end" && location !=
"start") {
407 throw error(ctx + R
"(: index_location must be "end" or "start")");
409 level.index_at_end = location == "end";
411 meta.shard_levels.push_back(std::move(level));
412 meta.chunk_shape = inner_shape;
414 parse_codecs(require(config,
"codecs", ctx +
": sharding_indexed"), meta.shape.size(), ctx);
416 for (
const CodecSpec& codec : meta.codecs) {
417 if (codec.name ==
"sharding_indexed") {
418 throw error(ctx +
": sharding_indexed must be the sole codec of its level");
427inline ArrayMeta parse_array_meta_impl(
const json& j,
const std::string& ctx,
bool lenient) {
428 if (!j.is_object()) {
429 throw error(ctx +
": expected a JSON object");
431 if (detail::json_to_uint64(detail_v3::require(j,
"zarr_format", ctx), ctx +
": zarr_format") !=
433 throw error(ctx +
": zarr_format must be 3");
435 if (detail_v3::require(j,
"node_type", ctx) !=
"array") {
436 throw error(ctx +
": node_type must be 'array'");
438 detail_v3::check_members(
440 {
"zarr_format",
"node_type",
"shape",
"data_type",
"chunk_grid",
"chunk_key_encoding",
441 "fill_value",
"codecs",
"attributes",
"dimension_names",
"storage_transformers"},
445 meta.format = ZarrFormat::v3;
446 meta.shape = detail::parse_extents(detail_v3::require(j,
"shape", ctx),
"shape", ctx);
447 meta.dtype =
parse_data_type(detail_v3::require(j,
"data_type", ctx), ctx);
449 const json& grid = detail_v3::require(j,
"chunk_grid", ctx);
450 if (!grid.is_object() || grid.value(
"name",
"") != std::string(
"regular")) {
451 throw error(ctx +
": only the 'regular' chunk_grid is supported");
455 const std::string grid_ctx = ctx +
": chunk_grid";
456 const json& grid_config = detail_v3::require(grid,
"configuration", grid_ctx);
457 meta.chunk_shape = detail::parse_extents(detail_v3::require(grid_config,
"chunk_shape", grid_ctx),
459 if (meta.chunk_shape.size() != meta.shape.size()) {
460 throw error(ctx +
": chunk_shape rank " + std::to_string(meta.chunk_shape.size()) +
461 " != shape rank " + std::to_string(meta.shape.size()));
463 for (
const std::uint64_t c : meta.chunk_shape) {
465 throw error(ctx +
": chunk extents must be positive");
469 detail_v3::parse_chunk_key_encoding(detail_v3::require(j,
"chunk_key_encoding", ctx), meta, ctx);
471 meta.fill =
parse_fill(detail_v3::require(j,
"fill_value", ctx), meta.dtype, ctx +
": fill_value",
474 detail_v3::parse_codecs(detail_v3::require(j,
"codecs", ctx), meta.shape.size(), ctx);
475 detail_v3::lower_sharding(meta, ctx);
477 meta.attributes = j.value(
"attributes", json::object());
478 if (!meta.attributes.is_object()) {
479 throw error(ctx +
": 'attributes' must be an object");
481 detail_v3::parse_dimension_names(j, meta, ctx);
483 const auto st_it = j.find(
"storage_transformers");
484 if (st_it != j.end() && !(st_it->is_array() && st_it->empty())) {
485 throw error(ctx +
": storage_transformers are not supported");
493[[nodiscard]]
inline ArrayMeta parse_array_meta(
const json& j,
const std::string& ctx,
494 bool lenient =
false) {
495 return detail::guard_json(ctx, [&] {
return detail_v3::parse_array_meta_impl(j, ctx, lenient); });
510 bool lenient =
false) {
511 return detail::guard_json(ctx, [&]() ->
GroupMeta {
512 if (!j.is_object()) {
513 throw error(ctx +
": expected a JSON object");
515 if (detail::json_to_uint64(detail_v3::require(j,
"zarr_format", ctx), ctx +
": zarr_format") !=
517 throw error(ctx +
": zarr_format must be 3");
519 if (detail_v3::require(j,
"node_type", ctx) !=
"group") {
520 throw error(ctx +
": node_type must be 'group'");
522 detail_v3::check_members(j, {
"zarr_format",
"node_type",
"attributes",
"consolidated_metadata"},
526 meta.
attributes = j.value(
"attributes", json::object());
527 const auto cons_it = j.find(
"consolidated_metadata");
528 if (cons_it != j.end() && cons_it->is_object() && cons_it->contains(
"metadata") &&
529 (*cons_it)[
"metadata"].is_object()) {
530 meta.consolidated = (*cons_it)[
"metadata"];
537[[nodiscard]]
inline std::string chunk_key(
const std::vector<std::uint64_t>& index,
539 std::string key =
"c";
540 for (
const std::uint64_t i : index) {
542 key += std::to_string(i);
550[[nodiscard]]
inline std::string emit_data_type(
DataType dt) {
576 case DType::complex64:
578 case DType::complex128:
581 return "r" + std::to_string(std::uint64_t{dt.
itemsize} * 8);
583 throw error(
"v3 emission not implemented for this dtype");
590inline std::string hex_bit_string(
const Bytes& bytes,
bool reverse_for_endianness) {
591 constexpr std::string_view kDigits =
"0123456789abcdef";
592 std::string out =
"0x";
593 for (std::size_t i = 0; i < bytes.size(); ++i) {
594 const std::size_t at =
595 reverse_for_endianness && detail::host_is_little_endian() ? bytes.size() - 1 - i : i;
596 out.push_back(kDigits[
static_cast<std::size_t
>(bytes[at]) >> 4U]);
597 out.push_back(kDigits[
static_cast<std::size_t
>(bytes[at]) & 0x0FU]);
605inline json emit_float_fill(
const std::uint8_t* data, DType kind, std::uint32_t width) {
607 if (kind == DType::float16) {
608 std::uint16_t bits = 0;
609 std::memcpy(&bits, data, 2);
610 v = detail::half_bits_to_double(bits);
611 }
else if (kind == DType::float32) {
613 std::memcpy(&f, data, 4);
614 v =
static_cast<double>(f);
616 std::memcpy(&v, data, 8);
619 const Bytes bits(data, data + width);
620 if (bits == detail::quiet_nan_bytes(kind)) {
623 return hex_bit_string(bits,
true);
626 return v > 0 ?
"Infinity" :
"-Infinity";
638 const Bytes& bytes = fill ? *fill : zeros;
641 return bytes[0] != 0;
645 return detail_v3::emit_float_fill(bytes.data(), dt.
kind, dt.
itemsize);
646 case DType::complex64:
647 case DType::complex128: {
648 const DType component = dt.
kind == DType::complex64 ? DType::float32 : DType::float64;
649 const std::uint32_t half = dt.
itemsize / 2;
650 return json::array({detail_v3::emit_float_fill(bytes.data(), component, half),
651 detail_v3::emit_float_fill(bytes.data() + half, component, half)});
654 return detail_v3::hex_bit_string(bytes,
false);
657 return detail::fill_to_json(bytes, dt);
663inline json emit_codec_list(
const std::vector<CodecSpec>& codecs) {
664 json out = json::array();
666 if (codec.name ==
"shuffle") {
667 throw error(
"the v2 shuffle filter cannot be represented in v3 metadata");
669 json c = {{
"name", codec.name}};
670 if (codec.configuration.is_object() && !codec.configuration.empty()) {
671 c[
"configuration"] = codec.configuration;
673 out.push_back(std::move(c));
685 j[
"zarr_format"] = 3;
686 j[
"node_type"] =
"array";
687 j[
"shape"] = meta.
shape;
688 j[
"data_type"] = emit_data_type(meta.
dtype);
689 const std::vector<std::uint64_t>& grid_shape =
691 j[
"chunk_grid"] = {{
"name",
"regular"}, {
"configuration", {{
"chunk_shape", grid_shape}}}};
692 j[
"chunk_key_encoding"] = {
693 {
"name", meta.
key_encoding == ChunkKeyKind::v3_default ?
"default" :
"v2"},
697 json codecs = detail_v3::emit_codec_list(meta.
codecs);
698 for (std::size_t i = meta.
shard_levels.size(); i-- > 0;) {
700 const std::vector<std::uint64_t>& inner_shape =
702 codecs = json::array({{{
"name",
"sharding_indexed"},
704 {{
"chunk_shape", inner_shape},
705 {
"codecs", std::move(codecs)},
706 {
"index_codecs", detail_v3::emit_codec_list(level.
index_codecs)},
707 {
"index_location", level.
index_at_end ?
"end" :
"start"}}}}});
709 j[
"codecs"] = std::move(codecs);
720[[nodiscard]]
inline json emit_group_meta(
const json& attributes) {
722 j[
"zarr_format"] = 3;
723 j[
"node_type"] =
"group";
724 if (attributes.is_object() && !attributes.empty()) {
725 j[
"attributes"] = attributes;
734inline void consolidate(
Store& store) {
737 throw error(
"v3::consolidate: no zarr.json at the store root");
740 json metadata = json::object();
741 for (
const std::string& key : store.
list_prefix(
"")) {
742 if (key ==
kMetaKey || !detail::ends_with(key, std::string(
"/") +
kMetaKey)) {
745 const std::string path = key.substr(0, key.size() - std::string(
kMetaKey).size() - 1);
746 if (
const auto bytes = store.
read(key)) {
747 metadata[path] = detail::parse_json(*bytes, key);
750 root[
"consolidated_metadata"] = {
751 {
"kind",
"inline"}, {
"must_understand",
false}, {
"metadata", std::move(metadata)}};
virtual void write(std::string_view key, Bytes value)=0
Create or replace the value at key.
virtual std::vector< std::string > list_prefix(std::string_view prefix)=0
All keys starting with prefix ("" or ending in '/'), sorted.
virtual std::optional< Bytes > read(std::string_view key)=0
Full value at key, or std::nullopt if the key is absent.
Definition metadata.hpp:36
json configuration
Codec-specific configuration.
Definition metadata.hpp:40
std::string name
Codec name ("transpose", "bytes", "gzip", "zlib", ...).
Definition metadata.hpp:38
A concrete element type: kind plus size (the size only varies for raw).
Definition types.hpp:122
std::uint32_t itemsize
Element size in bytes.
Definition types.hpp:126
static constexpr DataType raw_bytes(std::uint32_t size)
A raw byte-string type of size bytes (v2 |V<size>).
Definition types.hpp:139
DType kind
Element type kind.
Definition types.hpp:124
static constexpr DataType of(DType kind)
Definition types.hpp:131
Definition metadata.hpp:86
bool index_at_end
v3 sharding spec index_location: end (default) or start.
Definition metadata.hpp:92
std::vector< CodecSpec > index_codecs
Codecs of the shard index (fixed-size: bytes and optional crc32c).
Definition metadata.hpp:90
DType
Definition types.hpp:52
std::vector< std::uint8_t > Bytes
Owned byte buffer used throughout the value-based public API.
Definition types.hpp:42
std::optional< Bytes > parse_fill(const json &v, DataType dt, const std::string &ctx)
Definition v2.hpp:279
ParsedDType parse_data_type(const std::string &text, const std::string &ctx)
Definition v2.hpp:126
json emit_fill(const std::optional< Bytes > &fill, DataType dt)
Definition v3.hpp:636
GroupMeta parse_group_meta(const json &j, const std::string &ctx, bool lenient=false)
Parses a v3 group zarr.json document.
Definition v3.hpp:509
constexpr const char * kMetaKey
v3 metadata document name.
Definition v3.hpp:29