mirror of
https://github.com/nlohmann/json.git
synced 2026-10-09 16:07:14 +00:00
Compare commits
5
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2913e96433 | ||
|
|
44e8597701 | ||
|
|
374dfe4f0f | ||
|
|
d540750f21 | ||
|
|
69a0c1b82c |
No files matched your search
@@ -64,18 +64,7 @@ values of that type directly to a `basic_json` instance, and they will automatic
|
|||||||
rather than arrays:
|
rather than arrays:
|
||||||
|
|
||||||
```cpp
|
```cpp
|
||||||
using custom_json = nlohmann::basic_json<
|
using custom_json = nlohmann::ordered_json::with_binary_t<std::vector<std::byte>>;
|
||||||
nlohmann::ordered_map, // ObjectType
|
|
||||||
std::vector, // ArrayType
|
|
||||||
std::string, // StringType
|
|
||||||
bool, // BooleanType
|
|
||||||
std::int64_t, // NumberIntegerType
|
|
||||||
std::uint64_t, // NumberUnsignedType
|
|
||||||
double, // NumberFloatType
|
|
||||||
std::allocator, // AllocatorType
|
|
||||||
nlohmann::adl_serializer,
|
|
||||||
std::vector<std::byte> // Custom BinaryType
|
|
||||||
>;
|
|
||||||
|
|
||||||
std::vector<std::byte> data{std::byte{1}, std::byte{2}, std::byte{3}};
|
std::vector<std::byte> data{std::byte{1}, std::byte{2}, std::byte{3}};
|
||||||
custom_json j = data; // Creates a binary value, not an array
|
custom_json j = data; // Creates a binary value, not an array
|
||||||
|
|||||||
@@ -26,7 +26,8 @@ To store objects in C++, a type is defined by the template parameters described
|
|||||||
|
|
||||||
`StringType`
|
`StringType`
|
||||||
: the type of the keys or names (e.g., `std::string`). The comparison function `std::less<StringType>` is used to
|
: the type of the keys or names (e.g., `std::string`). The comparison function `std::less<StringType>` is used to
|
||||||
order elements inside the container.
|
order elements inside the container. `object_t::key_type` must be implicitly convertible to `string_t` (required by the
|
||||||
|
binary formats).
|
||||||
|
|
||||||
`AllocatorType`
|
`AllocatorType`
|
||||||
: the allocator to use for objects (e.g., `std::allocator`)
|
: the allocator to use for objects (e.g., `std::allocator`)
|
||||||
|
|||||||
@@ -15,19 +15,7 @@ class base_class_with_hidden_members
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
using json = nlohmann::basic_json <
|
using json = nlohmann::json::with_base_class_t<base_class_with_hidden_members>;
|
||||||
std::map,
|
|
||||||
std::vector,
|
|
||||||
std::string,
|
|
||||||
bool,
|
|
||||||
std::int64_t,
|
|
||||||
std::uint64_t,
|
|
||||||
double,
|
|
||||||
std::allocator,
|
|
||||||
nlohmann::adl_serializer,
|
|
||||||
std::vector<std::uint8_t>,
|
|
||||||
base_class_with_hidden_members
|
|
||||||
>;
|
|
||||||
|
|
||||||
int main()
|
int main()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -16,8 +16,8 @@
|
|||||||
#include <iosfwd> // ostream
|
#include <iosfwd> // ostream
|
||||||
#endif // JSON_NO_IO
|
#endif // JSON_NO_IO
|
||||||
#include <limits> // max
|
#include <limits> // max
|
||||||
|
#include <map> // map
|
||||||
#include <numeric> // accumulate
|
#include <numeric> // accumulate
|
||||||
#include <set> // set
|
|
||||||
#include <string> // string
|
#include <string> // string
|
||||||
#include <utility> // move
|
#include <utility> // move
|
||||||
#include <vector> // vector
|
#include <vector> // vector
|
||||||
@@ -359,33 +359,82 @@ class json_pointer
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
/*!
|
/*!
|
||||||
@brief the reference token sequences that denote arrays
|
@brief the pointer prefixes of a flattened object, and which of them denote arrays
|
||||||
|
|
||||||
@ref unflatten collects the pointer prefixes that have a reference token 0
|
@ref unflatten collects the pointer prefixes that have a reference token 0
|
||||||
among their children; @ref get_and_create creates arrays exactly below
|
among their children; @ref get_and_create creates arrays exactly below
|
||||||
those prefixes and objects everywhere else. Deciding this up front keeps
|
those prefixes and objects everywhere else. Deciding this up front keeps
|
||||||
the result independent of the order in which the flattened object is
|
the result independent of the order in which the flattened object is
|
||||||
iterated, which is unspecified for some object types.
|
iterated, which is unspecified for some object types.
|
||||||
|
|
||||||
|
The prefixes form a tree and are numbered, so each of them is stored only
|
||||||
|
once (as a node) rather than as a copy of all of its reference tokens.
|
||||||
*/
|
*/
|
||||||
using array_parents_t = std::set<std::vector<string_t>>;
|
struct prefix_tree
|
||||||
|
{
|
||||||
|
// children[id] maps a reference token to the number of the prefix
|
||||||
|
// extended by that token; number 0 is the empty prefix
|
||||||
|
std::vector<std::map<string_t, std::size_t>> children;
|
||||||
|
// is_array[id] is true iff some flattened key has the reference token
|
||||||
|
// 0 directly below the prefix with number id
|
||||||
|
std::vector<bool> is_array;
|
||||||
|
|
||||||
|
// start with the empty prefix only
|
||||||
|
prefix_tree()
|
||||||
|
: children(1)
|
||||||
|
, is_array(1, false)
|
||||||
|
{}
|
||||||
|
|
||||||
|
// return the number of the prefix with number id extended by
|
||||||
|
// reference_token, adding it if it is new
|
||||||
|
std::size_t add_child(std::size_t id, string_t&& reference_token)
|
||||||
|
{
|
||||||
|
if (reference_token == "0")
|
||||||
|
{
|
||||||
|
is_array[id] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// read the number before the emplace_back below, which may
|
||||||
|
// reallocate children and invalidate the iterator
|
||||||
|
const std::size_t next = children.size();
|
||||||
|
const auto inserted = children[id].emplace(std::move(reference_token), next);
|
||||||
|
const std::size_t child = inserted.first->second;
|
||||||
|
if (inserted.second)
|
||||||
|
{
|
||||||
|
children.emplace_back();
|
||||||
|
is_array.push_back(false);
|
||||||
|
}
|
||||||
|
return child;
|
||||||
|
}
|
||||||
|
|
||||||
|
// return the number of the prefix with number id extended by
|
||||||
|
// reference_token, which must have been added before
|
||||||
|
std::size_t find_child(std::size_t id, const string_t& reference_token) const
|
||||||
|
{
|
||||||
|
const auto it = children[id].find(reference_token);
|
||||||
|
JSON_ASSERT(it != children[id].end());
|
||||||
|
return it->second;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@brief create and return a reference to the pointed to value
|
@brief create and return a reference to the pointed to value
|
||||||
|
|
||||||
Complexity: Linear in the number of reference tokens.
|
Complexity: Linear in the number of reference tokens (times the logarithm
|
||||||
|
of the number of siblings for the prefix lookup).
|
||||||
|
|
||||||
@throw parse_error.106 if an array index begins with '0'
|
@throw parse_error.106 if an array index begins with '0'
|
||||||
@throw parse_error.109 if array index is not a number
|
@throw parse_error.109 if array index is not a number
|
||||||
@throw type_error.313 if value cannot be unflattened
|
@throw type_error.313 if value cannot be unflattened
|
||||||
*/
|
*/
|
||||||
template<typename BasicJsonType>
|
template<typename BasicJsonType>
|
||||||
BasicJsonType& get_and_create(BasicJsonType& j, const array_parents_t& array_parents) const
|
BasicJsonType& get_and_create(BasicJsonType& j, const prefix_tree& tree) const
|
||||||
{
|
{
|
||||||
auto* result = &j;
|
auto* result = &j;
|
||||||
|
|
||||||
// the reference tokens that have been consumed so far; used to look up
|
// the number of the prefix consumed so far; used to look up whether
|
||||||
// whether the value to be created below is an array or an object
|
// the value to be created below is an array or an object
|
||||||
std::vector<string_t> prefix;
|
std::size_t id = 0;
|
||||||
|
|
||||||
// in case no reference tokens exist, return a reference to the JSON value
|
// in case no reference tokens exist, return a reference to the JSON value
|
||||||
// j which will be overwritten by a primitive value
|
// j which will be overwritten by a primitive value
|
||||||
@@ -395,7 +444,7 @@ class json_pointer
|
|||||||
{
|
{
|
||||||
case detail::value_t::null:
|
case detail::value_t::null:
|
||||||
{
|
{
|
||||||
if (array_parents.find(prefix) != array_parents.end())
|
if (tree.is_array[id])
|
||||||
{
|
{
|
||||||
// some reference token below this position is 0, so the
|
// some reference token below this position is 0, so the
|
||||||
// value is an array
|
// value is an array
|
||||||
@@ -440,7 +489,7 @@ class json_pointer
|
|||||||
JSON_THROW(detail::type_error::create(313, "invalid value to unflatten", &j));
|
JSON_THROW(detail::type_error::create(313, "invalid value to unflatten", &j));
|
||||||
}
|
}
|
||||||
|
|
||||||
prefix.push_back(reference_token);
|
id = tree.find_child(id, reference_token);
|
||||||
}
|
}
|
||||||
|
|
||||||
return *result;
|
return *result;
|
||||||
@@ -878,49 +927,74 @@ class json_pointer
|
|||||||
@param[in,out] result the result object to insert values to
|
@param[in,out] result the result object to insert values to
|
||||||
|
|
||||||
@note Empty objects or arrays are flattened to `null`.
|
@note Empty objects or arrays are flattened to `null`.
|
||||||
|
|
||||||
|
The value is walked with an explicit stack rather than the call stack, so
|
||||||
|
arbitrarily deeply nested values can be flattened.
|
||||||
|
|
||||||
|
@sa https://github.com/nlohmann/json/issues/5393
|
||||||
*/
|
*/
|
||||||
template<typename BasicJsonType>
|
template<typename BasicJsonType>
|
||||||
static void flatten(const string_t& reference_string,
|
static void flatten(const string_t& reference_string,
|
||||||
const BasicJsonType& value,
|
const BasicJsonType& value,
|
||||||
BasicJsonType& result)
|
BasicJsonType& result)
|
||||||
{
|
{
|
||||||
switch (value.type())
|
using object_const_iterator = typename BasicJsonType::object_t::const_iterator;
|
||||||
|
|
||||||
|
// an array or object being walked: the container, the array index or
|
||||||
|
// object iterator of the next child, and the length of the path of the
|
||||||
|
// container itself
|
||||||
|
struct frame
|
||||||
|
{
|
||||||
|
frame(const BasicJsonType* container_, object_const_iterator member_, const std::size_t path_length_) noexcept
|
||||||
|
: container(container_), member(std::move(member_)), path_length(path_length_)
|
||||||
|
{}
|
||||||
|
|
||||||
|
const BasicJsonType* container;
|
||||||
|
std::size_t index = 0;
|
||||||
|
object_const_iterator member;
|
||||||
|
std::size_t path_length;
|
||||||
|
};
|
||||||
|
|
||||||
|
// The containers being flattened are kept on an explicit stack, and
|
||||||
|
// every child is flattened completely before the next one, so the
|
||||||
|
// entries come out in the same order as with a recursive walk. The
|
||||||
|
// path of the value being flattened is kept in one buffer that grows
|
||||||
|
// and shrinks with the stack, rather than in a new string per level.
|
||||||
|
std::vector<frame> stack;
|
||||||
|
string_t path = reference_string;
|
||||||
|
|
||||||
|
// flatten `v`, whose path is `path`: primitives and empty containers
|
||||||
|
// are added to the result right away; other containers get a frame
|
||||||
|
const auto enter = [&stack, &path, &result](const BasicJsonType & v)
|
||||||
|
{
|
||||||
|
switch (v.type())
|
||||||
{
|
{
|
||||||
case detail::value_t::array:
|
case detail::value_t::array:
|
||||||
{
|
{
|
||||||
if (value.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[reference_string] = nullptr;
|
result[path] = nullptr;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// iterate array and use index as a reference string
|
stack.emplace_back(&v, object_const_iterator(), path.size());
|
||||||
for (std::size_t i = 0; i < value.m_data.m_value.array->size(); ++i)
|
|
||||||
{
|
|
||||||
flatten(detail::concat<string_t>(reference_string, '/', std::to_string(i)),
|
|
||||||
value.m_data.m_value.array->operator[](i), result);
|
|
||||||
}
|
}
|
||||||
}
|
return;
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
case detail::value_t::object:
|
case detail::value_t::object:
|
||||||
{
|
{
|
||||||
if (value.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[reference_string] = nullptr;
|
result[path] = nullptr;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// iterate object and use keys as reference string
|
stack.emplace_back(&v, v.m_data.m_value.object->begin(), path.size());
|
||||||
for (const auto& element : *value.m_data.m_value.object)
|
|
||||||
{
|
|
||||||
flatten(detail::concat<string_t>(reference_string, '/', detail::escape(element.first)), element.second, result);
|
|
||||||
}
|
}
|
||||||
}
|
return;
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
case detail::value_t::null:
|
case detail::value_t::null:
|
||||||
@@ -934,8 +1008,50 @@ class json_pointer
|
|||||||
default:
|
default:
|
||||||
{
|
{
|
||||||
// add a primitive value with its reference string
|
// add a primitive value with its reference string
|
||||||
result[reference_string] = value;
|
result[path] = v;
|
||||||
break;
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
enter(value);
|
||||||
|
while (!stack.empty())
|
||||||
|
{
|
||||||
|
// the frame is changed through stack.back(): enter() may push a
|
||||||
|
// frame, which would invalidate a reference to it
|
||||||
|
const BasicJsonType* const container = stack.back().container;
|
||||||
|
|
||||||
|
// drop the path of the previous child
|
||||||
|
path.resize(stack.back().path_length);
|
||||||
|
|
||||||
|
if (container->is_array())
|
||||||
|
{
|
||||||
|
const auto& array = *container->m_data.m_value.array;
|
||||||
|
const std::size_t i = stack.back().index;
|
||||||
|
if (i == array.size())
|
||||||
|
{
|
||||||
|
stack.pop_back();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// iterate array and use index as a reference string
|
||||||
|
++stack.back().index;
|
||||||
|
detail::concat_into(path, '/', detail::to_string<string_t>(i));
|
||||||
|
enter(array[i]);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
const object_const_iterator it = stack.back().member;
|
||||||
|
if (it == container->m_data.m_value.object->end())
|
||||||
|
{
|
||||||
|
stack.pop_back();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// iterate object and use keys as reference string
|
||||||
|
++stack.back().member;
|
||||||
|
detail::concat_into(path, '/', detail::escape(it->first));
|
||||||
|
enter(it->second);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -963,19 +1079,15 @@ class json_pointer
|
|||||||
|
|
||||||
// collect the pointer prefixes that have a reference token 0 among
|
// collect the pointer prefixes that have a reference token 0 among
|
||||||
// their children; the values below them are arrays, all others are
|
// their children; the values below them are arrays, all others are
|
||||||
// objects (see array_parents_t)
|
// objects (see prefix_tree)
|
||||||
array_parents_t array_parents;
|
prefix_tree tree;
|
||||||
for (const auto& element : *value.m_data.m_value.object)
|
for (const auto& element : *value.m_data.m_value.object)
|
||||||
{
|
{
|
||||||
json_pointer ptr(element.first);
|
json_pointer ptr(element.first);
|
||||||
std::vector<string_t> prefix;
|
std::size_t id = 0;
|
||||||
for (auto& reference_token : ptr.reference_tokens)
|
for (auto& reference_token : ptr.reference_tokens)
|
||||||
{
|
{
|
||||||
if (reference_token == "0")
|
id = tree.add_child(id, std::move(reference_token));
|
||||||
{
|
|
||||||
array_parents.insert(prefix);
|
|
||||||
}
|
|
||||||
prefix.push_back(std::move(reference_token));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -991,7 +1103,7 @@ class json_pointer
|
|||||||
// that if the JSON pointer is "" (i.e., points to the whole value),
|
// that if the JSON pointer is "" (i.e., points to the whole value),
|
||||||
// function get_and_create returns a reference to the result itself.
|
// function get_and_create returns a reference to the result itself.
|
||||||
// An assignment will then create a primitive value.
|
// An assignment will then create a primitive value.
|
||||||
json_pointer(element.first).get_and_create(result, array_parents) = element.second;
|
json_pointer(element.first).get_and_create(result, tree) = element.second;
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
|||||||
@@ -85,6 +85,12 @@ template<typename BasicJsonType, typename CharType, typename OutputSinkType = ou
|
|||||||
class binary_writer
|
class binary_writer
|
||||||
{
|
{
|
||||||
using string_t = typename BasicJsonType::string_t;
|
using string_t = typename BasicJsonType::string_t;
|
||||||
|
|
||||||
|
/// an object key as string_t: a reference when object_t::key_type already is
|
||||||
|
/// string_t, otherwise a converted copy that outlives sanitize_utf8_for_write's result
|
||||||
|
using object_key_string_t = typename std::conditional <
|
||||||
|
std::is_same<typename BasicJsonType::object_t::key_type, string_t>::value,
|
||||||
|
const string_t&, string_t >::type;
|
||||||
using binary_t = typename BasicJsonType::binary_t;
|
using binary_t = typename BasicJsonType::binary_t;
|
||||||
using number_float_t = typename BasicJsonType::number_float_t;
|
using number_float_t = typename BasicJsonType::number_float_t;
|
||||||
|
|
||||||
@@ -244,16 +250,7 @@ class binary_writer
|
|||||||
|
|
||||||
case value_t::string:
|
case value_t::string:
|
||||||
{
|
{
|
||||||
string_t storage;
|
write_cbor_string(*j.m_data.m_value.string, j);
|
||||||
const string_t& value = sanitize_utf8_for_write(*j.m_data.m_value.string, j, storage);
|
|
||||||
|
|
||||||
// step 1: write control byte and the string length
|
|
||||||
write_cbor_head(0x60, value.size());
|
|
||||||
|
|
||||||
// step 2: write the string
|
|
||||||
oa.write_characters(
|
|
||||||
reinterpret_cast<const CharType*>(value.data()),
|
|
||||||
value.size());
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -316,23 +313,20 @@ class binary_writer
|
|||||||
|
|
||||||
case value_t::object:
|
case value_t::object:
|
||||||
{
|
{
|
||||||
|
static_assert(
|
||||||
|
std::is_convertible <
|
||||||
|
typename BasicJsonType::object_t::key_type,
|
||||||
|
string_t >::value,
|
||||||
|
"object_t::key_type must be implicitly convertible to string_t");
|
||||||
|
|
||||||
// step 1: write control byte and the object size
|
// step 1: write control byte and the object size
|
||||||
write_cbor_head(0xA0, j.m_data.m_value.object->size());
|
write_cbor_head(0xA0, j.m_data.m_value.object->size());
|
||||||
|
|
||||||
for (const auto& el : *j.m_data.m_value.object)
|
for (const auto& el : *j.m_data.m_value.object)
|
||||||
{
|
{
|
||||||
// el.first is checked here, against the object as
|
// el.first is written directly (not via a temporary
|
||||||
// diagnostics context, because write_cbor(el.first)
|
// basic_json), with the object as diagnostics context
|
||||||
// converts it to a temporary basic_json that would be
|
write_cbor_string(el.first, j);
|
||||||
// used as the context instead; for error_handler_t::keep
|
|
||||||
// and ::replace/::ignore the recursive write_cbor(el.first)
|
|
||||||
// call below handles the key like any other string, so no
|
|
||||||
// separate check is needed here for those
|
|
||||||
if (error_handler == error_handler_t::strict)
|
|
||||||
{
|
|
||||||
check_utf8(el.first, j);
|
|
||||||
}
|
|
||||||
write_cbor(el.first);
|
|
||||||
write_cbor(el.second, depth + 1);
|
write_cbor(el.second, depth + 1);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -491,39 +485,7 @@ class binary_writer
|
|||||||
|
|
||||||
case value_t::string:
|
case value_t::string:
|
||||||
{
|
{
|
||||||
string_t storage;
|
write_msgpack_string(*j.m_data.m_value.string, j);
|
||||||
const string_t& value = sanitize_utf8_for_write(*j.m_data.m_value.string, j, storage);
|
|
||||||
|
|
||||||
// step 1: write control byte and the string length
|
|
||||||
const auto N = to_msgpack_length(value.size(), j);
|
|
||||||
if (N <= 31)
|
|
||||||
{
|
|
||||||
// fixstr
|
|
||||||
write_number(static_cast<std::uint8_t>(0xA0 | N));
|
|
||||||
}
|
|
||||||
else if (N <= (std::numeric_limits<std::uint8_t>::max)())
|
|
||||||
{
|
|
||||||
// str 8
|
|
||||||
oa.write_character(to_char_type(0xD9));
|
|
||||||
write_number(static_cast<std::uint8_t>(N));
|
|
||||||
}
|
|
||||||
else if (N <= (std::numeric_limits<std::uint16_t>::max)())
|
|
||||||
{
|
|
||||||
// str 16
|
|
||||||
oa.write_character(to_char_type(0xDA));
|
|
||||||
write_number(static_cast<std::uint16_t>(N));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// str 32
|
|
||||||
oa.write_character(to_char_type(0xDB));
|
|
||||||
write_number(static_cast<std::uint32_t>(N));
|
|
||||||
}
|
|
||||||
|
|
||||||
// step 2: write the string
|
|
||||||
oa.write_characters(
|
|
||||||
reinterpret_cast<const CharType*>(value.data()),
|
|
||||||
value.size());
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -629,19 +591,20 @@ class binary_writer
|
|||||||
|
|
||||||
case value_t::object:
|
case value_t::object:
|
||||||
{
|
{
|
||||||
|
static_assert(
|
||||||
|
std::is_convertible <
|
||||||
|
typename BasicJsonType::object_t::key_type,
|
||||||
|
string_t >::value,
|
||||||
|
"object_t::key_type must be implicitly convertible to string_t");
|
||||||
|
|
||||||
// step 1: write control byte and the object size
|
// step 1: write control byte and the object size
|
||||||
write_msgpack_object_prefix(j.m_data.m_value.object->size(), j);
|
write_msgpack_object_prefix(j.m_data.m_value.object->size(), j);
|
||||||
|
|
||||||
for (const auto& el : *j.m_data.m_value.object)
|
for (const auto& el : *j.m_data.m_value.object)
|
||||||
{
|
{
|
||||||
// as in write_cbor, el.first is checked here against the
|
// as in write_cbor, el.first is written directly with the
|
||||||
// object as diagnostics context; the recursive call below
|
// object as diagnostics context
|
||||||
// handles keep/replace/ignore like any other string
|
write_msgpack_string(el.first, j);
|
||||||
if (error_handler == error_handler_t::strict)
|
|
||||||
{
|
|
||||||
check_utf8(el.first, j);
|
|
||||||
}
|
|
||||||
write_msgpack(el.first);
|
|
||||||
write_msgpack(el.second, depth + 1);
|
write_msgpack(el.second, depth + 1);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -819,8 +782,10 @@ class binary_writer
|
|||||||
|
|
||||||
for (const auto& el : *j.m_data.m_value.object)
|
for (const auto& el : *j.m_data.m_value.object)
|
||||||
{
|
{
|
||||||
|
// a converted key must outlive the reference returned by sanitize_utf8_for_write
|
||||||
|
const object_key_string_t key_string = el.first;
|
||||||
string_t storage;
|
string_t storage;
|
||||||
const string_t& key = sanitize_utf8_for_write(el.first, j, storage);
|
const string_t& key = sanitize_utf8_for_write(key_string, j, storage);
|
||||||
write_number_with_ubjson_prefix(key.size(), true, use_bjdata);
|
write_number_with_ubjson_prefix(key.size(), true, use_bjdata);
|
||||||
oa.write_characters(
|
oa.write_characters(
|
||||||
reinterpret_cast<const CharType*>(key.data()),
|
reinterpret_cast<const CharType*>(key.data()),
|
||||||
@@ -1025,13 +990,9 @@ class binary_writer
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// el.first is checked here, against the object as diagnostics
|
// the key is written directly (not via a temporary basic_json),
|
||||||
// context, like the matching check in write_cbor's object case
|
// with the object as diagnostics context, as in write_cbor
|
||||||
if (error_handler == error_handler_t::strict)
|
write_cbor_string(current.object_it->first, *current.value);
|
||||||
{
|
|
||||||
check_utf8(current.object_it->first, *current.value);
|
|
||||||
}
|
|
||||||
write_cbor(current.object_it->first);
|
|
||||||
const BasicJsonType* child = &(current.object_it->second);
|
const BasicJsonType* child = &(current.object_it->second);
|
||||||
++stack.back().object_it;
|
++stack.back().object_it;
|
||||||
write_cbor_value_or_push(*child, stack);
|
write_cbor_value_or_push(*child, stack);
|
||||||
@@ -1106,11 +1067,9 @@ class binary_writer
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (error_handler == error_handler_t::strict)
|
// as in write_cbor_iterative, the key is written directly with
|
||||||
{
|
// the object as diagnostics context
|
||||||
check_utf8(current.object_it->first, *current.value);
|
write_msgpack_string(current.object_it->first, *current.value);
|
||||||
}
|
|
||||||
write_msgpack(current.object_it->first);
|
|
||||||
const BasicJsonType* child = &(current.object_it->second);
|
const BasicJsonType* child = &(current.object_it->second);
|
||||||
++stack.back().object_it;
|
++stack.back().object_it;
|
||||||
write_msgpack_value_or_push(*child, stack);
|
write_msgpack_value_or_push(*child, stack);
|
||||||
@@ -1366,8 +1325,10 @@ class binary_writer
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// a converted key must outlive the reference returned by sanitize_utf8_for_write
|
||||||
|
const object_key_string_t key_string = current.object_it->first;
|
||||||
string_t storage;
|
string_t storage;
|
||||||
const string_t& key = sanitize_utf8_for_write(current.object_it->first, j, storage);
|
const string_t& key = sanitize_utf8_for_write(key_string, j, storage);
|
||||||
write_number_with_ubjson_prefix(key.size(), true, use_bjdata);
|
write_number_with_ubjson_prefix(key.size(), true, use_bjdata);
|
||||||
oa.write_characters(
|
oa.write_characters(
|
||||||
reinterpret_cast<const CharType*>(key.data()),
|
reinterpret_cast<const CharType*>(key.data()),
|
||||||
@@ -1988,6 +1949,85 @@ class binary_writer
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief write a CBOR text string
|
||||||
|
|
||||||
|
@a value is checked or sanitized according to @ref error_handler, with
|
||||||
|
@a context (the string value itself, or the object a key belongs to) used
|
||||||
|
as diagnostics context; this avoids converting object keys to a temporary
|
||||||
|
basic_json just to write them
|
||||||
|
|
||||||
|
@note When object_t::key_type is not string_t, @a value is a temporary
|
||||||
|
string_t converted from the key, which lives only until the end of
|
||||||
|
the caller's statement. The reference returned by
|
||||||
|
@ref sanitize_utf8_for_write may refer to it, so it must not escape
|
||||||
|
this function.
|
||||||
|
*/
|
||||||
|
void write_cbor_string(const string_t& value, const BasicJsonType& context)
|
||||||
|
{
|
||||||
|
string_t storage;
|
||||||
|
const string_t& sanitized = sanitize_utf8_for_write(value, context, storage);
|
||||||
|
|
||||||
|
// step 1: write control byte and the string length
|
||||||
|
write_cbor_head(0x60, sanitized.size());
|
||||||
|
|
||||||
|
// step 2: write the string
|
||||||
|
oa.write_characters(
|
||||||
|
reinterpret_cast<const CharType*>(sanitized.data()),
|
||||||
|
sanitized.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
/////////////
|
||||||
|
// MsgPack //
|
||||||
|
/////////////
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief write a MessagePack str
|
||||||
|
|
||||||
|
@a value is checked or sanitized according to @ref error_handler, with
|
||||||
|
@a context used as diagnostics context, as in @ref write_cbor_string
|
||||||
|
|
||||||
|
@note As in @ref write_cbor_string, @a value may be a temporary string_t
|
||||||
|
converted from a key, so the reference returned by
|
||||||
|
@ref sanitize_utf8_for_write must not escape this function.
|
||||||
|
*/
|
||||||
|
void write_msgpack_string(const string_t& value, const BasicJsonType& context)
|
||||||
|
{
|
||||||
|
string_t storage;
|
||||||
|
const string_t& sanitized = sanitize_utf8_for_write(value, context, storage);
|
||||||
|
|
||||||
|
// step 1: write control byte and the string length
|
||||||
|
const auto N = to_msgpack_length(sanitized.size(), context);
|
||||||
|
if (N <= 31)
|
||||||
|
{
|
||||||
|
// fixstr
|
||||||
|
write_number(static_cast<std::uint8_t>(0xA0 | N));
|
||||||
|
}
|
||||||
|
else if (N <= (std::numeric_limits<std::uint8_t>::max)())
|
||||||
|
{
|
||||||
|
// str 8
|
||||||
|
oa.write_character(to_char_type(0xD9));
|
||||||
|
write_number(static_cast<std::uint8_t>(N));
|
||||||
|
}
|
||||||
|
else if (N <= (std::numeric_limits<std::uint16_t>::max)())
|
||||||
|
{
|
||||||
|
// str 16
|
||||||
|
oa.write_character(to_char_type(0xDA));
|
||||||
|
write_number(static_cast<std::uint16_t>(N));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// str 32
|
||||||
|
oa.write_character(to_char_type(0xDB));
|
||||||
|
write_number(static_cast<std::uint32_t>(N));
|
||||||
|
}
|
||||||
|
|
||||||
|
// step 2: write the string
|
||||||
|
oa.write_characters(
|
||||||
|
reinterpret_cast<const CharType*>(sanitized.data()),
|
||||||
|
sanitized.size());
|
||||||
|
}
|
||||||
|
|
||||||
////////////
|
////////////
|
||||||
// UBJSON //
|
// UBJSON //
|
||||||
////////////
|
////////////
|
||||||
@@ -2730,6 +2770,11 @@ class binary_writer
|
|||||||
itself in every case but a sanitized `replace`/`ignore` one, so @a
|
itself in every case but a sanitized `replace`/`ignore` one, so @a
|
||||||
storage must outlive the returned reference only then.
|
storage must outlive the returned reference only then.
|
||||||
|
|
||||||
|
@a s must be an lvalue that outlives the returned reference. An object key
|
||||||
|
whose `key_type` is not @ref string_t must therefore first be converted
|
||||||
|
into a named string_t (see @ref object_key_string_t); the deleted overload
|
||||||
|
below enforces this at compile time.
|
||||||
|
|
||||||
@param[in] s the string (value or object key) to write
|
@param[in] s the string (value or object key) to write
|
||||||
@param[in] context the value @a s belongs to (for diagnostics)
|
@param[in] context the value @a s belongs to (for diagnostics)
|
||||||
@param[out] storage backing storage for a sanitized copy
|
@param[out] storage backing storage for a sanitized copy
|
||||||
@@ -2759,6 +2804,10 @@ class binary_writer
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// deleted: anything but a string_t would bind a temporary that dies before the returned reference is used
|
||||||
|
template < typename T, enable_if_t < !std::is_same<T, string_t>::value, int > = 0 >
|
||||||
|
const string_t& sanitize_utf8_for_write(const T& /*s*/, const BasicJsonType& /*context*/, string_t& /*storage*/) const = delete; // NOLINT(hicpp-use-equals-delete,modernize-use-equals-delete): a private helper's guard, not part of the interface
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@brief write an integer in the shortest encoding
|
@brief write an integer in the shortest encoding
|
||||||
|
|
||||||
|
|||||||
+277
-116
@@ -19928,8 +19928,8 @@ NLOHMANN_JSON_NAMESPACE_END
|
|||||||
#include <iosfwd> // ostream
|
#include <iosfwd> // ostream
|
||||||
#endif // JSON_NO_IO
|
#endif // JSON_NO_IO
|
||||||
#include <limits> // max
|
#include <limits> // max
|
||||||
|
#include <map> // map
|
||||||
#include <numeric> // accumulate
|
#include <numeric> // accumulate
|
||||||
#include <set> // set
|
|
||||||
#include <string> // string
|
#include <string> // string
|
||||||
#include <utility> // move
|
#include <utility> // move
|
||||||
#include <vector> // vector
|
#include <vector> // vector
|
||||||
@@ -20277,33 +20277,82 @@ class json_pointer
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
/*!
|
/*!
|
||||||
@brief the reference token sequences that denote arrays
|
@brief the pointer prefixes of a flattened object, and which of them denote arrays
|
||||||
|
|
||||||
@ref unflatten collects the pointer prefixes that have a reference token 0
|
@ref unflatten collects the pointer prefixes that have a reference token 0
|
||||||
among their children; @ref get_and_create creates arrays exactly below
|
among their children; @ref get_and_create creates arrays exactly below
|
||||||
those prefixes and objects everywhere else. Deciding this up front keeps
|
those prefixes and objects everywhere else. Deciding this up front keeps
|
||||||
the result independent of the order in which the flattened object is
|
the result independent of the order in which the flattened object is
|
||||||
iterated, which is unspecified for some object types.
|
iterated, which is unspecified for some object types.
|
||||||
|
|
||||||
|
The prefixes form a tree and are numbered, so each of them is stored only
|
||||||
|
once (as a node) rather than as a copy of all of its reference tokens.
|
||||||
*/
|
*/
|
||||||
using array_parents_t = std::set<std::vector<string_t>>;
|
struct prefix_tree
|
||||||
|
{
|
||||||
|
// children[id] maps a reference token to the number of the prefix
|
||||||
|
// extended by that token; number 0 is the empty prefix
|
||||||
|
std::vector<std::map<string_t, std::size_t>> children;
|
||||||
|
// is_array[id] is true iff some flattened key has the reference token
|
||||||
|
// 0 directly below the prefix with number id
|
||||||
|
std::vector<bool> is_array;
|
||||||
|
|
||||||
|
// start with the empty prefix only
|
||||||
|
prefix_tree()
|
||||||
|
: children(1)
|
||||||
|
, is_array(1, false)
|
||||||
|
{}
|
||||||
|
|
||||||
|
// return the number of the prefix with number id extended by
|
||||||
|
// reference_token, adding it if it is new
|
||||||
|
std::size_t add_child(std::size_t id, string_t&& reference_token)
|
||||||
|
{
|
||||||
|
if (reference_token == "0")
|
||||||
|
{
|
||||||
|
is_array[id] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// read the number before the emplace_back below, which may
|
||||||
|
// reallocate children and invalidate the iterator
|
||||||
|
const std::size_t next = children.size();
|
||||||
|
const auto inserted = children[id].emplace(std::move(reference_token), next);
|
||||||
|
const std::size_t child = inserted.first->second;
|
||||||
|
if (inserted.second)
|
||||||
|
{
|
||||||
|
children.emplace_back();
|
||||||
|
is_array.push_back(false);
|
||||||
|
}
|
||||||
|
return child;
|
||||||
|
}
|
||||||
|
|
||||||
|
// return the number of the prefix with number id extended by
|
||||||
|
// reference_token, which must have been added before
|
||||||
|
std::size_t find_child(std::size_t id, const string_t& reference_token) const
|
||||||
|
{
|
||||||
|
const auto it = children[id].find(reference_token);
|
||||||
|
JSON_ASSERT(it != children[id].end());
|
||||||
|
return it->second;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@brief create and return a reference to the pointed to value
|
@brief create and return a reference to the pointed to value
|
||||||
|
|
||||||
Complexity: Linear in the number of reference tokens.
|
Complexity: Linear in the number of reference tokens (times the logarithm
|
||||||
|
of the number of siblings for the prefix lookup).
|
||||||
|
|
||||||
@throw parse_error.106 if an array index begins with '0'
|
@throw parse_error.106 if an array index begins with '0'
|
||||||
@throw parse_error.109 if array index is not a number
|
@throw parse_error.109 if array index is not a number
|
||||||
@throw type_error.313 if value cannot be unflattened
|
@throw type_error.313 if value cannot be unflattened
|
||||||
*/
|
*/
|
||||||
template<typename BasicJsonType>
|
template<typename BasicJsonType>
|
||||||
BasicJsonType& get_and_create(BasicJsonType& j, const array_parents_t& array_parents) const
|
BasicJsonType& get_and_create(BasicJsonType& j, const prefix_tree& tree) const
|
||||||
{
|
{
|
||||||
auto* result = &j;
|
auto* result = &j;
|
||||||
|
|
||||||
// the reference tokens that have been consumed so far; used to look up
|
// the number of the prefix consumed so far; used to look up whether
|
||||||
// whether the value to be created below is an array or an object
|
// the value to be created below is an array or an object
|
||||||
std::vector<string_t> prefix;
|
std::size_t id = 0;
|
||||||
|
|
||||||
// in case no reference tokens exist, return a reference to the JSON value
|
// in case no reference tokens exist, return a reference to the JSON value
|
||||||
// j which will be overwritten by a primitive value
|
// j which will be overwritten by a primitive value
|
||||||
@@ -20313,7 +20362,7 @@ class json_pointer
|
|||||||
{
|
{
|
||||||
case detail::value_t::null:
|
case detail::value_t::null:
|
||||||
{
|
{
|
||||||
if (array_parents.find(prefix) != array_parents.end())
|
if (tree.is_array[id])
|
||||||
{
|
{
|
||||||
// some reference token below this position is 0, so the
|
// some reference token below this position is 0, so the
|
||||||
// value is an array
|
// value is an array
|
||||||
@@ -20358,7 +20407,7 @@ class json_pointer
|
|||||||
JSON_THROW(detail::type_error::create(313, "invalid value to unflatten", &j));
|
JSON_THROW(detail::type_error::create(313, "invalid value to unflatten", &j));
|
||||||
}
|
}
|
||||||
|
|
||||||
prefix.push_back(reference_token);
|
id = tree.find_child(id, reference_token);
|
||||||
}
|
}
|
||||||
|
|
||||||
return *result;
|
return *result;
|
||||||
@@ -20796,49 +20845,74 @@ class json_pointer
|
|||||||
@param[in,out] result the result object to insert values to
|
@param[in,out] result the result object to insert values to
|
||||||
|
|
||||||
@note Empty objects or arrays are flattened to `null`.
|
@note Empty objects or arrays are flattened to `null`.
|
||||||
|
|
||||||
|
The value is walked with an explicit stack rather than the call stack, so
|
||||||
|
arbitrarily deeply nested values can be flattened.
|
||||||
|
|
||||||
|
@sa https://github.com/nlohmann/json/issues/5393
|
||||||
*/
|
*/
|
||||||
template<typename BasicJsonType>
|
template<typename BasicJsonType>
|
||||||
static void flatten(const string_t& reference_string,
|
static void flatten(const string_t& reference_string,
|
||||||
const BasicJsonType& value,
|
const BasicJsonType& value,
|
||||||
BasicJsonType& result)
|
BasicJsonType& result)
|
||||||
{
|
{
|
||||||
switch (value.type())
|
using object_const_iterator = typename BasicJsonType::object_t::const_iterator;
|
||||||
|
|
||||||
|
// an array or object being walked: the container, the array index or
|
||||||
|
// object iterator of the next child, and the length of the path of the
|
||||||
|
// container itself
|
||||||
|
struct frame
|
||||||
|
{
|
||||||
|
frame(const BasicJsonType* container_, object_const_iterator member_, const std::size_t path_length_) noexcept
|
||||||
|
: container(container_), member(std::move(member_)), path_length(path_length_)
|
||||||
|
{}
|
||||||
|
|
||||||
|
const BasicJsonType* container;
|
||||||
|
std::size_t index = 0;
|
||||||
|
object_const_iterator member;
|
||||||
|
std::size_t path_length;
|
||||||
|
};
|
||||||
|
|
||||||
|
// The containers being flattened are kept on an explicit stack, and
|
||||||
|
// every child is flattened completely before the next one, so the
|
||||||
|
// entries come out in the same order as with a recursive walk. The
|
||||||
|
// path of the value being flattened is kept in one buffer that grows
|
||||||
|
// and shrinks with the stack, rather than in a new string per level.
|
||||||
|
std::vector<frame> stack;
|
||||||
|
string_t path = reference_string;
|
||||||
|
|
||||||
|
// flatten `v`, whose path is `path`: primitives and empty containers
|
||||||
|
// are added to the result right away; other containers get a frame
|
||||||
|
const auto enter = [&stack, &path, &result](const BasicJsonType & v)
|
||||||
|
{
|
||||||
|
switch (v.type())
|
||||||
{
|
{
|
||||||
case detail::value_t::array:
|
case detail::value_t::array:
|
||||||
{
|
{
|
||||||
if (value.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[reference_string] = nullptr;
|
result[path] = nullptr;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// iterate array and use index as a reference string
|
stack.emplace_back(&v, object_const_iterator(), path.size());
|
||||||
for (std::size_t i = 0; i < value.m_data.m_value.array->size(); ++i)
|
|
||||||
{
|
|
||||||
flatten(detail::concat<string_t>(reference_string, '/', std::to_string(i)),
|
|
||||||
value.m_data.m_value.array->operator[](i), result);
|
|
||||||
}
|
}
|
||||||
}
|
return;
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
case detail::value_t::object:
|
case detail::value_t::object:
|
||||||
{
|
{
|
||||||
if (value.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[reference_string] = nullptr;
|
result[path] = nullptr;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// iterate object and use keys as reference string
|
stack.emplace_back(&v, v.m_data.m_value.object->begin(), path.size());
|
||||||
for (const auto& element : *value.m_data.m_value.object)
|
|
||||||
{
|
|
||||||
flatten(detail::concat<string_t>(reference_string, '/', detail::escape(element.first)), element.second, result);
|
|
||||||
}
|
}
|
||||||
}
|
return;
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
case detail::value_t::null:
|
case detail::value_t::null:
|
||||||
@@ -20852,8 +20926,50 @@ class json_pointer
|
|||||||
default:
|
default:
|
||||||
{
|
{
|
||||||
// add a primitive value with its reference string
|
// add a primitive value with its reference string
|
||||||
result[reference_string] = value;
|
result[path] = v;
|
||||||
break;
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
enter(value);
|
||||||
|
while (!stack.empty())
|
||||||
|
{
|
||||||
|
// the frame is changed through stack.back(): enter() may push a
|
||||||
|
// frame, which would invalidate a reference to it
|
||||||
|
const BasicJsonType* const container = stack.back().container;
|
||||||
|
|
||||||
|
// drop the path of the previous child
|
||||||
|
path.resize(stack.back().path_length);
|
||||||
|
|
||||||
|
if (container->is_array())
|
||||||
|
{
|
||||||
|
const auto& array = *container->m_data.m_value.array;
|
||||||
|
const std::size_t i = stack.back().index;
|
||||||
|
if (i == array.size())
|
||||||
|
{
|
||||||
|
stack.pop_back();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// iterate array and use index as a reference string
|
||||||
|
++stack.back().index;
|
||||||
|
detail::concat_into(path, '/', detail::to_string<string_t>(i));
|
||||||
|
enter(array[i]);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
const object_const_iterator it = stack.back().member;
|
||||||
|
if (it == container->m_data.m_value.object->end())
|
||||||
|
{
|
||||||
|
stack.pop_back();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// iterate object and use keys as reference string
|
||||||
|
++stack.back().member;
|
||||||
|
detail::concat_into(path, '/', detail::escape(it->first));
|
||||||
|
enter(it->second);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -20881,19 +20997,15 @@ class json_pointer
|
|||||||
|
|
||||||
// collect the pointer prefixes that have a reference token 0 among
|
// collect the pointer prefixes that have a reference token 0 among
|
||||||
// their children; the values below them are arrays, all others are
|
// their children; the values below them are arrays, all others are
|
||||||
// objects (see array_parents_t)
|
// objects (see prefix_tree)
|
||||||
array_parents_t array_parents;
|
prefix_tree tree;
|
||||||
for (const auto& element : *value.m_data.m_value.object)
|
for (const auto& element : *value.m_data.m_value.object)
|
||||||
{
|
{
|
||||||
json_pointer ptr(element.first);
|
json_pointer ptr(element.first);
|
||||||
std::vector<string_t> prefix;
|
std::size_t id = 0;
|
||||||
for (auto& reference_token : ptr.reference_tokens)
|
for (auto& reference_token : ptr.reference_tokens)
|
||||||
{
|
{
|
||||||
if (reference_token == "0")
|
id = tree.add_child(id, std::move(reference_token));
|
||||||
{
|
|
||||||
array_parents.insert(prefix);
|
|
||||||
}
|
|
||||||
prefix.push_back(std::move(reference_token));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -20909,7 +21021,7 @@ class json_pointer
|
|||||||
// that if the JSON pointer is "" (i.e., points to the whole value),
|
// that if the JSON pointer is "" (i.e., points to the whole value),
|
||||||
// function get_and_create returns a reference to the result itself.
|
// function get_and_create returns a reference to the result itself.
|
||||||
// An assignment will then create a primitive value.
|
// An assignment will then create a primitive value.
|
||||||
json_pointer(element.first).get_and_create(result, array_parents) = element.second;
|
json_pointer(element.first).get_and_create(result, tree) = element.second;
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
@@ -21590,6 +21702,12 @@ template<typename BasicJsonType, typename CharType, typename OutputSinkType = ou
|
|||||||
class binary_writer
|
class binary_writer
|
||||||
{
|
{
|
||||||
using string_t = typename BasicJsonType::string_t;
|
using string_t = typename BasicJsonType::string_t;
|
||||||
|
|
||||||
|
/// an object key as string_t: a reference when object_t::key_type already is
|
||||||
|
/// string_t, otherwise a converted copy that outlives sanitize_utf8_for_write's result
|
||||||
|
using object_key_string_t = typename std::conditional <
|
||||||
|
std::is_same<typename BasicJsonType::object_t::key_type, string_t>::value,
|
||||||
|
const string_t&, string_t >::type;
|
||||||
using binary_t = typename BasicJsonType::binary_t;
|
using binary_t = typename BasicJsonType::binary_t;
|
||||||
using number_float_t = typename BasicJsonType::number_float_t;
|
using number_float_t = typename BasicJsonType::number_float_t;
|
||||||
|
|
||||||
@@ -21749,16 +21867,7 @@ class binary_writer
|
|||||||
|
|
||||||
case value_t::string:
|
case value_t::string:
|
||||||
{
|
{
|
||||||
string_t storage;
|
write_cbor_string(*j.m_data.m_value.string, j);
|
||||||
const string_t& value = sanitize_utf8_for_write(*j.m_data.m_value.string, j, storage);
|
|
||||||
|
|
||||||
// step 1: write control byte and the string length
|
|
||||||
write_cbor_head(0x60, value.size());
|
|
||||||
|
|
||||||
// step 2: write the string
|
|
||||||
oa.write_characters(
|
|
||||||
reinterpret_cast<const CharType*>(value.data()),
|
|
||||||
value.size());
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -21821,23 +21930,20 @@ class binary_writer
|
|||||||
|
|
||||||
case value_t::object:
|
case value_t::object:
|
||||||
{
|
{
|
||||||
|
static_assert(
|
||||||
|
std::is_convertible <
|
||||||
|
typename BasicJsonType::object_t::key_type,
|
||||||
|
string_t >::value,
|
||||||
|
"object_t::key_type must be implicitly convertible to string_t");
|
||||||
|
|
||||||
// step 1: write control byte and the object size
|
// step 1: write control byte and the object size
|
||||||
write_cbor_head(0xA0, j.m_data.m_value.object->size());
|
write_cbor_head(0xA0, j.m_data.m_value.object->size());
|
||||||
|
|
||||||
for (const auto& el : *j.m_data.m_value.object)
|
for (const auto& el : *j.m_data.m_value.object)
|
||||||
{
|
{
|
||||||
// el.first is checked here, against the object as
|
// el.first is written directly (not via a temporary
|
||||||
// diagnostics context, because write_cbor(el.first)
|
// basic_json), with the object as diagnostics context
|
||||||
// converts it to a temporary basic_json that would be
|
write_cbor_string(el.first, j);
|
||||||
// used as the context instead; for error_handler_t::keep
|
|
||||||
// and ::replace/::ignore the recursive write_cbor(el.first)
|
|
||||||
// call below handles the key like any other string, so no
|
|
||||||
// separate check is needed here for those
|
|
||||||
if (error_handler == error_handler_t::strict)
|
|
||||||
{
|
|
||||||
check_utf8(el.first, j);
|
|
||||||
}
|
|
||||||
write_cbor(el.first);
|
|
||||||
write_cbor(el.second, depth + 1);
|
write_cbor(el.second, depth + 1);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -21996,39 +22102,7 @@ class binary_writer
|
|||||||
|
|
||||||
case value_t::string:
|
case value_t::string:
|
||||||
{
|
{
|
||||||
string_t storage;
|
write_msgpack_string(*j.m_data.m_value.string, j);
|
||||||
const string_t& value = sanitize_utf8_for_write(*j.m_data.m_value.string, j, storage);
|
|
||||||
|
|
||||||
// step 1: write control byte and the string length
|
|
||||||
const auto N = to_msgpack_length(value.size(), j);
|
|
||||||
if (N <= 31)
|
|
||||||
{
|
|
||||||
// fixstr
|
|
||||||
write_number(static_cast<std::uint8_t>(0xA0 | N));
|
|
||||||
}
|
|
||||||
else if (N <= (std::numeric_limits<std::uint8_t>::max)())
|
|
||||||
{
|
|
||||||
// str 8
|
|
||||||
oa.write_character(to_char_type(0xD9));
|
|
||||||
write_number(static_cast<std::uint8_t>(N));
|
|
||||||
}
|
|
||||||
else if (N <= (std::numeric_limits<std::uint16_t>::max)())
|
|
||||||
{
|
|
||||||
// str 16
|
|
||||||
oa.write_character(to_char_type(0xDA));
|
|
||||||
write_number(static_cast<std::uint16_t>(N));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// str 32
|
|
||||||
oa.write_character(to_char_type(0xDB));
|
|
||||||
write_number(static_cast<std::uint32_t>(N));
|
|
||||||
}
|
|
||||||
|
|
||||||
// step 2: write the string
|
|
||||||
oa.write_characters(
|
|
||||||
reinterpret_cast<const CharType*>(value.data()),
|
|
||||||
value.size());
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -22134,19 +22208,20 @@ class binary_writer
|
|||||||
|
|
||||||
case value_t::object:
|
case value_t::object:
|
||||||
{
|
{
|
||||||
|
static_assert(
|
||||||
|
std::is_convertible <
|
||||||
|
typename BasicJsonType::object_t::key_type,
|
||||||
|
string_t >::value,
|
||||||
|
"object_t::key_type must be implicitly convertible to string_t");
|
||||||
|
|
||||||
// step 1: write control byte and the object size
|
// step 1: write control byte and the object size
|
||||||
write_msgpack_object_prefix(j.m_data.m_value.object->size(), j);
|
write_msgpack_object_prefix(j.m_data.m_value.object->size(), j);
|
||||||
|
|
||||||
for (const auto& el : *j.m_data.m_value.object)
|
for (const auto& el : *j.m_data.m_value.object)
|
||||||
{
|
{
|
||||||
// as in write_cbor, el.first is checked here against the
|
// as in write_cbor, el.first is written directly with the
|
||||||
// object as diagnostics context; the recursive call below
|
// object as diagnostics context
|
||||||
// handles keep/replace/ignore like any other string
|
write_msgpack_string(el.first, j);
|
||||||
if (error_handler == error_handler_t::strict)
|
|
||||||
{
|
|
||||||
check_utf8(el.first, j);
|
|
||||||
}
|
|
||||||
write_msgpack(el.first);
|
|
||||||
write_msgpack(el.second, depth + 1);
|
write_msgpack(el.second, depth + 1);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -22324,8 +22399,10 @@ class binary_writer
|
|||||||
|
|
||||||
for (const auto& el : *j.m_data.m_value.object)
|
for (const auto& el : *j.m_data.m_value.object)
|
||||||
{
|
{
|
||||||
|
// a converted key must outlive the reference returned by sanitize_utf8_for_write
|
||||||
|
const object_key_string_t key_string = el.first;
|
||||||
string_t storage;
|
string_t storage;
|
||||||
const string_t& key = sanitize_utf8_for_write(el.first, j, storage);
|
const string_t& key = sanitize_utf8_for_write(key_string, j, storage);
|
||||||
write_number_with_ubjson_prefix(key.size(), true, use_bjdata);
|
write_number_with_ubjson_prefix(key.size(), true, use_bjdata);
|
||||||
oa.write_characters(
|
oa.write_characters(
|
||||||
reinterpret_cast<const CharType*>(key.data()),
|
reinterpret_cast<const CharType*>(key.data()),
|
||||||
@@ -22530,13 +22607,9 @@ class binary_writer
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// el.first is checked here, against the object as diagnostics
|
// the key is written directly (not via a temporary basic_json),
|
||||||
// context, like the matching check in write_cbor's object case
|
// with the object as diagnostics context, as in write_cbor
|
||||||
if (error_handler == error_handler_t::strict)
|
write_cbor_string(current.object_it->first, *current.value);
|
||||||
{
|
|
||||||
check_utf8(current.object_it->first, *current.value);
|
|
||||||
}
|
|
||||||
write_cbor(current.object_it->first);
|
|
||||||
const BasicJsonType* child = &(current.object_it->second);
|
const BasicJsonType* child = &(current.object_it->second);
|
||||||
++stack.back().object_it;
|
++stack.back().object_it;
|
||||||
write_cbor_value_or_push(*child, stack);
|
write_cbor_value_or_push(*child, stack);
|
||||||
@@ -22611,11 +22684,9 @@ class binary_writer
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (error_handler == error_handler_t::strict)
|
// as in write_cbor_iterative, the key is written directly with
|
||||||
{
|
// the object as diagnostics context
|
||||||
check_utf8(current.object_it->first, *current.value);
|
write_msgpack_string(current.object_it->first, *current.value);
|
||||||
}
|
|
||||||
write_msgpack(current.object_it->first);
|
|
||||||
const BasicJsonType* child = &(current.object_it->second);
|
const BasicJsonType* child = &(current.object_it->second);
|
||||||
++stack.back().object_it;
|
++stack.back().object_it;
|
||||||
write_msgpack_value_or_push(*child, stack);
|
write_msgpack_value_or_push(*child, stack);
|
||||||
@@ -22871,8 +22942,10 @@ class binary_writer
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// a converted key must outlive the reference returned by sanitize_utf8_for_write
|
||||||
|
const object_key_string_t key_string = current.object_it->first;
|
||||||
string_t storage;
|
string_t storage;
|
||||||
const string_t& key = sanitize_utf8_for_write(current.object_it->first, j, storage);
|
const string_t& key = sanitize_utf8_for_write(key_string, j, storage);
|
||||||
write_number_with_ubjson_prefix(key.size(), true, use_bjdata);
|
write_number_with_ubjson_prefix(key.size(), true, use_bjdata);
|
||||||
oa.write_characters(
|
oa.write_characters(
|
||||||
reinterpret_cast<const CharType*>(key.data()),
|
reinterpret_cast<const CharType*>(key.data()),
|
||||||
@@ -23493,6 +23566,85 @@ class binary_writer
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief write a CBOR text string
|
||||||
|
|
||||||
|
@a value is checked or sanitized according to @ref error_handler, with
|
||||||
|
@a context (the string value itself, or the object a key belongs to) used
|
||||||
|
as diagnostics context; this avoids converting object keys to a temporary
|
||||||
|
basic_json just to write them
|
||||||
|
|
||||||
|
@note When object_t::key_type is not string_t, @a value is a temporary
|
||||||
|
string_t converted from the key, which lives only until the end of
|
||||||
|
the caller's statement. The reference returned by
|
||||||
|
@ref sanitize_utf8_for_write may refer to it, so it must not escape
|
||||||
|
this function.
|
||||||
|
*/
|
||||||
|
void write_cbor_string(const string_t& value, const BasicJsonType& context)
|
||||||
|
{
|
||||||
|
string_t storage;
|
||||||
|
const string_t& sanitized = sanitize_utf8_for_write(value, context, storage);
|
||||||
|
|
||||||
|
// step 1: write control byte and the string length
|
||||||
|
write_cbor_head(0x60, sanitized.size());
|
||||||
|
|
||||||
|
// step 2: write the string
|
||||||
|
oa.write_characters(
|
||||||
|
reinterpret_cast<const CharType*>(sanitized.data()),
|
||||||
|
sanitized.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
/////////////
|
||||||
|
// MsgPack //
|
||||||
|
/////////////
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief write a MessagePack str
|
||||||
|
|
||||||
|
@a value is checked or sanitized according to @ref error_handler, with
|
||||||
|
@a context used as diagnostics context, as in @ref write_cbor_string
|
||||||
|
|
||||||
|
@note As in @ref write_cbor_string, @a value may be a temporary string_t
|
||||||
|
converted from a key, so the reference returned by
|
||||||
|
@ref sanitize_utf8_for_write must not escape this function.
|
||||||
|
*/
|
||||||
|
void write_msgpack_string(const string_t& value, const BasicJsonType& context)
|
||||||
|
{
|
||||||
|
string_t storage;
|
||||||
|
const string_t& sanitized = sanitize_utf8_for_write(value, context, storage);
|
||||||
|
|
||||||
|
// step 1: write control byte and the string length
|
||||||
|
const auto N = to_msgpack_length(sanitized.size(), context);
|
||||||
|
if (N <= 31)
|
||||||
|
{
|
||||||
|
// fixstr
|
||||||
|
write_number(static_cast<std::uint8_t>(0xA0 | N));
|
||||||
|
}
|
||||||
|
else if (N <= (std::numeric_limits<std::uint8_t>::max)())
|
||||||
|
{
|
||||||
|
// str 8
|
||||||
|
oa.write_character(to_char_type(0xD9));
|
||||||
|
write_number(static_cast<std::uint8_t>(N));
|
||||||
|
}
|
||||||
|
else if (N <= (std::numeric_limits<std::uint16_t>::max)())
|
||||||
|
{
|
||||||
|
// str 16
|
||||||
|
oa.write_character(to_char_type(0xDA));
|
||||||
|
write_number(static_cast<std::uint16_t>(N));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// str 32
|
||||||
|
oa.write_character(to_char_type(0xDB));
|
||||||
|
write_number(static_cast<std::uint32_t>(N));
|
||||||
|
}
|
||||||
|
|
||||||
|
// step 2: write the string
|
||||||
|
oa.write_characters(
|
||||||
|
reinterpret_cast<const CharType*>(sanitized.data()),
|
||||||
|
sanitized.size());
|
||||||
|
}
|
||||||
|
|
||||||
////////////
|
////////////
|
||||||
// UBJSON //
|
// UBJSON //
|
||||||
////////////
|
////////////
|
||||||
@@ -24235,6 +24387,11 @@ class binary_writer
|
|||||||
itself in every case but a sanitized `replace`/`ignore` one, so @a
|
itself in every case but a sanitized `replace`/`ignore` one, so @a
|
||||||
storage must outlive the returned reference only then.
|
storage must outlive the returned reference only then.
|
||||||
|
|
||||||
|
@a s must be an lvalue that outlives the returned reference. An object key
|
||||||
|
whose `key_type` is not @ref string_t must therefore first be converted
|
||||||
|
into a named string_t (see @ref object_key_string_t); the deleted overload
|
||||||
|
below enforces this at compile time.
|
||||||
|
|
||||||
@param[in] s the string (value or object key) to write
|
@param[in] s the string (value or object key) to write
|
||||||
@param[in] context the value @a s belongs to (for diagnostics)
|
@param[in] context the value @a s belongs to (for diagnostics)
|
||||||
@param[out] storage backing storage for a sanitized copy
|
@param[out] storage backing storage for a sanitized copy
|
||||||
@@ -24264,6 +24421,10 @@ class binary_writer
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// deleted: anything but a string_t would bind a temporary that dies before the returned reference is used
|
||||||
|
template < typename T, enable_if_t < !std::is_same<T, string_t>::value, int > = 0 >
|
||||||
|
const string_t& sanitize_utf8_for_write(const T& /*s*/, const BasicJsonType& /*context*/, string_t& /*storage*/) const = delete; // NOLINT(hicpp-use-equals-delete,modernize-use-equals-delete): a private helper's guard, not part of the interface
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@brief write an integer in the shortest encoding
|
@brief write an integer in the shortest encoding
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,75 @@
|
|||||||
|
// __ _____ _____ _____
|
||||||
|
// __| | __| | | | 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
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <map>
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
#include <utility>
|
||||||
|
#include <nlohmann/json.hpp>
|
||||||
|
|
||||||
|
namespace custom_object_key_test
|
||||||
|
{
|
||||||
|
class key
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
key() = default;
|
||||||
|
|
||||||
|
key(const char* value)
|
||||||
|
: m_value(value)
|
||||||
|
{}
|
||||||
|
|
||||||
|
key(std::string value)
|
||||||
|
: m_value(std::move(value))
|
||||||
|
{}
|
||||||
|
|
||||||
|
operator std::string() const
|
||||||
|
{
|
||||||
|
return m_value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Required by JSON_DIAGNOSTICS, which reads object keys through data()
|
||||||
|
// when building the path of an exception.
|
||||||
|
const char* data() const noexcept
|
||||||
|
{
|
||||||
|
return m_value.data();
|
||||||
|
}
|
||||||
|
|
||||||
|
friend bool operator<(const key& lhs, const key& rhs)
|
||||||
|
{
|
||||||
|
return lhs.m_value < rhs.m_value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::string m_value;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename Key, typename Value, typename Compare, typename Allocator>
|
||||||
|
class object
|
||||||
|
: public std::map <
|
||||||
|
key,
|
||||||
|
Value,
|
||||||
|
std::less<key>, // NOLINT(modernize-use-transparent-functors)
|
||||||
|
typename std::allocator_traits<Allocator>::template rebind_alloc <
|
||||||
|
std::pair<const key, Value >>>
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
using allocator_type =
|
||||||
|
typename std::allocator_traits<Allocator>::template rebind_alloc <
|
||||||
|
std::pair<const key, Value >>;
|
||||||
|
|
||||||
|
using base_type =
|
||||||
|
std::map<key, Value, std::less<key>, allocator_type>; // NOLINT(modernize-use-transparent-functors)
|
||||||
|
|
||||||
|
public:
|
||||||
|
using base_type::base_type;
|
||||||
|
};
|
||||||
|
|
||||||
|
using json = nlohmann::basic_json<object>;
|
||||||
|
} // namespace custom_object_key_test
|
||||||
@@ -12,7 +12,11 @@
|
|||||||
#include <nlohmann/json.hpp>
|
#include <nlohmann/json.hpp>
|
||||||
using nlohmann::json;
|
using nlohmann::json;
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <map>
|
||||||
|
#include <memory>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <utility>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
@@ -55,6 +59,53 @@ std::string dump_and_parse(const std::string& raw, eh error_handler)
|
|||||||
return json::parse(json(raw).dump(-1, ' ', false, error_handler)).get<std::string>();
|
return json::parse(json(raw).dump(-1, ' ', false, error_handler)).get<std::string>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// an object key type that is not string_t, but converts implicitly to it;
|
||||||
|
// data() is only used when JSON_DIAGNOSTICS is enabled
|
||||||
|
DOCTEST_CLANG_SUPPRESS_WARNING_PUSH
|
||||||
|
DOCTEST_CLANG_SUPPRESS_WARNING("-Wunused-member-function")
|
||||||
|
class converting_key
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
converting_key(const char* s) : m_value(s) {} // NOLINT(google-explicit-constructor,hicpp-explicit-conversions)
|
||||||
|
converting_key(std::string s) : m_value(std::move(s)) {} // NOLINT(google-explicit-constructor,hicpp-explicit-conversions)
|
||||||
|
|
||||||
|
// the conversion yields a temporary string_t
|
||||||
|
operator std::string() const // NOLINT(google-explicit-constructor,hicpp-explicit-conversions)
|
||||||
|
{
|
||||||
|
return m_value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// read by the exception messages when JSON_DIAGNOSTICS is enabled
|
||||||
|
const char* data() const noexcept
|
||||||
|
{
|
||||||
|
return m_value.data();
|
||||||
|
}
|
||||||
|
|
||||||
|
friend bool operator<(const converting_key& lhs, const converting_key& rhs)
|
||||||
|
{
|
||||||
|
return lhs.m_value < rhs.m_value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::string m_value;
|
||||||
|
};
|
||||||
|
DOCTEST_CLANG_SUPPRESS_WARNING_POP
|
||||||
|
|
||||||
|
// ObjectType using converting_key; the Key template argument is ignored
|
||||||
|
template<typename Key, typename Value, typename Compare, typename Allocator>
|
||||||
|
class converting_key_object : public std::map<converting_key, Value, std::less<converting_key>, // NOLINT(modernize-use-transparent-functors)
|
||||||
|
typename std::allocator_traits<Allocator>::template rebind_alloc<std::pair<const converting_key, Value>>>
|
||||||
|
{
|
||||||
|
using base_type = std::map<converting_key, Value, std::less<converting_key>, // NOLINT(modernize-use-transparent-functors)
|
||||||
|
typename std::allocator_traits<Allocator>::template rebind_alloc<std::pair<const converting_key, Value>>>;
|
||||||
|
|
||||||
|
public:
|
||||||
|
using base_type::base_type;
|
||||||
|
using base_type::operator=;
|
||||||
|
};
|
||||||
|
|
||||||
|
using converting_key_json = nlohmann::basic_json<converting_key_object>;
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
TEST_CASE("UTF-8 error_handler for the binary readers and writers")
|
TEST_CASE("UTF-8 error_handler for the binary readers and writers")
|
||||||
@@ -370,3 +421,109 @@ TEST_CASE("UTF-8 error_handler for the binary readers and writers")
|
|||||||
CHECK(json::from_bson(bson_bytes)["k"].get<std::string>() == ill_formed_cases()[0].bytes);
|
CHECK(json::from_bson(bson_bytes)["k"].get<std::string>() == ill_formed_cases()[0].bytes);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The UBJSON and BJData writers bind the (possibly sanitized) key to a const
|
||||||
|
// string_t&. If key_type is not string_t but converts to it, the converted
|
||||||
|
// temporary must outlive that reference; this was a use-after-scope found by
|
||||||
|
// AddressSanitizer. Keys exceed the small string optimization on purpose.
|
||||||
|
TEST_CASE("UBJSON and BJData writers with an object_t whose key_type is not string_t")
|
||||||
|
{
|
||||||
|
const std::string long_prefix(70, 'k');
|
||||||
|
|
||||||
|
SECTION("well-formed keys, every error_handler")
|
||||||
|
{
|
||||||
|
const std::string key1 = long_prefix + "-first";
|
||||||
|
const std::string key2 = long_prefix + "-second";
|
||||||
|
|
||||||
|
converting_key_json::object_t o;
|
||||||
|
o.emplace(converting_key(key1), 1);
|
||||||
|
o.emplace(converting_key(key2), "value");
|
||||||
|
const converting_key_json v(std::move(o));
|
||||||
|
|
||||||
|
json expected;
|
||||||
|
expected[key1] = 1;
|
||||||
|
expected[key2] = "value";
|
||||||
|
|
||||||
|
const std::array<std::pair<bool, bool>, 3> combos = {{{false, false}, {true, false}, {true, true}}};
|
||||||
|
for (const auto h : all_handlers())
|
||||||
|
{
|
||||||
|
CAPTURE(static_cast<int>(h))
|
||||||
|
for (const auto& combo : combos)
|
||||||
|
{
|
||||||
|
const bool use_count = combo.first;
|
||||||
|
const bool use_type = combo.second;
|
||||||
|
CAPTURE(use_count)
|
||||||
|
CAPTURE(use_type)
|
||||||
|
|
||||||
|
CHECK(json::from_ubjson(converting_key_json::to_ubjson(v, use_count, use_type, h)) == expected);
|
||||||
|
CHECK(json::from_bjdata(converting_key_json::to_bjdata(v, use_count, use_type, json::bjdata_version_t::draft2, h)) == expected);
|
||||||
|
CHECK(json::from_bjdata(converting_key_json::to_bjdata(v, use_count, use_type, json::bjdata_version_t::draft3, h)) == expected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("ill-formed keys")
|
||||||
|
{
|
||||||
|
for (const auto& c : ill_formed_cases())
|
||||||
|
{
|
||||||
|
CAPTURE(c.name)
|
||||||
|
const std::string key = long_prefix + c.bytes;
|
||||||
|
|
||||||
|
converting_key_json::object_t o;
|
||||||
|
o.emplace(converting_key(key), 1);
|
||||||
|
const converting_key_json v(std::move(o));
|
||||||
|
|
||||||
|
CHECK_THROWS_AS(converting_key_json::to_ubjson(v, false, false, eh::strict), converting_key_json::type_error&);
|
||||||
|
CHECK_THROWS_AS(converting_key_json::to_bjdata(v, false, false, json::bjdata_version_t::draft2, eh::strict), converting_key_json::type_error&);
|
||||||
|
|
||||||
|
for (const auto h :
|
||||||
|
{
|
||||||
|
eh::replace, eh::ignore
|
||||||
|
})
|
||||||
|
{
|
||||||
|
CAPTURE(static_cast<int>(h))
|
||||||
|
const std::string expected = dump_and_parse(key, h);
|
||||||
|
|
||||||
|
CHECK(json::from_ubjson(converting_key_json::to_ubjson(v, false, false, h)).begin().key() == expected);
|
||||||
|
CHECK(json::from_bjdata(converting_key_json::to_bjdata(v, false, false, json::bjdata_version_t::draft2, h)).begin().key() == expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
CHECK(json::from_ubjson(converting_key_json::to_ubjson(v, false, false, eh::keep)).begin().key() == key);
|
||||||
|
CHECK(json::from_bjdata(converting_key_json::to_bjdata(v, false, false, json::bjdata_version_t::draft2, eh::keep)).begin().key() == key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("nested deeper than the recursion limit")
|
||||||
|
{
|
||||||
|
// wrap the previous value, innermost first
|
||||||
|
converting_key_json v = 42;
|
||||||
|
json expected = 42;
|
||||||
|
for (int i = 199; i >= 0; --i)
|
||||||
|
{
|
||||||
|
const std::string key = "level-" + std::to_string(i) + "-" + std::string(64, 'x');
|
||||||
|
|
||||||
|
converting_key_json::object_t o;
|
||||||
|
o.emplace(converting_key(key), std::move(v));
|
||||||
|
v = converting_key_json(std::move(o));
|
||||||
|
|
||||||
|
json e;
|
||||||
|
e[key] = std::move(expected);
|
||||||
|
expected = std::move(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto h : all_handlers())
|
||||||
|
{
|
||||||
|
CAPTURE(static_cast<int>(h))
|
||||||
|
for (const bool use_count :
|
||||||
|
{
|
||||||
|
false, true
|
||||||
|
})
|
||||||
|
{
|
||||||
|
CAPTURE(use_count)
|
||||||
|
|
||||||
|
CHECK(json::from_ubjson(converting_key_json::to_ubjson(v, use_count, false, h)) == expected);
|
||||||
|
CHECK(json::from_bjdata(converting_key_json::to_bjdata(v, use_count, false, json::bjdata_version_t::draft2, h)) == expected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -46,9 +46,7 @@ class huge_binary_t : public std::vector<std::uint8_t>
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
using huge_binary_json = nlohmann::basic_json <
|
using huge_binary_json = nlohmann::json::with_binary_t<huge_binary_t>;
|
||||||
std::map, std::vector, std::string, bool, std::int64_t, std::uint64_t,
|
|
||||||
double, std::allocator, nlohmann::adl_serializer, huge_binary_t, void >;
|
|
||||||
|
|
||||||
// a string type that can be made to report a size beyond INT32_MAX without
|
// a string type that can be made to report a size beyond INT32_MAX without
|
||||||
// allocating that much memory, so BSON length overflow can be tested for
|
// allocating that much memory, so BSON length overflow can be tested for
|
||||||
@@ -96,9 +94,7 @@ class huge_string_t : public std::string
|
|||||||
bool pretend_huge = false;
|
bool pretend_huge = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
using huge_string_json = nlohmann::basic_json <
|
using huge_string_json = nlohmann::json::with_string_t<huge_string_t>;
|
||||||
std::map, std::vector, huge_string_t, bool, std::int64_t, std::uint64_t,
|
|
||||||
double, std::allocator, nlohmann::adl_serializer, std::vector<std::uint8_t>, void >;
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
TEST_CASE("BSON")
|
TEST_CASE("BSON")
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ using nlohmann::json;
|
|||||||
#include "make_test_data_available.hpp"
|
#include "make_test_data_available.hpp"
|
||||||
#include "round_trip_corpus.hpp"
|
#include "round_trip_corpus.hpp"
|
||||||
#include "test_utils.hpp"
|
#include "test_utils.hpp"
|
||||||
|
#include "custom_object_key_type.hpp"
|
||||||
#include "sax_countdown.hpp"
|
#include "sax_countdown.hpp"
|
||||||
using utils::SaxCountdown;
|
using utils::SaxCountdown;
|
||||||
|
|
||||||
@@ -3357,3 +3358,55 @@ TEST_CASE("CBOR large strings and binaries (chunked reader)")
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("CBOR supports custom object key types")
|
||||||
|
{
|
||||||
|
using custom_json = custom_object_key_test::json;
|
||||||
|
using custom_key = custom_object_key_test::key;
|
||||||
|
|
||||||
|
custom_json::object_t object;
|
||||||
|
object.emplace(custom_key{"short"}, 1);
|
||||||
|
object.emplace(
|
||||||
|
custom_key{"a key longer than twenty-three characters"},
|
||||||
|
2);
|
||||||
|
|
||||||
|
const custom_json value(std::move(object));
|
||||||
|
const auto encoded = custom_json::to_cbor(value);
|
||||||
|
|
||||||
|
CHECK(nlohmann::json::from_cbor(encoded) == nlohmann::json
|
||||||
|
{
|
||||||
|
{"short", 1},
|
||||||
|
{"a key longer than twenty-three characters", 2}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("CBOR supports custom object key types nested deeper than the recursion depth limit")
|
||||||
|
{
|
||||||
|
// below detail::recursion_depth_limit(), keys are written by
|
||||||
|
// write_cbor_iterative instead of write_cbor
|
||||||
|
using custom_json = custom_object_key_test::json;
|
||||||
|
using custom_key = custom_object_key_test::key;
|
||||||
|
|
||||||
|
const std::size_t depth = nlohmann::detail::recursion_depth_limit() + 10;
|
||||||
|
|
||||||
|
custom_json value = 1;
|
||||||
|
nlohmann::json expected = 1;
|
||||||
|
for (std::size_t i = 0; i < depth; ++i)
|
||||||
|
{
|
||||||
|
// alternate short keys with ones long enough to need a length byte
|
||||||
|
const std::string name = (i % 2 == 0) ? "k" + std::to_string(i)
|
||||||
|
: "a key longer than thirty-one characters " + std::to_string(i);
|
||||||
|
|
||||||
|
custom_json::object_t object;
|
||||||
|
object.emplace(custom_key{name}, std::move(value));
|
||||||
|
value = custom_json(std::move(object));
|
||||||
|
|
||||||
|
nlohmann::json::object_t expected_object;
|
||||||
|
expected_object.emplace(name, std::move(expected));
|
||||||
|
expected = nlohmann::json(std::move(expected_object));
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto encoded = custom_json::to_cbor(value);
|
||||||
|
CHECK(encoded == nlohmann::json::to_cbor(expected));
|
||||||
|
CHECK(nlohmann::json::from_cbor(encoded) == expected);
|
||||||
|
}
|
||||||
@@ -400,20 +400,7 @@ class base_class_with_hidden_members
|
|||||||
std::size_t m_size = 42;
|
std::size_t m_size = 42;
|
||||||
};
|
};
|
||||||
|
|
||||||
using json_with_hidden_base_members =
|
using json_with_hidden_base_members = nlohmann::json::with_base_class_t<base_class_with_hidden_members>;
|
||||||
nlohmann::basic_json <
|
|
||||||
std::map,
|
|
||||||
std::vector,
|
|
||||||
std::string,
|
|
||||||
bool,
|
|
||||||
std::int64_t,
|
|
||||||
std::uint64_t,
|
|
||||||
double,
|
|
||||||
std::allocator,
|
|
||||||
nlohmann::adl_serializer,
|
|
||||||
std::vector<std::uint8_t>,
|
|
||||||
base_class_with_hidden_members
|
|
||||||
>;
|
|
||||||
|
|
||||||
TEST_CASE("JSON Node as_base_class")
|
TEST_CASE("JSON Node as_base_class")
|
||||||
{
|
{
|
||||||
@@ -459,19 +446,7 @@ struct const_member_base
|
|||||||
const int id = 7; // NOLINT(misc-non-private-member-variables-in-classes)
|
const int id = 7; // NOLINT(misc-non-private-member-variables-in-classes)
|
||||||
};
|
};
|
||||||
|
|
||||||
using json_with_const_base = nlohmann::basic_json <
|
using json_with_const_base = nlohmann::json::with_base_class_t<const_member_base>;
|
||||||
std::map,
|
|
||||||
std::vector,
|
|
||||||
std::string,
|
|
||||||
bool,
|
|
||||||
std::int64_t,
|
|
||||||
std::uint64_t,
|
|
||||||
double,
|
|
||||||
std::allocator,
|
|
||||||
nlohmann::adl_serializer,
|
|
||||||
std::vector<std::uint8_t>,
|
|
||||||
const_member_base
|
|
||||||
>;
|
|
||||||
|
|
||||||
// build an array nested @a depth levels deep, with the innermost value 1;
|
// build an array nested @a depth levels deep, with the innermost value 1;
|
||||||
// every level is constructed (never assigned), since const_member_base does
|
// every level is constructed (never assigned), since const_member_base does
|
||||||
|
|||||||
@@ -26,15 +26,11 @@ namespace
|
|||||||
|
|
||||||
// a BinaryType whose value type is signed: the elements must still be
|
// a BinaryType whose value type is signed: the elements must still be
|
||||||
// processed as the numbers 0..255
|
// processed as the numbers 0..255
|
||||||
using char_binary_json = nlohmann::basic_json <
|
using char_binary_json = nlohmann::json::with_binary_t<std::vector<char>>;
|
||||||
std::map, std::vector, std::string, bool, std::int64_t, std::uint64_t,
|
|
||||||
double, std::allocator, nlohmann::adl_serializer, std::vector<char>, void >;
|
|
||||||
|
|
||||||
#ifdef JSON_HAS_CPP_17
|
#ifdef JSON_HAS_CPP_17
|
||||||
// a BinaryType whose value type is not an integer type at all
|
// a BinaryType whose value type is not an integer type at all
|
||||||
using byte_binary_json = nlohmann::basic_json <
|
using byte_binary_json = nlohmann::json::with_binary_t<std::vector<std::byte>>;
|
||||||
std::map, std::vector, std::string, bool, std::int64_t, std::uint64_t,
|
|
||||||
double, std::allocator, nlohmann::adl_serializer, std::vector<std::byte>, void >;
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|||||||
@@ -939,3 +939,114 @@ TEST_CASE("unescaping keeps a '~' that does not start an escape sequence")
|
|||||||
nlohmann::detail::unescape(s);
|
nlohmann::detail::unescape(s);
|
||||||
CHECK(s == "~/~");
|
CHECK(s == "~/~");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("flatten of structured values")
|
||||||
|
{
|
||||||
|
SECTION("values nested too deeply for the call stack (#5393)")
|
||||||
|
{
|
||||||
|
// flatten() used to recurse once per nesting level
|
||||||
|
const std::size_t depth = 100000;
|
||||||
|
for (const bool objects :
|
||||||
|
{
|
||||||
|
false, true
|
||||||
|
})
|
||||||
|
{
|
||||||
|
CAPTURE(objects)
|
||||||
|
std::string text;
|
||||||
|
std::string path;
|
||||||
|
for (std::size_t i = 0; i < depth; ++i)
|
||||||
|
{
|
||||||
|
text += objects ? "{\"a\":" : "[";
|
||||||
|
path += objects ? "/a" : "/0";
|
||||||
|
}
|
||||||
|
text += "0";
|
||||||
|
text += std::string(depth, objects ? '}' : ']');
|
||||||
|
const auto value = json::parse(text);
|
||||||
|
|
||||||
|
const auto flat = value.flatten();
|
||||||
|
REQUIRE(flat.size() == 1);
|
||||||
|
REQUIRE(flat.begin().key().size() == path.size());
|
||||||
|
CHECK(flat.begin().key() == path);
|
||||||
|
CHECK(flat.begin().value() == 0);
|
||||||
|
|
||||||
|
// unflatten() is linear in the depth, so the value roundtrips
|
||||||
|
CHECK(flat.unflatten() == value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("unflatten of a deeply nested pointer")
|
||||||
|
{
|
||||||
|
const std::size_t depth = 100000;
|
||||||
|
for (const bool objects :
|
||||||
|
{
|
||||||
|
false, true
|
||||||
|
})
|
||||||
|
{
|
||||||
|
CAPTURE(objects)
|
||||||
|
std::string path;
|
||||||
|
for (std::size_t i = 0; i < depth; ++i)
|
||||||
|
{
|
||||||
|
path += objects ? "/a" : "/0";
|
||||||
|
}
|
||||||
|
|
||||||
|
json flat = json::object();
|
||||||
|
flat[path] = 1;
|
||||||
|
const json value = flat.unflatten();
|
||||||
|
|
||||||
|
// walk down iteratively
|
||||||
|
std::size_t levels = 0;
|
||||||
|
const json* current = &value;
|
||||||
|
while (objects ? current->is_object() : current->is_array())
|
||||||
|
{
|
||||||
|
REQUIRE(current->size() == 1);
|
||||||
|
current = objects ? ¤t->at("a") : ¤t->at(0);
|
||||||
|
++levels;
|
||||||
|
}
|
||||||
|
CHECK(levels == depth);
|
||||||
|
CHECK(*current == 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("unflatten does not depend on the iteration order")
|
||||||
|
{
|
||||||
|
// the "0" key comes after its sibling in iteration order
|
||||||
|
const nlohmann::ordered_json flat_array = nlohmann::ordered_json::parse(R"({"/a/1": 2, "/a/0": 1})");
|
||||||
|
CHECK(flat_array.unflatten() == nlohmann::ordered_json::parse(R"({"a": [1, 2]})"));
|
||||||
|
|
||||||
|
const nlohmann::ordered_json flat_object = nlohmann::ordered_json::parse(R"({"/b/1": 2})");
|
||||||
|
CHECK(flat_object.unflatten() == nlohmann::ordered_json::parse(R"({"b": {"1": 2}})"));
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("objects and arrays interleaved")
|
||||||
|
{
|
||||||
|
const json value =
|
||||||
|
{
|
||||||
|
{"a", {1, {{"b", json::array()}, {"c", json::object()}}, json::array({{{"x~/", {true, nullptr}}}})}},
|
||||||
|
{"a/b", {{"~", 1}}},
|
||||||
|
{"z", "s"}
|
||||||
|
};
|
||||||
|
|
||||||
|
const json expected =
|
||||||
|
{
|
||||||
|
{"/a/0", 1},
|
||||||
|
{"/a/1/b", nullptr},
|
||||||
|
{"/a/1/c", nullptr},
|
||||||
|
{"/a/2/0/x~0~1/0", true},
|
||||||
|
{"/a/2/0/x~0~1/1", nullptr},
|
||||||
|
{"/a~1b/~0", 1},
|
||||||
|
{"/z", "s"}
|
||||||
|
};
|
||||||
|
|
||||||
|
CHECK(value.flatten() == expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("order of the entries of an ordered_json")
|
||||||
|
{
|
||||||
|
const auto value = nlohmann::ordered_json::parse(
|
||||||
|
R"({"z":"s","a/b":{"~":1,"k":[]},"a":[1,{"c":{},"b":[]},[{"x~/":[true,null],"w":2}]]})");
|
||||||
|
|
||||||
|
const auto flat = value.flatten();
|
||||||
|
CHECK(flat.dump() ==
|
||||||
|
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})");
|
||||||
|
}
|
||||||
|
}
|
||||||
+59
-46
@@ -31,6 +31,7 @@ using nlohmann::json;
|
|||||||
#include "make_test_data_available.hpp"
|
#include "make_test_data_available.hpp"
|
||||||
#include "round_trip_corpus.hpp"
|
#include "round_trip_corpus.hpp"
|
||||||
#include "test_utils.hpp"
|
#include "test_utils.hpp"
|
||||||
|
#include "custom_object_key_type.hpp"
|
||||||
#include "sax_countdown.hpp"
|
#include "sax_countdown.hpp"
|
||||||
using utils::SaxCountdown;
|
using utils::SaxCountdown;
|
||||||
|
|
||||||
@@ -2201,10 +2202,7 @@ struct huge_array : std::vector<T, A>
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
using huge_array_json = nlohmann::basic_json <
|
using huge_array_json = nlohmann::json::with_array_t<huge_array>;
|
||||||
std::map, huge_array, std::string, bool, std::int64_t, std::uint64_t,
|
|
||||||
double, std::allocator, nlohmann::adl_serializer,
|
|
||||||
std::vector<std::uint8_t>, void >;
|
|
||||||
|
|
||||||
TEST_CASE("MessagePack Size above uint32 for array")
|
TEST_CASE("MessagePack Size above uint32 for array")
|
||||||
{
|
{
|
||||||
@@ -2249,18 +2247,7 @@ template<typename K, typename V,
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
using huge_object_json = nlohmann::basic_json <
|
using huge_object_json = nlohmann::json::with_object_t<huge_map>;
|
||||||
huge_map,
|
|
||||||
std::vector,
|
|
||||||
std::string,
|
|
||||||
bool,
|
|
||||||
std::int64_t,
|
|
||||||
std::uint64_t,
|
|
||||||
double,
|
|
||||||
std::allocator,
|
|
||||||
nlohmann::adl_serializer,
|
|
||||||
std::vector<std::uint8_t>,
|
|
||||||
void >;
|
|
||||||
|
|
||||||
TEST_CASE("MessagePack Size above uint32 for object")
|
TEST_CASE("MessagePack Size above uint32 for object")
|
||||||
{
|
{
|
||||||
@@ -2295,18 +2282,7 @@ struct huge_string : std::string
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
using huge_string_json = nlohmann::basic_json <
|
using huge_string_json = nlohmann::json::with_string_t<huge_string>;
|
||||||
std::map,
|
|
||||||
std::vector,
|
|
||||||
huge_string,
|
|
||||||
bool,
|
|
||||||
std::int64_t,
|
|
||||||
std::uint64_t,
|
|
||||||
double,
|
|
||||||
std::allocator,
|
|
||||||
nlohmann::adl_serializer,
|
|
||||||
std::vector<std::uint8_t>,
|
|
||||||
void >;
|
|
||||||
|
|
||||||
TEST_CASE("MessagePack Size above uint32 for string")
|
TEST_CASE("MessagePack Size above uint32 for string")
|
||||||
{
|
{
|
||||||
@@ -2329,18 +2305,7 @@ struct huge_binary : std::vector<std::uint8_t>
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
using huge_binary_json = nlohmann::basic_json <
|
using huge_binary_json = nlohmann::json::with_binary_t<huge_binary>;
|
||||||
std::map,
|
|
||||||
std::vector,
|
|
||||||
std::string,
|
|
||||||
bool,
|
|
||||||
std::int64_t,
|
|
||||||
std::uint64_t,
|
|
||||||
double,
|
|
||||||
std::allocator,
|
|
||||||
nlohmann::adl_serializer,
|
|
||||||
huge_binary,
|
|
||||||
void >;
|
|
||||||
|
|
||||||
TEST_CASE("MessagePack Size above uint32 for binary")
|
TEST_CASE("MessagePack Size above uint32 for binary")
|
||||||
{
|
{
|
||||||
@@ -2390,14 +2355,10 @@ class beyond_uint32_string_t : public std::string
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
using beyond_uint32_string_json = nlohmann::basic_json <
|
using beyond_uint32_string_json = nlohmann::json::with_string_t<beyond_uint32_string_t>;
|
||||||
std::map, std::vector, beyond_uint32_string_t, bool, std::int64_t, std::uint64_t,
|
|
||||||
double, std::allocator, nlohmann::adl_serializer, std::vector<std::uint8_t>, void >;
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
using beyond_uint32_binary_json = nlohmann::basic_json <
|
using beyond_uint32_binary_json = nlohmann::json::with_binary_t<beyond_uint32_binary_t>;
|
||||||
std::map, std::vector, std::string, bool, std::int64_t, std::uint64_t,
|
|
||||||
double, std::allocator, nlohmann::adl_serializer, beyond_uint32_binary_t, void >;
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
TEST_CASE("MessagePack lengths beyond UINT32_MAX cannot be serialized")
|
TEST_CASE("MessagePack lengths beyond UINT32_MAX cannot be serialized")
|
||||||
@@ -2522,3 +2483,55 @@ TEST_CASE("MessagePack large strings and binaries (chunked reader)")
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("MessagePack supports custom object key types")
|
||||||
|
{
|
||||||
|
using custom_json = custom_object_key_test::json;
|
||||||
|
using custom_key = custom_object_key_test::key;
|
||||||
|
|
||||||
|
custom_json::object_t object;
|
||||||
|
object.emplace(custom_key{"short"}, 1);
|
||||||
|
object.emplace(
|
||||||
|
custom_key{"a key longer than thirty-one characters"},
|
||||||
|
2);
|
||||||
|
|
||||||
|
const custom_json value(std::move(object));
|
||||||
|
const auto encoded = custom_json::to_msgpack(value);
|
||||||
|
|
||||||
|
CHECK(nlohmann::json::from_msgpack(encoded) == nlohmann::json
|
||||||
|
{
|
||||||
|
{"short", 1},
|
||||||
|
{"a key longer than thirty-one characters", 2}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("MessagePack supports custom object key types nested deeper than the recursion depth limit")
|
||||||
|
{
|
||||||
|
// below detail::recursion_depth_limit(), keys are written by
|
||||||
|
// write_msgpack_iterative instead of write_msgpack
|
||||||
|
using custom_json = custom_object_key_test::json;
|
||||||
|
using custom_key = custom_object_key_test::key;
|
||||||
|
|
||||||
|
const std::size_t depth = nlohmann::detail::recursion_depth_limit() + 10;
|
||||||
|
|
||||||
|
custom_json value = 1;
|
||||||
|
nlohmann::json expected = 1;
|
||||||
|
for (std::size_t i = 0; i < depth; ++i)
|
||||||
|
{
|
||||||
|
// alternate short keys with ones long enough to need a length byte
|
||||||
|
const std::string name = (i % 2 == 0) ? "k" + std::to_string(i)
|
||||||
|
: "a key longer than thirty-one characters " + std::to_string(i);
|
||||||
|
|
||||||
|
custom_json::object_t object;
|
||||||
|
object.emplace(custom_key{name}, std::move(value));
|
||||||
|
value = custom_json(std::move(object));
|
||||||
|
|
||||||
|
nlohmann::json::object_t expected_object;
|
||||||
|
expected_object.emplace(name, std::move(expected));
|
||||||
|
expected = nlohmann::json(std::move(expected_object));
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto encoded = custom_json::to_msgpack(value);
|
||||||
|
CHECK(encoded == nlohmann::json::to_msgpack(expected));
|
||||||
|
CHECK(nlohmann::json::from_msgpack(encoded) == expected);
|
||||||
|
}
|
||||||
@@ -217,16 +217,7 @@ void int_to_string(alt_string& target, std::size_t value)
|
|||||||
target = std::to_string(value).c_str();
|
target = std::to_string(value).c_str();
|
||||||
}
|
}
|
||||||
|
|
||||||
using alt_json = nlohmann::basic_json <
|
using alt_json = nlohmann::json::with_string_t<alt_string>;
|
||||||
std::map,
|
|
||||||
std::vector,
|
|
||||||
alt_string,
|
|
||||||
bool,
|
|
||||||
std::int64_t,
|
|
||||||
std::uint64_t,
|
|
||||||
double,
|
|
||||||
std::allocator,
|
|
||||||
nlohmann::adl_serializer >;
|
|
||||||
|
|
||||||
bool operator<(const char* op1, const alt_string& op2) noexcept
|
bool operator<(const char* op1, const alt_string& op2) noexcept
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in new issue
Block a user