mirror of
https://github.com/nlohmann/json.git
synced 2026-09-08 09:18:00 +00:00
Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d2c2db92a9 | ||
|
|
09b6b6b5ba |
@@ -125,6 +125,7 @@ The library uses the following mapping from JSON values types to BJData types ac
|
||||
|
||||
- `"_ArrayType_"` is one of `uint8`, `int8`, `uint16`, `int16`, `uint32`, `int32`, `uint64`, `int64`, `single`,
|
||||
`double`, `char`, or `byte`,
|
||||
- `"_ArraySize_"` is an array, since the dimensions are written as the ND-array header's length,
|
||||
- every entry of `"_ArraySize_"` is a non-negative integer, and their product is representable as a `std::size_t`,
|
||||
- `"_ArrayData_"` holds exactly that many elements, and
|
||||
- every element of `"_ArrayData_"` is a number of the kind named by `"_ArrayType_"` (a floating-point number for
|
||||
|
||||
@@ -1647,6 +1647,20 @@ class binary_writer
|
||||
return 'D'; // float 64
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief checks whether a JSON number fits into @a TargetType
|
||||
@param[in] el a JSON number of either the signed or unsigned integer kind
|
||||
@return whether @a el's value can be represented by @a TargetType without
|
||||
wrapping, regardless of which of the two kinds it is stored as
|
||||
*/
|
||||
template<typename TargetType>
|
||||
static bool bjdata_ndarray_value_in_range(const BasicJsonType& el)
|
||||
{
|
||||
return el.is_number_unsigned()
|
||||
? value_in_range_of<TargetType>(el.template get<std::uint64_t>())
|
||||
: value_in_range_of<TargetType>(el.template get<std::int64_t>());
|
||||
}
|
||||
|
||||
/*!
|
||||
@return false if the object is successfully converted to a bjdata ndarray, true if the type or size is invalid
|
||||
*/
|
||||
@@ -1668,6 +1682,15 @@ class binary_writer
|
||||
CharType dtype = it->second;
|
||||
|
||||
key = "_ArraySize_";
|
||||
// the dimensions are written verbatim as the header length below, so a
|
||||
// value that is not an array cannot produce a valid one: null emits 'Z'
|
||||
// and an object emits '{', neither of which a reader accepts after '#'.
|
||||
// Such an object is not a valid ndarray and falls back to a plain object.
|
||||
if (!value.at(key).is_array())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
std::size_t len = (value.at(key).empty() ? 0 : 1);
|
||||
for (const auto& el : value.at(key))
|
||||
{
|
||||
@@ -1722,6 +1745,60 @@ class binary_writer
|
||||
}
|
||||
}
|
||||
|
||||
// every element is cast to the (possibly narrower) C++ type matching
|
||||
// dtype below; a value that does not fit that type would silently
|
||||
// wrap (integers) or overflow to infinity (the "single" precision
|
||||
// float) instead of being reported, so such an object falls back to
|
||||
// a plain object encoding as well
|
||||
for (const auto& el : value.at(key))
|
||||
{
|
||||
bool in_range = true;
|
||||
switch (dtype)
|
||||
{
|
||||
case 'U':
|
||||
case 'C':
|
||||
case 'B':
|
||||
in_range = bjdata_ndarray_value_in_range<std::uint8_t>(el);
|
||||
break;
|
||||
case 'i':
|
||||
in_range = bjdata_ndarray_value_in_range<std::int8_t>(el);
|
||||
break;
|
||||
case 'u':
|
||||
in_range = bjdata_ndarray_value_in_range<std::uint16_t>(el);
|
||||
break;
|
||||
case 'I':
|
||||
in_range = bjdata_ndarray_value_in_range<std::int16_t>(el);
|
||||
break;
|
||||
case 'm':
|
||||
in_range = bjdata_ndarray_value_in_range<std::uint32_t>(el);
|
||||
break;
|
||||
case 'l':
|
||||
in_range = bjdata_ndarray_value_in_range<std::int32_t>(el);
|
||||
break;
|
||||
case 'M':
|
||||
in_range = bjdata_ndarray_value_in_range<std::uint64_t>(el);
|
||||
break;
|
||||
case 'L':
|
||||
in_range = bjdata_ndarray_value_in_range<std::int64_t>(el);
|
||||
break;
|
||||
case 'd':
|
||||
{
|
||||
const auto dval = el.template get<double>();
|
||||
in_range = !std::isfinite(dval) ||
|
||||
(dval >= static_cast<double>(std::numeric_limits<float>::lowest()) &&
|
||||
dval <= static_cast<double>((std::numeric_limits<float>::max)()));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
// 'D' (double) already spans the full range of number_float_t
|
||||
break;
|
||||
}
|
||||
if (!in_range)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
oa->write_character('[');
|
||||
oa->write_character('$');
|
||||
oa->write_character(dtype);
|
||||
|
||||
@@ -18655,6 +18655,20 @@ class binary_writer
|
||||
return 'D'; // float 64
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief checks whether a JSON number fits into @a TargetType
|
||||
@param[in] el a JSON number of either the signed or unsigned integer kind
|
||||
@return whether @a el's value can be represented by @a TargetType without
|
||||
wrapping, regardless of which of the two kinds it is stored as
|
||||
*/
|
||||
template<typename TargetType>
|
||||
static bool bjdata_ndarray_value_in_range(const BasicJsonType& el)
|
||||
{
|
||||
return el.is_number_unsigned()
|
||||
? value_in_range_of<TargetType>(el.template get<std::uint64_t>())
|
||||
: value_in_range_of<TargetType>(el.template get<std::int64_t>());
|
||||
}
|
||||
|
||||
/*!
|
||||
@return false if the object is successfully converted to a bjdata ndarray, true if the type or size is invalid
|
||||
*/
|
||||
@@ -18676,6 +18690,15 @@ class binary_writer
|
||||
CharType dtype = it->second;
|
||||
|
||||
key = "_ArraySize_";
|
||||
// the dimensions are written verbatim as the header length below, so a
|
||||
// value that is not an array cannot produce a valid one: null emits 'Z'
|
||||
// and an object emits '{', neither of which a reader accepts after '#'.
|
||||
// Such an object is not a valid ndarray and falls back to a plain object.
|
||||
if (!value.at(key).is_array())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
std::size_t len = (value.at(key).empty() ? 0 : 1);
|
||||
for (const auto& el : value.at(key))
|
||||
{
|
||||
@@ -18730,6 +18753,60 @@ class binary_writer
|
||||
}
|
||||
}
|
||||
|
||||
// every element is cast to the (possibly narrower) C++ type matching
|
||||
// dtype below; a value that does not fit that type would silently
|
||||
// wrap (integers) or overflow to infinity (the "single" precision
|
||||
// float) instead of being reported, so such an object falls back to
|
||||
// a plain object encoding as well
|
||||
for (const auto& el : value.at(key))
|
||||
{
|
||||
bool in_range = true;
|
||||
switch (dtype)
|
||||
{
|
||||
case 'U':
|
||||
case 'C':
|
||||
case 'B':
|
||||
in_range = bjdata_ndarray_value_in_range<std::uint8_t>(el);
|
||||
break;
|
||||
case 'i':
|
||||
in_range = bjdata_ndarray_value_in_range<std::int8_t>(el);
|
||||
break;
|
||||
case 'u':
|
||||
in_range = bjdata_ndarray_value_in_range<std::uint16_t>(el);
|
||||
break;
|
||||
case 'I':
|
||||
in_range = bjdata_ndarray_value_in_range<std::int16_t>(el);
|
||||
break;
|
||||
case 'm':
|
||||
in_range = bjdata_ndarray_value_in_range<std::uint32_t>(el);
|
||||
break;
|
||||
case 'l':
|
||||
in_range = bjdata_ndarray_value_in_range<std::int32_t>(el);
|
||||
break;
|
||||
case 'M':
|
||||
in_range = bjdata_ndarray_value_in_range<std::uint64_t>(el);
|
||||
break;
|
||||
case 'L':
|
||||
in_range = bjdata_ndarray_value_in_range<std::int64_t>(el);
|
||||
break;
|
||||
case 'd':
|
||||
{
|
||||
const auto dval = el.template get<double>();
|
||||
in_range = !std::isfinite(dval) ||
|
||||
(dval >= static_cast<double>(std::numeric_limits<float>::lowest()) &&
|
||||
dval <= static_cast<double>((std::numeric_limits<float>::max)()));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
// 'D' (double) already spans the full range of number_float_t
|
||||
break;
|
||||
}
|
||||
if (!in_range)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
oa->write_character('[');
|
||||
oa->write_character('$');
|
||||
oa->write_character(dtype);
|
||||
|
||||
@@ -2751,6 +2751,78 @@ TEST_CASE("BJData")
|
||||
CHECK(json::to_bjdata(j_ok) == std::vector<uint8_t>({'[', '$', 'U', '#', '[', 'i', 2, 'i', 3, ']', 1, 2, 3, 4, 5, 6}));
|
||||
CHECK(json::from_bjdata(json::to_bjdata(j_ok), true, true) == j_ok);
|
||||
}
|
||||
|
||||
SECTION("ndarray whose _ArraySize_ is not an array stays as object")
|
||||
{
|
||||
// the shape is written verbatim as the header length, so a
|
||||
// value that is not an array cannot produce a valid one: null
|
||||
// would emit 'Z' and an object '{', neither of which a reader
|
||||
// accepts after '#'. Both have to stay plain objects.
|
||||
json const j_null = json({{"_ArrayType_", "uint8"}, {"_ArraySize_", nullptr}, {"_ArrayData_", json::array()}});
|
||||
const auto out_null = json::to_bjdata(j_null);
|
||||
CHECK(out_null.at(0) == '{');
|
||||
CHECK(json::from_bjdata(out_null) == j_null);
|
||||
|
||||
// an object shape passes the per-entry check by iterating its
|
||||
// values rather than dimensions, so it needs rejecting too
|
||||
json const j_obj = json({{"_ArrayType_", "uint8"}, {"_ArraySize_", {{"a", 1}}}, {"_ArrayData_", {1}}});
|
||||
const auto out_obj = json::to_bjdata(j_obj);
|
||||
CHECK(out_obj.at(0) == '{');
|
||||
CHECK(json::from_bjdata(out_obj) == j_obj);
|
||||
|
||||
// a scalar shape is not a dimension list either
|
||||
json const j_num = json({{"_ArrayType_", "uint8"}, {"_ArraySize_", 1}, {"_ArrayData_", {1}}});
|
||||
const auto out_num = json::to_bjdata(j_num);
|
||||
CHECK(out_num.at(0) == '{');
|
||||
CHECK(json::from_bjdata(out_num) == j_num);
|
||||
}
|
||||
|
||||
SECTION("ndarray with out-of-range _ArrayData_ elements stays as object")
|
||||
{
|
||||
// each element is cast to the (possibly narrower) C++ type
|
||||
// named by _ArrayType_ before being written; a value that
|
||||
// does not fit that type would silently wrap instead of
|
||||
// being reported, so such an object falls back to a plain
|
||||
// object encoding that still round-trips (see GitHub issue #5403)
|
||||
|
||||
// an unsigned element that does not fit uint8
|
||||
json const j_uint8 = json({{"_ArrayType_", "uint8"}, {"_ArraySize_", {2}}, {"_ArrayData_", {1, 256}}});
|
||||
const auto out_uint8 = json::to_bjdata(j_uint8);
|
||||
CHECK(out_uint8.at(0) == '{');
|
||||
CHECK(json::from_bjdata(out_uint8) == j_uint8);
|
||||
|
||||
// a signed element that does not fit int8
|
||||
json const j_int8 = json({{"_ArrayType_", "int8"}, {"_ArraySize_", {2}}, {"_ArrayData_", {1, 200}}});
|
||||
const auto out_int8 = json::to_bjdata(j_int8);
|
||||
CHECK(out_int8.at(0) == '{');
|
||||
CHECK(json::from_bjdata(out_int8) == j_int8);
|
||||
|
||||
// a negative element is likewise out of range for an
|
||||
// unsigned _ArrayType_
|
||||
json const j_uint16_neg = json({{"_ArrayType_", "uint16"}, {"_ArraySize_", {2}}, {"_ArrayData_", {1, -1}}});
|
||||
const auto out_uint16_neg = json::to_bjdata(j_uint16_neg);
|
||||
CHECK(out_uint16_neg.at(0) == '{');
|
||||
CHECK(json::from_bjdata(out_uint16_neg) == j_uint16_neg);
|
||||
|
||||
// a double element that overflows to infinity when narrowed
|
||||
// to the "single" (float) precision named by _ArrayType_
|
||||
json const j_single = json({{"_ArrayType_", "single"}, {"_ArraySize_", {2}}, {"_ArrayData_", {1.5, 1e40}}});
|
||||
const auto out_single = json::to_bjdata(j_single);
|
||||
CHECK(out_single.at(0) == '{');
|
||||
CHECK(json::from_bjdata(out_single) == j_single);
|
||||
|
||||
// in-range boundary values still use the compact ndarray encoding
|
||||
json const j_uint8_ok = json({{"_ArrayType_", "uint8"}, {"_ArraySize_", {2}}, {"_ArrayData_", {0, 255}}});
|
||||
CHECK(json::to_bjdata(j_uint8_ok) == std::vector<uint8_t>({'[', '$', 'U', '#', '[', 'i', 2, ']', 0, 255}));
|
||||
|
||||
json const j_int8_ok = json({{"_ArrayType_", "int8"}, {"_ArraySize_", {2}}, {"_ArrayData_", {-128, 127}}});
|
||||
CHECK(json::to_bjdata(j_int8_ok) == std::vector<uint8_t>({'[', '$', 'i', '#', '[', 'i', 2, ']', 0x80, 0x7F}));
|
||||
|
||||
json const j_single_ok = json({{"_ArrayType_", "single"}, {"_ArraySize_", {1}}, {"_ArrayData_", {1.5}}});
|
||||
const auto out_single_ok = json::to_bjdata(j_single_ok);
|
||||
CHECK(out_single_ok.at(0) == '[');
|
||||
CHECK(json::from_bjdata(out_single_ok) == json({1.5f}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user