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-26 15:52:53 +01:00
1 parent da65c02eea
commit c4e1032f36
3 files changed
+88 -2

No files matched your search

@@ -3277,6 +3277,8 @@ class binary_reader
// get_ubjson_size_value() (the type marker is known before the dimension vector
// that determines size_and_type.first is read, so it is emitted first there to
// match the documented _ArrayType_, _ArraySize_, _ArrayData_ key order)
// 'C' elements are read as unsigned bytes, but BJData limits a char to 0..127
const bool nd_is_char = size_and_type.second == 'C';
if (size_and_type.second == 'C' || size_and_type.second == 'B')
{
size_and_type.second = 'U';
@@ -3290,6 +3292,24 @@ class binary_reader
for (std::size_t i = 0; i < size_and_type.first; ++i)
{
if (nd_is_char)
{
get();
if (JSON_HEDLEY_UNLIKELY(!unexpect_eof("char")))
{
return false;
}
if (JSON_HEDLEY_UNLIKELY(current > 127))
{
return last_byte_error(exception_id::invalid_string_or_size, "byte after 'C' must be in range 0x00..0x7F", "char", "");
}
if (JSON_HEDLEY_UNLIKELY(!sax->number_unsigned(static_cast<number_unsigned_t>(current))))
{
return false;
}
continue;
}
if (JSON_HEDLEY_UNLIKELY(!get_ubjson_value(size_and_type.second)))
{
return false;
@@ -4655,6 +4675,8 @@ class binary_reader
return "uint8";
case 'd':
return "single";
case 'h':
return "half";
case 'i':
return "int8";
case 'l':
@@ -3848,7 +3848,7 @@ class binary_writer
A C++11 `constexpr` function cannot contain a `switch`, so this is a plain
comparison chain instead; it is only reached once per ND-array candidate
object. Keep in sync with binary_reader's `bjd_type_name()`, which maps
the other way.
the other way (it also reads "half", which is never written).
*/
static CharType bjdata_ndarray_type_marker(const string_t& name)
{
@@ -4056,10 +4056,13 @@ class binary_writer
switch (dtype)
{
case 'U':
case 'C':
case 'B':
ok = write_bjdata_ndarray_element<std::uint8_t>(el, dry_run);
break;
case 'C':
// BJData limits a char to 0..127 (the intersection of the int8 and uint8 ranges)
ok = bjdata_ndarray_value_in_range<std::int8_t>(el) && bjdata_ndarray_value_in_range<std::uint8_t>(el);
break;
case 'i':
ok = write_bjdata_ndarray_element<std::int8_t>(el, dry_run);
break;