Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ad31938c71 | ||
|
|
908038c027 |
No files matched your search
@@ -674,16 +674,19 @@ class json_sax_dom_callback_parser
|
|||||||
if (keep && ref_stack.back())
|
if (keep && ref_stack.back())
|
||||||
{
|
{
|
||||||
auto& obj = *ref_stack.back()->m_data.m_value.object;
|
auto& obj = *ref_stack.back()->m_data.m_value.object;
|
||||||
const auto it = obj.find(val);
|
// a single lookup: insert the placeholder, or find the existing member
|
||||||
if (it != obj.end())
|
const auto inserted = obj.emplace(val, discarded);
|
||||||
|
const auto it = inserted.first;
|
||||||
|
if (!inserted.second)
|
||||||
{
|
{
|
||||||
// this is a duplicate key (legal in JSON); remember its
|
// this is a duplicate key (legal in JSON); remember its
|
||||||
// current value so it can be restored later if the new
|
// current value so it can be restored later if the new
|
||||||
// value is rejected by the callback, instead of being
|
// value is rejected by the callback, instead of being
|
||||||
// erased together with the discarded placeholder
|
// erased together with the discarded placeholder
|
||||||
duplicate_key_stash.emplace_back(&(it->second), it->second);
|
duplicate_key_stash.emplace_back(&(it->second), it->second);
|
||||||
|
it->second = discarded;
|
||||||
}
|
}
|
||||||
object_element = &(obj[val] = discarded);
|
object_element = &(it->second);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -938,6 +938,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, int /*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, ...)
|
||||||
|
{
|
||||||
|
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 +999,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 +1009,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, 0);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -1005,7 +1023,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, 0);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -1025,7 +1043,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, 0);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1071,6 +1089,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
+43
-7
@@ -13850,16 +13850,19 @@ class json_sax_dom_callback_parser
|
|||||||
if (keep && ref_stack.back())
|
if (keep && ref_stack.back())
|
||||||
{
|
{
|
||||||
auto& obj = *ref_stack.back()->m_data.m_value.object;
|
auto& obj = *ref_stack.back()->m_data.m_value.object;
|
||||||
const auto it = obj.find(val);
|
// a single lookup: insert the placeholder, or find the existing member
|
||||||
if (it != obj.end())
|
const auto inserted = obj.emplace(val, discarded);
|
||||||
|
const auto it = inserted.first;
|
||||||
|
if (!inserted.second)
|
||||||
{
|
{
|
||||||
// this is a duplicate key (legal in JSON); remember its
|
// this is a duplicate key (legal in JSON); remember its
|
||||||
// current value so it can be restored later if the new
|
// current value so it can be restored later if the new
|
||||||
// value is rejected by the callback, instead of being
|
// value is rejected by the callback, instead of being
|
||||||
// erased together with the discarded placeholder
|
// erased together with the discarded placeholder
|
||||||
duplicate_key_stash.emplace_back(&(it->second), it->second);
|
duplicate_key_stash.emplace_back(&(it->second), it->second);
|
||||||
|
it->second = discarded;
|
||||||
}
|
}
|
||||||
object_element = &(obj[val] = discarded);
|
object_element = &(it->second);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -21559,6 +21562,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, int /*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, ...)
|
||||||
|
{
|
||||||
|
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
|
||||||
@@ -21603,7 +21623,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())
|
||||||
{
|
{
|
||||||
@@ -21612,7 +21633,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, 0);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -21626,7 +21647,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, 0);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -21646,7 +21667,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, 0);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -21692,6 +21713,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();
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@@ -29158,6 +29183,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