diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 4ee989c19..e325a2555 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -20744,6 +20744,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'; @@ -20757,6 +20759,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(current)))) + { + return false; + } + continue; + } + if (JSON_HEDLEY_UNLIKELY(!get_ubjson_value(size_and_type.second))) { return false; @@ -22122,6 +22142,8 @@ class binary_reader return "uint8"; case 'd': return "single"; + case 'h': + return "half"; case 'i': return "int8"; case 'l': @@ -30276,7 +30298,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) { @@ -30484,10 +30506,13 @@ class binary_writer switch (dtype) { case 'U': - case 'C': case 'B': ok = write_bjdata_ndarray_element(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(el) && bjdata_ndarray_value_in_range(el); + break; case 'i': ok = write_bjdata_ndarray_element(el, dry_run); break; diff --git a/tests/src/unit-bjdata.cpp b/tests/src/unit-bjdata.cpp index 2c929062c..d0a22e50c 100644 --- a/tests/src/unit-bjdata.cpp +++ b/tests/src/unit-bjdata.cpp @@ -2921,14 +2921,20 @@ TEST_CASE("BJData") // 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}) + 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({'[', '$', '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}) + 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}}});