Read ND-arrays of half floats and enforce the char limit in ND-arrays

BJData N-dimensional arrays with element type 'h' (float16) failed to
read with a misleading parse_error.112, because the reader's dtype name
table lacked 'h'. It now maps to "half", the JData name. The writer still
does not pack "half"; such an object is written as a plain object and
reads back equal.

BJData limits 'C' (char) to 0..127 in Draft 2 and Draft 3, and the 1-D
reader enforces this, but ND-arrays with _ArrayType_ "char" wrote values
up to 255 as 'C' elements and read them back without a check. The writer
now only packs values in 0..127 (others fall back to a plain object that
round-trips), and the reader rejects 'C' elements above 127 with
parse_error.113 like the 1-D reader.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
This commit is contained in:
Niels Lohmann committed 2026-10-25 20:05:36 +01:00
1 parent d97ea0b163
commit 6755ad6ded
3 files changed
+88 -2

No files matched your search

+61
View File
@@ -2916,6 +2916,29 @@ TEST_CASE("BJData")
CHECK(json::from_bjdata(out_single_ok) == json({{"_ArrayType_", "single"}, {"_ArraySize_", {2, 1}}, {"_ArrayData_", {1.5f, -1.5f}}}));
}
SECTION("ndarray with _ArrayType_ \"char\" is limited to 0..127")
{
// both BJData drafts limit a char to 0..127; larger values must not
// be written as 'C' elements (the reader rejects them), so such an
// object stays a plain object that round-trips
for (const auto version : {json::bjdata_version_t::draft2, json::bjdata_version_t::draft3})
{
json const j_ok = json({{"_ArrayType_", "char"}, {"_ArraySize_", {2, 2}}, {"_ArrayData_", {0, 97, 98, 127}}});
const auto out_ok = json::to_bjdata(j_ok, true, true, version);
CHECK(out_ok == std::vector<uint8_t>({'[', '$', 'C', '#', '[', '$', 'i', '#', 'i', 2, 2, 2, 0, 97, 98, 127}));
CHECK(json::from_bjdata(out_ok) == j_ok);
for (const int bad : {128, 200, 255, 256, -1})
{
CAPTURE(bad)
json const j_bad = json({{"_ArrayType_", "char"}, {"_ArraySize_", {2, 2}}, {"_ArrayData_", {97, 98, 99, bad}}});
const auto out_bad = json::to_bjdata(j_bad, true, true, version);
CHECK(out_bad.at(0) == '{');
CHECK(json::from_bjdata(out_bad) == j_bad);
}
}
}
SECTION("ndarray annotation keys are read back in the documented order")
{
// from_bjdata() must emit the annotation object's keys in the order
@@ -3324,6 +3347,44 @@ TEST_CASE("BJData")
}
}
SECTION("ndarray of half-precision floats can be read")
{
// the 'h' element type is read as _ArrayType_ "half"
json _;
const std::vector<uint8_t> half = {'[', '$', 'h', '#', '[', '$', 'U', '#', 'U', 2, 2, 2, 0x00, 0x3C, 0x00, 0x40, 0x00, 0x42, 0x00, 0x44};
const json j_half = json::parse(R"({"_ArrayType_":"half","_ArraySize_":[2,2],"_ArrayData_":[1.0,2.0,3.0,4.0]})");
CHECK(json::from_bjdata(half) == j_half);
// the writer does not pack "half": it writes a plain object that reads back equal
CHECK(json::from_bjdata(json::to_bjdata(j_half)) == j_half);
CHECK(json::from_bjdata(json::to_bjdata(j_half, true, true, json::bjdata_version_t::draft3)) == j_half);
// truncated half data
CHECK_THROWS_AS(_ = json::from_bjdata(std::vector<uint8_t>(half.begin(), half.end() - 1)), json::parse_error&);
}
SECTION("ndarray of char rejects values above 127")
{
json _;
const std::vector<uint8_t> ok = {'[', '$', 'C', '#', '[', '$', 'U', '#', 'U', 2, 2, 2, 'a', 'b', 'c', 0x7F};
CHECK(json::from_bjdata(ok) == json::parse(R"({"_ArrayType_":"char","_ArraySize_":[2,2],"_ArrayData_":[97,98,99,127]})"));
std::vector<uint8_t> bad = ok;
bad.back() = 0xC8;
CHECK_THROWS_AS(_ = json::from_bjdata(bad), json::parse_error&);
CHECK(json::from_bjdata(bad, true, false).is_discarded());
// the same restriction holds for 1-D arrays
CHECK_THROWS_AS(_ = json::from_bjdata(std::vector<uint8_t>({'[', '$', 'C', '#', 'i', 1, 0xC8})), json::parse_error&);
// truncated input
CHECK_THROWS_AS(_ = json::from_bjdata(std::vector<uint8_t>(ok.begin(), ok.end() - 1)), json::parse_error&);
// 'B' (byte) elements may use the full range
const std::vector<uint8_t> byte_nd = {'[', '$', 'B', '#', '[', '$', 'U', '#', 'U', 2, 2, 2, 1, 2, 3, 0xC8};
CHECK(json::from_bjdata(byte_nd) == json::parse(R"({"_ArrayType_":"byte","_ArraySize_":[2,2],"_ArrayData_":[1,2,3,200]})"));
}
SECTION("do not accept NTFZ markers in ndarray optimized type (with count)")
{
json _;