Fix unflatten and binary dumping for non-default configurations

unflatten() decided between array and object by looking at the first reference
token it happened to see for a node: it started an array only when that token
was 0. With a sorted object type the token 0 always arrives first, so the
result was correct by accident; with an object type whose iteration order is
unspecified, {"/c/2":3,"/c/1":2,"/c/0":1} unflattened to an object with the
keys "0", "1", and "2" instead of an array.

Collect the pointer prefixes that have a reference token 0 among their children
before building the result, and let get_and_create() consult that set. The
outcome is now independent of the iteration order and matches, for every input,
what a sorted object type produced before: a value is restored as an array if
and only if one of its keys is 0. Iterating the flattened object in a different
order would have been simpler, but it would have changed the key order of the
result for insertion-ordered object types.

The serializer, std::hash, and the UBJSON writer converted the elements of a
binary value to an integer implicitly, which does not compile for a BinaryType
whose value type is std::byte, and which made dump() write the bytes of a
signed value type as negative numbers. Convert to std::uint8_t explicitly in
all three places, so every byte type dumps as 0..255. The default
std::vector<std::uint8_t> configuration is unaffected.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018hxZxz8svM54c6ATEvXp5E
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
This commit is contained in:
Niels LohmannandClaude Opus 5 committed 2026-08-28 17:38:55 +00:00
1 parent 06feaa8d04
commit 7a37a27a67
11 files changed
+207 -39

No files matched your search

@@ -887,7 +887,9 @@ class binary_writer
for (size_t i = 0; i < j.m_data.m_value.binary->size(); ++i)
{
oa->write_character(to_char_type(bjdata_draft3 ? 'B' : 'U'));
oa->write_character(to_char_type(j.m_data.m_value.binary->data()[i]));
// the cast is needed for binary types whose value type
// is not an integer (e.g., std::byte)
oa->write_character(to_char_type(static_cast<std::uint8_t>(j.m_data.m_value.binary->data()[i])));
}
}
+18 -6
View File
@@ -274,10 +274,10 @@ class serializer
for (auto i = val.m_data.m_value.binary->cbegin();
i != val.m_data.m_value.binary->cend() - 1; ++i)
{
dump_integer(*i);
dump_integer(to_byte_value(*i));
o->write_characters(", ", 2);
}
dump_integer(val.m_data.m_value.binary->back());
dump_integer(to_byte_value(val.m_data.m_value.binary->back()));
}
o->write_characters("],\n", 3);
@@ -305,10 +305,10 @@ class serializer
for (auto i = val.m_data.m_value.binary->cbegin();
i != val.m_data.m_value.binary->cend() - 1; ++i)
{
dump_integer(*i);
dump_integer(to_byte_value(*i));
o->write_character(',');
}
dump_integer(val.m_data.m_value.binary->back());
dump_integer(to_byte_value(val.m_data.m_value.binary->back()));
}
o->write_characters("],\"subtype\":", 12);
@@ -703,6 +703,19 @@ class serializer
pos += 6;
}
/*!
@brief convert a single element of a binary value to its byte value
The elements of a binary value are dumped as the numbers 0..255, regardless
of the value type of the configured BinaryType: that type may be signed
(`char`), unsigned (`std::uint8_t`), or not an integer at all
(`std::byte`), none of which @ref dump_integer can handle uniformly.
*/
static std::uint8_t to_byte_value(binary_char_t x) noexcept
{
return static_cast<std::uint8_t>(x);
}
// templates to avoid warnings about useless casts
template <typename NumberType, enable_if_t<std::is_signed<NumberType>::value, int> = 0>
bool is_negative_number(NumberType x)
@@ -728,8 +741,7 @@ class serializer
template < typename NumberType, detail::enable_if_t <
std::is_integral<NumberType>::value ||
std::is_same<NumberType, number_unsigned_t>::value ||
std::is_same<NumberType, number_integer_t>::value ||
std::is_same<NumberType, binary_char_t>::value,
std::is_same<NumberType, number_integer_t>::value,
int > = 0 >
void dump_integer(NumberType x)
{