13#include "libzarr/detail/common.hpp"
36[[nodiscard]]
inline std::string
meta_key(
const std::string& path,
const char* suffix) {
37 return path.empty() ? suffix : path +
"/" + suffix;
52template <
typename Fail>
53DataType int_dtype(
bool is_signed, std::uint64_t size,
const Fail& fail) {
56 kind = is_signed ? DType::int8 : DType::uint8;
57 }
else if (size == 2) {
58 kind = is_signed ? DType::int16 : DType::uint16;
59 }
else if (size == 4) {
60 kind = is_signed ? DType::int32 : DType::uint32;
61 }
else if (size == 8) {
62 kind = is_signed ? DType::int64 : DType::uint64;
64 fail(
"integer size must be 1, 2, 4 or 8");
71template <
typename Fail>
72DataType dtype_of_code(
char code, std::uint64_t size,
const Fail& fail) {
76 fail(
"bool must have size 1");
81 return int_dtype(code ==
'i', size, fail);
92 fail(
"float size must be 2, 4 or 8");
96 fail(
"raw dtype must have a positive size");
106 fail(
"complex size must be 8 or 16");
110 fail(
"string dtypes are not supported");
114 fail(
"datetime dtypes are not supported");
117 fail(
"unknown type code");
127 const auto fail = [&](
const char* why) {
throw error(ctx +
": dtype '" + text +
"': " + why); };
128 if (text.size() < 3) {
129 fail(
"expected the form '<f8', '|u1', ...");
131 const char order = text[0];
132 if (order !=
'<' && order !=
'>' && order !=
'|') {
133 fail(
"byte order must be '<', '>' or '|'");
135 std::uint64_t size = 0;
136 for (std::size_t i = 2; i < text.size(); ++i) {
137 if (text[i] <
'0' || text[i] >
'9' || size > 0xFFFFFF) {
138 fail(
"malformed item size");
140 size = size * 10 +
static_cast<std::uint64_t
>(text[i] -
'0');
142 const DataType dtype = detail_v2::dtype_of_code(text[1], size, fail);
144 const bool multi_byte = dtype.
kind != DType::boolean && dtype.
kind != DType::raw && size > 1;
145 return {dtype, order ==
'>' && multi_byte};
152 const char order_multi = big_endian ?
'>' :
'<';
161 return std::string(1, order_multi) +
"i2";
163 return std::string(1, order_multi) +
"i4";
165 return std::string(1, order_multi) +
"i8";
167 return std::string(1, order_multi) +
"u2";
169 return std::string(1, order_multi) +
"u4";
171 return std::string(1, order_multi) +
"u8";
173 return std::string(1, order_multi) +
"f2";
175 return std::string(1, order_multi) +
"f4";
177 return std::string(1, order_multi) +
"f8";
178 case DType::complex64:
179 return std::string(1, order_multi) +
"c8";
180 case DType::complex128:
181 return std::string(1, order_multi) +
"c16";
183 return "|V" + std::to_string(dt.
itemsize);
185 throw error(
"v2 emission not implemented for this dtype");
193[[nodiscard]]
inline std::optional<Bytes> parse_fill(
const json& v,
DataType dt,
194 const std::string& ctx);
198inline std::optional<Bytes> non_finite_fill(
const std::string& s,
DType kind) {
203 return detail::quiet_nan_bytes(kind);
205 if (s ==
"Infinity" || s ==
"+Infinity") {
206 return detail::infinity_bytes(kind,
false);
208 if (s ==
"-Infinity") {
209 return detail::infinity_bytes(kind,
true);
216inline Bytes complex_component_fill(
const json& v, DType kind,
const std::string& ctx) {
218 return detail::fill_from_double(v.get<
double>(),
DataType::of(kind), ctx);
221 if (
auto fill = non_finite_fill(v.get<std::string>(), kind)) {
222 return *std::move(fill);
225 throw error(ctx +
": cannot interpret complex fill_value component " + v.dump());
230inline std::optional<Bytes> numeric_string_fill(
const std::string& s, DataType dt,
231 const std::string& ctx) {
235 const char* begin = s.c_str();
238 const double d = std::strtod(begin, &end);
239 if (end == begin + s.size()) {
240 return detail::fill_from_double(d, dt, ctx);
242 }
else if (
is_signed_int(dt.kind) || dt.kind == DType::boolean) {
243 const long long i = std::strtoll(begin, &end, 10);
244 if (end == begin + s.size()) {
245 return detail::fill_from_int(
static_cast<std::int64_t
>(i), dt, ctx);
248 const unsigned long long u = std::strtoull(begin, &end, 10);
249 if (end == begin + s.size()) {
250 return detail::fill_from_uint(
static_cast<std::uint64_t
>(u), dt, ctx);
256inline Bytes string_fill(
const std::string& s, DataType dt,
const std::string& ctx) {
258 if (
auto fill = non_finite_fill(s, dt.kind)) {
259 return *std::move(fill);
262 if (dt.kind == DType::raw) {
264 Bytes decoded = detail::base64_decode(s, (ctx +
": fill_value").c_str());
265 if (decoded.size() != dt.itemsize) {
266 throw error(ctx +
": base64 fill_value decodes to " + std::to_string(decoded.size()) +
267 " bytes, dtype needs " + std::to_string(dt.itemsize));
271 if (
auto fill = numeric_string_fill(s, dt, ctx)) {
272 return *std::move(fill);
274 throw error(ctx +
": cannot interpret fill_value '" + s +
"' for this dtype");
280 const std::string& ctx) {
287 const DType component = dt.
kind == DType::complex64 ? DType::float32 : DType::float64;
288 Bytes out = detail_v2::complex_component_fill(v[0], component, ctx);
289 const Bytes imag = detail_v2::complex_component_fill(v[1], component, ctx);
290 out.insert(out.end(), imag.begin(), imag.end());
297 throw error(ctx +
": fill_value must be a scalar, got an array of " + std::to_string(v.size()));
299 if (v.is_boolean()) {
300 if (dt.
kind != DType::boolean) {
301 throw error(ctx +
": boolean fill_value for non-bool dtype");
303 return detail::scalar_bytes<std::uint8_t>(v.get<
bool>() ? 1 : 0);
306 return detail_v2::string_fill(v.get<std::string>(), dt, ctx);
308 if (v.is_number_unsigned()) {
309 return detail::fill_from_uint(v.get<std::uint64_t>(), dt, ctx);
311 if (v.is_number_integer()) {
312 return detail::fill_from_int(v.get<std::int64_t>(), dt, ctx);
314 if (v.is_number_float()) {
315 return detail::fill_from_double(v.get<
double>(), dt, ctx);
317 throw error(ctx +
": unsupported fill_value " + v.dump());
327inline char parse_separator(
const json& j,
const std::string& ctx) {
328 const auto it = j.find(
"dimension_separator");
332 if (!it->is_string() || (it->get<std::string>() !=
"." && it->get<std::string>() !=
"/")) {
333 throw error(ctx + R
"(: 'dimension_separator' must be "." or "/")");
335 return it->get<std::string>()[0];
341inline std::vector<CodecSpec> parse_filters(
const json& j,
const std::string& ctx) {
342 std::vector<CodecSpec> out;
343 const auto it = j.find(
"filters");
344 if (it == j.end() || it->is_null()) {
348 if (!it->is_array()) {
349 throw error(ctx +
": 'filters' must be null or an array");
351 for (
const json& f : *it) {
352 if (!f.is_object() || !f.contains(
"id") || !f[
"id"].is_string()) {
353 throw error(ctx +
": each filter must be an object with an 'id'");
355 const auto id = f[
"id"].get<std::string>();
356 if (
id ==
"shuffle") {
360 const std::int64_t elementsize = detail::lenient_int(f,
"elementsize", 0, ctx);
361 out.push_back(CodecSpec{
"shuffle", {{
"elementsize", elementsize}}});
363 std::string msg = ctx;
364 msg +=
": unsupported v2 filter '";
374inline std::optional<CodecSpec> parse_compressor(
const json& j,
const std::string& ctx) {
375 const auto it = j.find(
"compressor");
376 if (it == j.end() || it->is_null()) {
379 if (!it->is_object() || !it->contains(
"id") || !(*it)[
"id"].is_string()) {
380 throw error(ctx +
": 'compressor' must be null or an object with an 'id'");
382 const auto id = (*it)[
"id"].get<std::string>();
383 if (
id ==
"zlib" ||
id ==
"gzip") {
385 const std::int64_t level = detail::lenient_int(*it,
"level", 1, ctx);
386 if (level < 0 || level > 9) {
387 throw error(ctx +
": compressor level must be in 0..9");
389 return CodecSpec{id, {{
"level", level}}};
396 const std::string cname = it->value(
"cname",
"lz4");
397 const std::int64_t clevel = detail::lenient_int(*it,
"clevel", 5, ctx);
399 const std::int64_t blocksize = detail::lenient_int(*it,
"blocksize", 0, ctx);
402 {{
"cname", cname}, {
"clevel", clevel}, {
"shuffle",
shuffle}, {
"blocksize", blocksize}}};
406 const std::int64_t level = detail::lenient_int(*it,
"level", 0, ctx);
407 return CodecSpec{
"zstd", {{
"level", level}}};
409 throw error(ctx +
": unsupported v2 compressor '" +
id +
"'");
416inline ArrayMeta parse_array_meta_impl(
const json& j,
const std::string& ctx) {
417 if (!j.is_object()) {
418 throw error(ctx +
": expected a JSON object");
420 const auto require = [&](
const char* name) ->
const json& {
421 const auto it = j.find(name);
423 throw error(ctx +
": missing required member '" + name +
"'");
427 if (detail::json_to_uint64(require(
"zarr_format"), ctx +
": zarr_format") != 2) {
428 throw error(ctx +
": zarr_format must be 2");
432 meta.format = ZarrFormat::v2;
433 meta.shape = detail::parse_extents(require(
"shape"),
"shape", ctx);
434 meta.chunk_shape = detail::parse_extents(require(
"chunks"),
"chunks", ctx);
435 if (meta.chunk_shape.size() != meta.shape.size()) {
436 throw error(ctx +
": 'chunks' must be an array of the same rank as 'shape'");
438 for (
const std::uint64_t c : meta.chunk_shape) {
440 throw error(ctx +
": chunk extents must be positive");
444 const json& dtype = require(
"dtype");
445 if (!dtype.is_string()) {
446 throw error(ctx +
": structured dtypes are not supported");
448 const ParsedDType parsed =
parse_data_type(dtype.get<std::string>(), ctx);
449 meta.dtype = parsed.dtype;
451 const json& order = require(
"order");
452 if (!order.is_string() || (order !=
"C" && order !=
"F")) {
453 throw error(ctx + R
"(: 'order' must be "C" or "F")");
458 const auto fill_it = j.find(
"fill_value");
459 meta.fill = fill_it == j.end() ? std::nullopt :
parse_fill(*fill_it, meta.dtype, ctx);
461 meta.dimension_separator = parse_separator(j, ctx);
465 if (order ==
"F" && meta.shape.size() >= 2) {
466 json perm = json::array();
467 for (std::size_t d = meta.shape.size(); d-- > 0;) {
470 meta.codecs.push_back({
"transpose", {{
"order", perm}}});
472 meta.codecs.push_back({
"bytes", {{
"endian", parsed.big_endian ?
"big" :
"little"}}});
473 for (CodecSpec& filter : parse_filters(j, ctx)) {
474 meta.codecs.push_back(std::move(filter));
476 if (
auto compressor = parse_compressor(j, ctx)) {
477 meta.codecs.push_back(*std::move(compressor));
483inline json emit_filters(
const std::vector<CodecSpec>& codecs) {
484 json filters = json::array();
485 for (
const CodecSpec& codec : codecs) {
486 if (codec.name ==
"shuffle") {
489 {
"elementsize", codec.configuration.value(
"elementsize", std::int64_t{0})}});
492 return filters.empty() ?
json(
nullptr) : filters;
501 return detail::guard_json(ctx, [&] {
return detail_v2::parse_array_meta_impl(j, ctx); });
508 j[
"zarr_format"] = 2;
509 j[
"shape"] = meta.
shape;
511 j[
"filters"] = detail_v2::emit_filters(meta.
codecs);
512 j[
"fill_value"] = detail::fill_to_json(meta.
fill, meta.
dtype);
514 j[
"dimension_separator"] =
"/";
517 bool big_endian =
false;
518 bool f_order =
false;
519 j[
"compressor"] =
nullptr;
521 if (codec.name ==
"bytes") {
522 big_endian = codec.configuration.value(
"endian",
"little") == std::string(
"big");
523 }
else if (codec.name ==
"transpose") {
525 const json& perm = codec.configuration.at(
"order");
526 for (std::size_t i = 0; i < perm.size(); ++i) {
527 if (perm[i].get<std::uint64_t>() != perm.size() - 1 - i) {
528 throw error(R
"(v2 cannot represent this transpose; only order:"F" (full reversal))");
532 }
else if (codec.name ==
"gzip" || codec.name ==
"zlib") {
533 j[
"compressor"] = {{
"id", codec.name},
534 {
"level", codec.configuration.value(
"level", std::int64_t{1})}};
535 }
else if (codec.name ==
"blosc") {
538 std::int64_t shuffle_num = 1;
539 if (shuffle.is_number_integer()) {
540 shuffle_num = shuffle.get<std::int64_t>();
541 }
else if (shuffle ==
"noshuffle") {
543 }
else if (shuffle ==
"bitshuffle") {
546 j[
"compressor"] = {{
"id",
"blosc"},
548 {
"clevel", codec.configuration.value(
"clevel", std::int64_t{5})},
549 {
"shuffle", shuffle_num},
550 {
"blocksize", codec.configuration.value(
"blocksize", std::int64_t{0})}};
551 }
else if (codec.name ==
"zstd") {
552 j[
"compressor"] = {{
"id",
"zstd"},
553 {
"level", codec.configuration.value(
"level", std::int64_t{0})}};
554 }
else if (codec.name ==
"shuffle") {
557 throw error(
"v2 cannot represent codec '" + codec.name +
"'");
561 j[
"order"] = f_order ?
"F" :
"C";
572 detail::guard_json(ctx, [&] {
573 if (!j.is_object() || j.find(
"zarr_format") == j.end() ||
574 detail::json_to_uint64(j.at(
"zarr_format"), ctx) != 2) {
575 throw error(ctx +
": not a v2 group (zarr_format must be 2)");
582[[nodiscard]]
inline std::string
chunk_key(
const std::vector<std::uint64_t>& index,
587 std::string key = std::to_string(index[0]);
588 for (std::size_t d = 1; d < index.size(); ++d) {
590 key += std::to_string(index[d]);
604 c[
"metadata"][key] = value;
614 auto meta_it = c.find(
"metadata");
615 if (meta_it != c.end()) {
625 json metadata = json::object();
626 for (
const std::string& key : store.
list_prefix(
"")) {
627 const std::string_view k = key;
628 const auto leaf_is = [&](
const char* suffix) {
629 return k == suffix || detail::ends_with(k, std::string(
"/") + suffix);
632 const auto bytes = store.
read(key);
634 metadata[key] = detail::parse_json(*bytes, key);
638 const json c = {{
"metadata", metadata}, {
"zarr_consolidated_format", 1}};
649 if (!c.is_object() || c.value(
"zarr_consolidated_format", std::int64_t{0}) != 1 ||
650 !c.contains(
"metadata") || !c[
"metadata"].is_object()) {
653 return c[
"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 void erase(std::string_view key)=0
Remove key; removing an absent key is a no-op.
virtual std::optional< Bytes > read(std::string_view key)=0
Full value at key, or std::nullopt if the key is absent.
CodecSpec shuffle(int elementsize=0)
shuffle: byte-transposition filter. elementsize 0 means the dtype size.
Definition metadata.hpp:69
Definition metadata.hpp:36
json configuration
Codec-specific configuration.
Definition metadata.hpp:40
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
A parsed v2 dtype string: the element type plus its stored byte order.
Definition v2.hpp:43
bool big_endian
Stored byte order (v2 '>' dtypes).
Definition v2.hpp:47
DataType dtype
Element type.
Definition v2.hpp:45
constexpr bool is_float(DType kind)
True for float16/float32/float64.
Definition types.hpp:71
constexpr bool is_unsigned_int(DType kind)
True for uint8..uint64.
Definition types.hpp:82
DType
Definition types.hpp:52
constexpr bool is_signed_int(DType kind)
True for int8..int64.
Definition types.hpp:76
std::vector< std::uint8_t > Bytes
Owned byte buffer used throughout the value-based public API.
Definition types.hpp:42
constexpr bool is_complex(DType kind)
True for complex64/complex128.
Definition types.hpp:88
std::optional< Bytes > parse_fill(const json &v, DataType dt, const std::string &ctx)
Definition v2.hpp:279
void check_group_meta(const json &j, const std::string &ctx)
Validates a .zgroup document.
Definition v2.hpp:571
constexpr const char * kGroupSuffix
v2 group metadata document name.
Definition v2.hpp:29
std::string meta_key(const std::string &path, const char *suffix)
Store key of a metadata document for the node at path ("" = root).
Definition v2.hpp:36
ParsedDType parse_data_type(const std::string &text, const std::string &ctx)
Definition v2.hpp:126
constexpr const char * kAttrsSuffix
v2 attributes document name.
Definition v2.hpp:31
void write_meta_key(Store &store, const std::string &key, const json &value)
Definition v2.hpp:600
constexpr const char * kArraySuffix
v2 array metadata document name.
Definition v2.hpp:27
std::optional< json > read_consolidated(Store &store)
Loads the consolidated metadata map if present and well-formed.
Definition v2.hpp:643
std::string chunk_key(const std::vector< std::uint64_t > &index, char separator)
Definition v2.hpp:582
std::string emit_data_type(DataType dt, bool big_endian)
Definition v2.hpp:151
json emit_array_meta(const ArrayMeta &meta)
Definition v2.hpp:506
void erase_meta_key(Store &store, const std::string &key)
Removes a metadata document, keeping .zmetadata in sync (see write_meta_key).
Definition v2.hpp:610
constexpr const char * kConsolidatedKey
v2 consolidated-metadata document (store root).
Definition v2.hpp:33
ArrayMeta parse_array_meta(const json &j, const std::string &ctx)
Definition v2.hpp:500
json emit_group_meta()
The (only) content of a v2 group document.
Definition v2.hpp:568
void consolidate(Store &store)
Definition v2.hpp:624