mirror of
https://github.com/nlohmann/json.git
synced 2026-09-29 14:24:56 +01:00
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>
113 lines
3.7 KiB
C++
113 lines
3.7 KiB
C++
// __ _____ _____ _____
|
|
// __| | __| | | | JSON for Modern C++ (supporting code)
|
|
// | | |__ | | | | | | version 3.12.0
|
|
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
|
//
|
|
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
#include "doctest_compatibility.h"
|
|
|
|
#include <nlohmann/json.hpp>
|
|
|
|
#include <cstdint>
|
|
#include <functional>
|
|
#include <map>
|
|
#include <string>
|
|
#include <type_traits>
|
|
#include <unordered_map>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
namespace
|
|
{
|
|
|
|
// An ObjectType that does *not* define a key_compare member type. It adapts
|
|
// std::unordered_map to the template argument order expected by basic_json,
|
|
// where the third argument is a comparator rather than a hash function.
|
|
template<class Key, class T, class IgnoredCompare, class Allocator>
|
|
struct unordered_map_object
|
|
: std::unordered_map<Key, T, std::hash<Key>, std::equal_to<Key>, Allocator>
|
|
{
|
|
using base_t = std::unordered_map<Key, T, std::hash<Key>, std::equal_to<Key>, Allocator>;
|
|
using base_t::base_t;
|
|
};
|
|
|
|
using unordered_json = nlohmann::basic_json<unordered_map_object>;
|
|
|
|
} // namespace
|
|
|
|
TEST_CASE("object type without key_compare")
|
|
{
|
|
SECTION("object_comparator_t falls back to default_object_comparator_t")
|
|
{
|
|
CHECK(std::is_same < unordered_json::object_comparator_t,
|
|
unordered_json::default_object_comparator_t >::value);
|
|
}
|
|
|
|
SECTION("object types defining key_compare are unaffected")
|
|
{
|
|
CHECK(std::is_same<nlohmann::json::object_comparator_t,
|
|
nlohmann::json::object_t::key_compare>::value);
|
|
CHECK(std::is_same<nlohmann::ordered_json::object_comparator_t,
|
|
nlohmann::ordered_json::object_t::key_compare>::value);
|
|
}
|
|
|
|
SECTION("creating and accessing values")
|
|
{
|
|
unordered_json j;
|
|
j["one"] = 1;
|
|
j["two"] = "zwei";
|
|
j["three"]["nested"] = true;
|
|
|
|
CHECK(j.size() == 3);
|
|
CHECK(j.at("one") == 1);
|
|
CHECK(j["two"] == "zwei");
|
|
CHECK(j["three"]["nested"] == true);
|
|
CHECK(j.contains("one"));
|
|
CHECK(!j.contains("four"));
|
|
CHECK(j.find("one") != j.end());
|
|
CHECK(j.count("one") == 1);
|
|
CHECK(j.erase("one") == 1);
|
|
CHECK(j.size() == 2);
|
|
}
|
|
|
|
SECTION("serialization and deserialization")
|
|
{
|
|
const auto j = unordered_json::parse(R"({"a":[1,2,3],"b":{"c":null}})");
|
|
CHECK(j["a"].size() == 3);
|
|
CHECK(j["a"][2] == 3);
|
|
CHECK(j["b"]["c"].is_null());
|
|
CHECK(unordered_json::parse(j.dump()) == j);
|
|
}
|
|
|
|
SECTION("binary formats")
|
|
{
|
|
const auto j = unordered_json::parse(R"({"a":[1,2,3],"b":"x"})");
|
|
CHECK(unordered_json::from_cbor(unordered_json::to_cbor(j)) == j);
|
|
CHECK(unordered_json::from_msgpack(unordered_json::to_msgpack(j)) == j);
|
|
}
|
|
|
|
SECTION("flatten and unflatten do not depend on the iteration order")
|
|
{
|
|
// the flattened object is iterated in an unspecified order, so
|
|
// unflatten() must not decide between array and object based on
|
|
// whichever reference token it happens to see first
|
|
const auto j = unordered_json::parse(
|
|
R"({"c":[1,2,3],"d":{"e":"s"},"n":[[0,1],[2]],"o":{"2":"x"}})");
|
|
CHECK(j.flatten().unflatten() == j);
|
|
}
|
|
|
|
SECTION("conversion to and from nlohmann::json")
|
|
{
|
|
const auto j = unordered_json::parse(R"({"a":1,"b":[true,null]})");
|
|
const nlohmann::json converted(j);
|
|
|
|
CHECK(converted.is_object());
|
|
CHECK(converted["a"] == 1);
|
|
CHECK(converted["b"][0] == true);
|
|
CHECK(converted["b"][1].is_null());
|
|
CHECK(unordered_json(converted) == j);
|
|
}
|
|
}
|