mirror of
https://github.com/nlohmann/json.git
synced 2026-10-16 03:07:12 +00:00
Make flatten() linear for ordered_json (#5834)
* Make flatten() linear for ordered_json Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Use priority_tag instead of a C-style variadic overload in add_flattened clang-tidy flags the ellipsis fallback (cert-dcl50-cpp, modernize-avoid-variadic-functions). Signed-off-by: Niels Lohmann <mail@nlohmann.me> --------- Signed-off-by: Niels Lohmann <mail@nlohmann.me>
This commit is contained in:
1 parent
a6e9785ab6
commit
0c4e7c103c
4 files changed
+129
-8
No files matched your search
@@ -24,6 +24,7 @@
|
|||||||
|
|
||||||
#include <nlohmann/detail/exceptions.hpp>
|
#include <nlohmann/detail/exceptions.hpp>
|
||||||
#include <nlohmann/detail/macro_scope.hpp>
|
#include <nlohmann/detail/macro_scope.hpp>
|
||||||
|
#include <nlohmann/detail/meta/cpp_future.hpp>
|
||||||
#include <nlohmann/detail/string_concat.hpp>
|
#include <nlohmann/detail/string_concat.hpp>
|
||||||
#include <nlohmann/detail/string_escape.hpp>
|
#include <nlohmann/detail/string_escape.hpp>
|
||||||
#include <nlohmann/detail/string_utils.hpp>
|
#include <nlohmann/detail/string_utils.hpp>
|
||||||
@@ -938,6 +939,23 @@ class json_pointer
|
|||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
// Add the entry (key, v) to the object `obj`. Flattened paths are pairwise
|
||||||
|
// distinct, so objects that can append a key known to be absent
|
||||||
|
// (ordered_map) skip the lookup, which is linear and would make flatten
|
||||||
|
// quadratic; all other object types use operator[].
|
||||||
|
template<typename ObjectType, typename ValueType>
|
||||||
|
static auto add_flattened(ObjectType& obj, const string_t& key, ValueType&& v, detail::priority_tag<1> /*unused*/)
|
||||||
|
-> decltype(obj.emplace_new_key(key, std::forward<ValueType>(v)), void())
|
||||||
|
{
|
||||||
|
obj.emplace_new_key(key, std::forward<ValueType>(v));
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename ObjectType, typename ValueType>
|
||||||
|
static void add_flattened(ObjectType& obj, const string_t& key, ValueType&& v, detail::priority_tag<0> /*unused*/)
|
||||||
|
{
|
||||||
|
obj[key] = std::forward<ValueType>(v);
|
||||||
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@param[in] reference_string the reference string to the current value
|
@param[in] reference_string the reference string to the current value
|
||||||
@param[in] value the value to consider
|
@param[in] value the value to consider
|
||||||
@@ -982,7 +1000,8 @@ class json_pointer
|
|||||||
|
|
||||||
// flatten `v`, whose path is `path`: primitives and empty containers
|
// flatten `v`, whose path is `path`: primitives and empty containers
|
||||||
// are added to the result right away; other containers get a frame
|
// are added to the result right away; other containers get a frame
|
||||||
const auto enter = [&stack, &path, &result](const BasicJsonType & v)
|
auto& object = *result.m_data.m_value.object;
|
||||||
|
const auto enter = [&stack, &path, &object](const BasicJsonType & v)
|
||||||
{
|
{
|
||||||
switch (v.type())
|
switch (v.type())
|
||||||
{
|
{
|
||||||
@@ -991,7 +1010,7 @@ class json_pointer
|
|||||||
if (v.m_data.m_value.array->empty())
|
if (v.m_data.m_value.array->empty())
|
||||||
{
|
{
|
||||||
// flatten empty array as null
|
// flatten empty array as null
|
||||||
result[path] = nullptr;
|
add_flattened(object, path, nullptr, detail::priority_tag<1> {});
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -1005,7 +1024,7 @@ class json_pointer
|
|||||||
if (v.m_data.m_value.object->empty())
|
if (v.m_data.m_value.object->empty())
|
||||||
{
|
{
|
||||||
// flatten empty object as null
|
// flatten empty object as null
|
||||||
result[path] = nullptr;
|
add_flattened(object, path, nullptr, detail::priority_tag<1> {});
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -1025,7 +1044,7 @@ class json_pointer
|
|||||||
default:
|
default:
|
||||||
{
|
{
|
||||||
// add a primitive value with its reference string
|
// add a primitive value with its reference string
|
||||||
result[path] = v;
|
add_flattened(object, path, v, detail::priority_tag<1> {});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1071,6 +1090,10 @@ class json_pointer
|
|||||||
enter(it->second);
|
enter(it->second);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// the entries were added without going through basic_json, so the
|
||||||
|
// parent pointers (JSON_DIAGNOSTICS) have to be set now
|
||||||
|
result.set_parents();
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
|
|||||||
@@ -143,6 +143,17 @@ public:
|
|||||||
return {std::prev(this->end()), true};
|
return {std::prev(this->end()), true};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// @brief add an element at the end without checking whether the key exists
|
||||||
|
/// @note The caller must guarantee that @a key is not yet contained, otherwise
|
||||||
|
/// the container holds duplicate keys. This avoids the linear search of
|
||||||
|
/// emplace() and operator[] when many keys known to be new are added.
|
||||||
|
template<class V, detail::enable_if_t<
|
||||||
|
detail::is_constructible<T, V>::value, int> = 0>
|
||||||
|
void emplace_new_key(const key_type& key, V && t)
|
||||||
|
{
|
||||||
|
append(key, std::forward<V>(t));
|
||||||
|
}
|
||||||
|
|
||||||
T& operator[](const key_type& key)
|
T& operator[](const key_type& key)
|
||||||
{
|
{
|
||||||
return emplace(key, T{}).first->second;
|
return emplace(key, T{}).first->second;
|
||||||
|
|||||||
Generated
+39
-4
@@ -20644,6 +20644,8 @@ NLOHMANN_JSON_NAMESPACE_END
|
|||||||
|
|
||||||
// #include <nlohmann/detail/macro_scope.hpp>
|
// #include <nlohmann/detail/macro_scope.hpp>
|
||||||
|
|
||||||
|
// #include <nlohmann/detail/meta/cpp_future.hpp>
|
||||||
|
|
||||||
// #include <nlohmann/detail/string_concat.hpp>
|
// #include <nlohmann/detail/string_concat.hpp>
|
||||||
|
|
||||||
// #include <nlohmann/detail/string_escape.hpp>
|
// #include <nlohmann/detail/string_escape.hpp>
|
||||||
@@ -21562,6 +21564,23 @@ class json_pointer
|
|||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
// Add the entry (key, v) to the object `obj`. Flattened paths are pairwise
|
||||||
|
// distinct, so objects that can append a key known to be absent
|
||||||
|
// (ordered_map) skip the lookup, which is linear and would make flatten
|
||||||
|
// quadratic; all other object types use operator[].
|
||||||
|
template<typename ObjectType, typename ValueType>
|
||||||
|
static auto add_flattened(ObjectType& obj, const string_t& key, ValueType&& v, detail::priority_tag<1> /*unused*/)
|
||||||
|
-> decltype(obj.emplace_new_key(key, std::forward<ValueType>(v)), void())
|
||||||
|
{
|
||||||
|
obj.emplace_new_key(key, std::forward<ValueType>(v));
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename ObjectType, typename ValueType>
|
||||||
|
static void add_flattened(ObjectType& obj, const string_t& key, ValueType&& v, detail::priority_tag<0> /*unused*/)
|
||||||
|
{
|
||||||
|
obj[key] = std::forward<ValueType>(v);
|
||||||
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@param[in] reference_string the reference string to the current value
|
@param[in] reference_string the reference string to the current value
|
||||||
@param[in] value the value to consider
|
@param[in] value the value to consider
|
||||||
@@ -21606,7 +21625,8 @@ class json_pointer
|
|||||||
|
|
||||||
// flatten `v`, whose path is `path`: primitives and empty containers
|
// flatten `v`, whose path is `path`: primitives and empty containers
|
||||||
// are added to the result right away; other containers get a frame
|
// are added to the result right away; other containers get a frame
|
||||||
const auto enter = [&stack, &path, &result](const BasicJsonType & v)
|
auto& object = *result.m_data.m_value.object;
|
||||||
|
const auto enter = [&stack, &path, &object](const BasicJsonType & v)
|
||||||
{
|
{
|
||||||
switch (v.type())
|
switch (v.type())
|
||||||
{
|
{
|
||||||
@@ -21615,7 +21635,7 @@ class json_pointer
|
|||||||
if (v.m_data.m_value.array->empty())
|
if (v.m_data.m_value.array->empty())
|
||||||
{
|
{
|
||||||
// flatten empty array as null
|
// flatten empty array as null
|
||||||
result[path] = nullptr;
|
add_flattened(object, path, nullptr, detail::priority_tag<1> {});
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -21629,7 +21649,7 @@ class json_pointer
|
|||||||
if (v.m_data.m_value.object->empty())
|
if (v.m_data.m_value.object->empty())
|
||||||
{
|
{
|
||||||
// flatten empty object as null
|
// flatten empty object as null
|
||||||
result[path] = nullptr;
|
add_flattened(object, path, nullptr, detail::priority_tag<1> {});
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -21649,7 +21669,7 @@ class json_pointer
|
|||||||
default:
|
default:
|
||||||
{
|
{
|
||||||
// add a primitive value with its reference string
|
// add a primitive value with its reference string
|
||||||
result[path] = v;
|
add_flattened(object, path, v, detail::priority_tag<1> {});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -21695,6 +21715,10 @@ class json_pointer
|
|||||||
enter(it->second);
|
enter(it->second);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// the entries were added without going through basic_json, so the
|
||||||
|
// parent pointers (JSON_DIAGNOSTICS) have to be set now
|
||||||
|
result.set_parents();
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@@ -29161,6 +29185,17 @@ public:
|
|||||||
return {std::prev(this->end()), true};
|
return {std::prev(this->end()), true};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// @brief add an element at the end without checking whether the key exists
|
||||||
|
/// @note The caller must guarantee that @a key is not yet contained, otherwise
|
||||||
|
/// the container holds duplicate keys. This avoids the linear search of
|
||||||
|
/// emplace() and operator[] when many keys known to be new are added.
|
||||||
|
template<class V, detail::enable_if_t<
|
||||||
|
detail::is_constructible<T, V>::value, int> = 0>
|
||||||
|
void emplace_new_key(const key_type& key, V && t)
|
||||||
|
{
|
||||||
|
append(key, std::forward<V>(t));
|
||||||
|
}
|
||||||
|
|
||||||
T& operator[](const key_type& key)
|
T& operator[](const key_type& key)
|
||||||
{
|
{
|
||||||
return emplace(key, T{}).first->second;
|
return emplace(key, T{}).first->second;
|
||||||
|
|||||||
@@ -24,6 +24,8 @@ using nlohmann::json;
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include <map>
|
#include <map>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
|
||||||
TEST_CASE("JSON pointers")
|
TEST_CASE("JSON pointers")
|
||||||
@@ -1071,3 +1073,53 @@ TEST_CASE("flatten of structured values")
|
|||||||
R"({"/z":"s","/a~1b/~0":1,"/a~1b/k":null,"/a/0":1,"/a/1/c":null,"/a/1/b":null,"/a/2/0/x~0~1/0":true,"/a/2/0/x~0~1/1":null,"/a/2/0/w":2})");
|
R"({"/z":"s","/a~1b/~0":1,"/a~1b/k":null,"/a/0":1,"/a/1/c":null,"/a/1/b":null,"/a/2/0/x~0~1/0":true,"/a/2/0/x~0~1/1":null,"/a/2/0/w":2})");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("flatten of ordered_json")
|
||||||
|
{
|
||||||
|
using nlohmann::ordered_json;
|
||||||
|
|
||||||
|
SECTION("keeps the document order of the leaves")
|
||||||
|
{
|
||||||
|
const ordered_json j = ordered_json::parse(R"({
|
||||||
|
"z": 1, "a": {"y": [true, null, {}], "b": "x", "": []},
|
||||||
|
"m~n": 2.5, "o/p": {"k": {}}, "0": [[1], [2, 3]]
|
||||||
|
})");
|
||||||
|
|
||||||
|
const ordered_json flat = j.flatten();
|
||||||
|
std::vector<std::string> keys;
|
||||||
|
for (const auto& el : flat.items())
|
||||||
|
{
|
||||||
|
keys.push_back(el.key());
|
||||||
|
}
|
||||||
|
const std::vector<std::string> expected =
|
||||||
|
{
|
||||||
|
"/z", "/a/y/0", "/a/y/1", "/a/y/2", "/a/b", "/a/", "/m~0n", "/o~1p/k", "/0/0/0", "/0/1/0", "/0/1/1"
|
||||||
|
};
|
||||||
|
CHECK(keys == expected);
|
||||||
|
CHECK(flat.size() == expected.size());
|
||||||
|
CHECK(flat["/m~0n"] == 2.5);
|
||||||
|
CHECK(flat["/a/y/2"].is_null());
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("round-trips and agrees with json")
|
||||||
|
{
|
||||||
|
ordered_json j;
|
||||||
|
for (int i = 0; i < 200; ++i)
|
||||||
|
{
|
||||||
|
j["k" + std::to_string(i)] = {{"v", i}, {"l", {i, i + 1, "s"}}};
|
||||||
|
}
|
||||||
|
const ordered_json flat = j.flatten();
|
||||||
|
CHECK(flat.size() == 800);
|
||||||
|
CHECK(flat.unflatten() == j);
|
||||||
|
|
||||||
|
// same entries as for a sorted object
|
||||||
|
const nlohmann::json plain = nlohmann::json::parse(j.dump());
|
||||||
|
CHECK(nlohmann::json::parse(flat.dump()) == plain.flatten());
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("primitive and empty root")
|
||||||
|
{
|
||||||
|
CHECK(ordered_json(1).flatten() == ordered_json({{"", 1}}));
|
||||||
|
CHECK(ordered_json::array().flatten() == ordered_json({{"", nullptr}}));
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user