* Speed up JSON pointer parsing, formatting, comparison and lookup Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Restore unflatten() speed after the linear-time rewrite The prefix tree introduced for linear unflatten() kept a std::map per prefix, copied each token into it and looked every token up a second time while creating the values. Replace the per-prefix maps by one open-addressing table keyed by (parent, token) that refers to the parsed tokens, skip the lookups for the prefix shared with the previous key (flattened objects are mostly sorted), and derive the prefix numbers of the second pass from parent links instead of lookups. Time and memory stay linear in the pointer depth, and the result is still independent of the iteration order of the flattened object. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Put [[noreturn]] before __attribute__ on the JSON pointer throw helpers Clang 18 on Linux rejects a C++11 attribute list after a GNU __attribute__ ("an attribute list cannot appear here"). Signed-off-by: Niels Lohmann <mail@nlohmann.me> --------- Signed-off-by: Niels Lohmann <mail@nlohmann.me>
373 lines
12 KiB
C++
373 lines
12 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 <algorithm>
|
|
#include <numeric>
|
|
#include <nlohmann/json.hpp>
|
|
using nlohmann::json;
|
|
|
|
TEST_CASE("algorithms")
|
|
{
|
|
json j_array = {13, 29, 3, {{"one", 1}, {"two", 2}}, true, false, {1, 2, 3}, "foo", "baz"};
|
|
json j_object = {{"one", 1}, {"two", 2}};
|
|
|
|
SECTION("non-modifying sequence operations")
|
|
{
|
|
SECTION("std::all_of")
|
|
{
|
|
CHECK(std::all_of(j_array.begin(), j_array.end(), [](const json & value)
|
|
{
|
|
return !value.empty();
|
|
}));
|
|
CHECK(std::all_of(j_object.begin(), j_object.end(), [](const json & value)
|
|
{
|
|
return value.type() == json::value_t::number_integer;
|
|
}));
|
|
}
|
|
|
|
SECTION("std::any_of")
|
|
{
|
|
CHECK(std::any_of(j_array.begin(), j_array.end(), [](const json & value)
|
|
{
|
|
return value.is_string() && value.get<std::string>() == "foo";
|
|
}));
|
|
CHECK(std::any_of(j_object.begin(), j_object.end(), [](const json & value)
|
|
{
|
|
return value.get<int>() > 1;
|
|
}));
|
|
}
|
|
|
|
SECTION("std::none_of")
|
|
{
|
|
CHECK(std::none_of(j_array.begin(), j_array.end(), [](const json & value)
|
|
{
|
|
return value.empty();
|
|
}));
|
|
CHECK(std::none_of(j_object.begin(), j_object.end(), [](const json & value)
|
|
{
|
|
return value.get<int>() <= 0;
|
|
}));
|
|
}
|
|
|
|
SECTION("std::for_each")
|
|
{
|
|
SECTION("reading")
|
|
{
|
|
int sum = 0;
|
|
|
|
std::for_each(j_array.cbegin(), j_array.cend(), [&sum](const json & value)
|
|
{
|
|
if (value.is_number())
|
|
{
|
|
sum += static_cast<int>(value);
|
|
}
|
|
});
|
|
|
|
CHECK(sum == 45);
|
|
}
|
|
|
|
SECTION("writing")
|
|
{
|
|
auto add17 = [](json & value)
|
|
{
|
|
if (value.is_array())
|
|
{
|
|
value.push_back(17);
|
|
}
|
|
};
|
|
|
|
std::for_each(j_array.begin(), j_array.end(), add17);
|
|
|
|
CHECK(j_array[6] == json({1, 2, 3, 17}));
|
|
}
|
|
}
|
|
|
|
SECTION("std::count")
|
|
{
|
|
CHECK(std::count(j_array.begin(), j_array.end(), json(true)) == 1);
|
|
}
|
|
|
|
SECTION("std::count_if")
|
|
{
|
|
CHECK(std::count_if(j_array.begin(), j_array.end(), [](const json & value)
|
|
{
|
|
return (value.is_number());
|
|
}) == 3);
|
|
CHECK(std::count_if(j_array.begin(), j_array.end(), [](const json&)
|
|
{
|
|
return true;
|
|
}) == 9);
|
|
}
|
|
|
|
SECTION("std::mismatch")
|
|
{
|
|
json j_array2 = {13, 29, 3, {{"one", 1}, {"two", 2}, {"three", 3}}, true, false, {1, 2, 3}, "foo", "baz"};
|
|
auto res = std::mismatch(j_array.begin(), j_array.end(), j_array2.begin());
|
|
CHECK(*res.first == json({{"one", 1}, {"two", 2}}));
|
|
CHECK(*res.second == json({{"one", 1}, {"two", 2}, {"three", 3}}));
|
|
}
|
|
|
|
SECTION("std::equal")
|
|
{
|
|
SECTION("using operator==")
|
|
{
|
|
CHECK(std::equal(j_array.begin(), j_array.end(), j_array.begin()));
|
|
CHECK(std::equal(j_object.begin(), j_object.end(), j_object.begin()));
|
|
CHECK(!std::equal(j_array.begin(), j_array.end(), j_object.begin()));
|
|
}
|
|
|
|
SECTION("using user-defined comparison")
|
|
{
|
|
// compare objects only by size of its elements
|
|
json j_array2 = {13, 29, 3, {"Hello", "World"}, true, false, {{"one", 1}, {"two", 2}, {"three", 3}}, "foo", "baz"};
|
|
CHECK(!std::equal(j_array.begin(), j_array.end(), j_array2.begin()));
|
|
CHECK(std::equal(j_array.begin(), j_array.end(), j_array2.begin(),
|
|
[](const json & a, const json & b)
|
|
{
|
|
return (a.size() == b.size());
|
|
}));
|
|
}
|
|
}
|
|
|
|
SECTION("std::find")
|
|
{
|
|
auto it = std::find(j_array.begin(), j_array.end(), json(false));
|
|
CHECK(std::distance(j_array.begin(), it) == 5);
|
|
}
|
|
|
|
SECTION("std::find_if")
|
|
{
|
|
auto it = std::find_if(j_array.begin(), j_array.end(),
|
|
[](const json & value)
|
|
{
|
|
return value.is_boolean();
|
|
});
|
|
CHECK(std::distance(j_array.begin(), it) == 4);
|
|
}
|
|
|
|
SECTION("std::find_if_not")
|
|
{
|
|
auto it = std::find_if_not(j_array.begin(), j_array.end(),
|
|
[](const json & value)
|
|
{
|
|
return value.is_number();
|
|
});
|
|
CHECK(std::distance(j_array.begin(), it) == 3);
|
|
}
|
|
|
|
SECTION("std::adjacent_find")
|
|
{
|
|
CHECK(std::adjacent_find(j_array.begin(), j_array.end()) == j_array.end());
|
|
CHECK(std::adjacent_find(j_array.begin(), j_array.end(),
|
|
[](const json & v1, const json & v2)
|
|
{
|
|
return v1.type() == v2.type();
|
|
}) == j_array.begin());
|
|
}
|
|
}
|
|
|
|
SECTION("modifying sequence operations")
|
|
{
|
|
SECTION("std::reverse")
|
|
{
|
|
std::reverse(j_array.begin(), j_array.end());
|
|
CHECK(j_array == json({"baz", "foo", {1, 2, 3}, false, true, {{"one", 1}, {"two", 2}}, 3, 29, 13}));
|
|
}
|
|
|
|
SECTION("std::rotate")
|
|
{
|
|
std::rotate(j_array.begin(), j_array.begin() + 1, j_array.end());
|
|
CHECK(j_array == json({29, 3, {{"one", 1}, {"two", 2}}, true, false, {1, 2, 3}, "foo", "baz", 13}));
|
|
}
|
|
|
|
SECTION("std::partition")
|
|
{
|
|
auto it = std::partition(j_array.begin(), j_array.end(), [](const json & v)
|
|
{
|
|
return v.is_string();
|
|
});
|
|
CHECK(std::distance(j_array.begin(), it) == 2);
|
|
CHECK(!it[2].is_string());
|
|
}
|
|
}
|
|
|
|
SECTION("sorting operations")
|
|
{
|
|
SECTION("std::sort")
|
|
{
|
|
SECTION("with standard comparison")
|
|
{
|
|
json j = {13, 29, 3, {{"one", 1}, {"two", 2}}, true, false, {1, 2, 3}, "foo", "baz", nullptr};
|
|
std::sort(j.begin(), j.end());
|
|
CHECK(j == json({nullptr, false, true, 3, 13, 29, {{"one", 1}, {"two", 2}}, {1, 2, 3}, "baz", "foo"}));
|
|
}
|
|
|
|
SECTION("with user-defined comparison")
|
|
{
|
|
json j = {3, {{"one", 1}, {"two", 2}}, {1, 2, 3}, nullptr};
|
|
std::sort(j.begin(), j.end(), [](const json & a, const json & b)
|
|
{
|
|
return a.size() < b.size();
|
|
});
|
|
CHECK(j == json({nullptr, 3, {{"one", 1}, {"two", 2}}, {1, 2, 3}}));
|
|
}
|
|
|
|
SECTION("sorting an object")
|
|
{
|
|
json j({{"one", 1}, {"two", 2}});
|
|
CHECK_THROWS_WITH_AS(std::sort(j.begin(), j.end()), "[json.exception.invalid_iterator.209] cannot use offsets with object iterators", json::invalid_iterator&);
|
|
}
|
|
}
|
|
|
|
SECTION("std::partial_sort")
|
|
{
|
|
json j = {13, 29, 3, {{"one", 1}, {"two", 2}}, true, false, {1, 2, 3}, "foo", "baz", nullptr};
|
|
std::partial_sort(j.begin(), j.begin() + 4, j.end());
|
|
// only the first four elements are expected to be sorted, the rest are
|
|
// unspecified by the standard
|
|
const json expected({nullptr, false, true, 3});
|
|
// std::equal below only bounds-checks the first range; assert the
|
|
// second range is at least as long to rule out an over-read (CWE-126)
|
|
CHECK(std::distance(begin(expected), end(expected)) >= 4);
|
|
CHECK(std::equal(j.begin(), j.begin() + 4, begin(expected)));
|
|
}
|
|
}
|
|
|
|
SECTION("set operations")
|
|
{
|
|
SECTION("std::merge")
|
|
{
|
|
{
|
|
json j1 = {2, 4, 6, 8};
|
|
json j2 = {1, 2, 3, 5, 7};
|
|
json j3;
|
|
|
|
std::merge(j1.begin(), j1.end(), j2.begin(), j2.end(), std::back_inserter(j3));
|
|
CHECK(j3 == json({1, 2, 2, 3, 4, 5, 6, 7, 8}));
|
|
}
|
|
}
|
|
|
|
SECTION("std::set_difference")
|
|
{
|
|
json j1 = {1, 2, 3, 4, 5, 6, 7, 8};
|
|
json j2 = {1, 2, 3, 5, 7};
|
|
json j3;
|
|
|
|
std::set_difference(j1.begin(), j1.end(), j2.begin(), j2.end(), std::back_inserter(j3));
|
|
CHECK(j3 == json({4, 6, 8}));
|
|
}
|
|
|
|
SECTION("std::set_intersection")
|
|
{
|
|
json j1 = {1, 2, 3, 4, 5, 6, 7, 8};
|
|
json j2 = {1, 2, 3, 5, 7};
|
|
json j3;
|
|
|
|
std::set_intersection(j1.begin(), j1.end(), j2.begin(), j2.end(), std::back_inserter(j3));
|
|
CHECK(j3 == json({1, 2, 3, 5, 7}));
|
|
}
|
|
|
|
SECTION("std::set_union")
|
|
{
|
|
json j1 = {2, 4, 6, 8};
|
|
json j2 = {1, 2, 3, 5, 7};
|
|
json j3;
|
|
|
|
std::set_union(j1.begin(), j1.end(), j2.begin(), j2.end(), std::back_inserter(j3));
|
|
CHECK(j3 == json({1, 2, 3, 4, 5, 6, 7, 8}));
|
|
}
|
|
|
|
SECTION("std::set_symmetric_difference")
|
|
{
|
|
json j1 = {2, 4, 6, 8};
|
|
json j2 = {1, 2, 3, 5, 7};
|
|
json j3;
|
|
|
|
std::set_symmetric_difference(j1.begin(), j1.end(), j2.begin(), j2.end(), std::back_inserter(j3));
|
|
CHECK(j3 == json({1, 3, 4, 5, 6, 7, 8}));
|
|
}
|
|
}
|
|
|
|
SECTION("heap operations")
|
|
{
|
|
std::make_heap(j_array.begin(), j_array.end());
|
|
CHECK(std::is_heap(j_array.begin(), j_array.end()));
|
|
std::sort_heap(j_array.begin(), j_array.end());
|
|
CHECK(j_array == json({false, true, 3, 13, 29, {{"one", 1}, {"two", 2}}, {1, 2, 3}, "baz", "foo"}));
|
|
}
|
|
|
|
SECTION("iota")
|
|
{
|
|
SECTION("int")
|
|
{
|
|
json json_arr = {0, 5, 2, 4, 10, 20, 30, 40, 50, 1};
|
|
std::iota(json_arr.begin(), json_arr.end(), 0);
|
|
CHECK(json_arr == json({0, 1, 2, 3, 4, 5, 6, 7, 8, 9}));
|
|
}
|
|
SECTION("double")
|
|
{
|
|
json json_arr = {0.5, 1.5, 1.3, 4.1, 10.2, 20.5, 30.6, 40.1, 50.22, 1.5};
|
|
std::iota(json_arr.begin(), json_arr.end(), 0.5);
|
|
CHECK(json_arr == json({0.5, 1.5, 2.5, 3.5, 4.5, 5.5, 6.5, 7.5, 8.5, 9.5}));
|
|
}
|
|
|
|
SECTION("char")
|
|
{
|
|
json json_arr = {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', '0', '1'};
|
|
std::iota(json_arr.begin(), json_arr.end(), '0');
|
|
CHECK(json_arr == json({'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'}));
|
|
}
|
|
}
|
|
|
|
SECTION("copy")
|
|
{
|
|
SECTION("copy without if")
|
|
{
|
|
json dest_arr;
|
|
const json source_arr = {1, 2, 3, 4};
|
|
|
|
std::copy(source_arr.begin(), source_arr.end(), std::back_inserter(dest_arr));
|
|
|
|
CHECK(dest_arr == source_arr);
|
|
}
|
|
SECTION("copy if")
|
|
{
|
|
json dest_arr;
|
|
const json source_arr = {0, 3, 6, 9, 12, 15, 20};
|
|
|
|
std::copy_if(source_arr.begin(), source_arr.end(), std::back_inserter(dest_arr), [](const json & _value)
|
|
{
|
|
return _value.get<int>() % 3 == 0;
|
|
});
|
|
CHECK(dest_arr == json({0, 3, 6, 9, 12, 15}));
|
|
}
|
|
SECTION("copy n")
|
|
{
|
|
const json source_arr = {0, 1, 2, 3, 4, 5, 6, 7};
|
|
json dest_arr;
|
|
const unsigned char numToCopy = 2;
|
|
|
|
std::copy_n(source_arr.begin(), numToCopy, std::back_inserter(dest_arr));
|
|
CHECK(dest_arr == json{0, 1});
|
|
|
|
}
|
|
SECTION("copy n chars")
|
|
{
|
|
const json source_arr = {'1', '2', '3', '4', '5', '6', '7'};
|
|
json dest_arr;
|
|
const unsigned char numToCopy = 4;
|
|
|
|
std::copy_n(source_arr.begin(), numToCopy, std::back_inserter(dest_arr));
|
|
CHECK(dest_arr == json{'1', '2', '3', '4'});
|
|
}
|
|
}
|
|
|
|
}
|