diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 18ecb75fd..d8f7e41b3 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -4171,6 +4171,10 @@ struct has_to_json < BasicJsonType, T, enable_if_t < !is_basic_json::value >> template using detect_key_compare = typename T::key_compare; +// detects whether two values of type T can be compared with operator== +template +using detect_equal_comparable = decltype(static_cast(std::declval() == std::declval())); + // obtains the actual object key comparator: object_t::key_compare if the // object type defines it, and default_object_comparator_t otherwise // @@ -22042,12 +22046,6 @@ class binary_writer 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 write_cbor_head(0xA0, j.m_data.m_value.object->size()); @@ -22055,7 +22053,7 @@ class binary_writer { // el.first is written directly (not via a temporary // basic_json), with the object as diagnostics context - write_cbor_string(el.first, j); + write_cbor_key(el.first, j); write_cbor(el.second, depth + 1); } break; @@ -22320,12 +22318,6 @@ class binary_writer 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 write_msgpack_object_prefix(j.m_data.m_value.object->size(), j); @@ -22333,7 +22325,7 @@ class binary_writer { // as in write_cbor, el.first is written directly with the // object as diagnostics context - write_msgpack_string(el.first, j); + write_msgpack_key(el.first, j); write_msgpack(el.second, depth + 1); } break; @@ -22609,9 +22601,9 @@ class binary_writer } } - /// @brief a CBOR or MessagePack array or object whose elements - /// @ref write_cbor_iterative or @ref write_msgpack_iterative is - /// still writing + /// @brief a CBOR, MessagePack, or BON8 array or object whose elements + /// @ref write_cbor_iterative, @ref write_msgpack_iterative, or + /// @ref write_bon8_iterative is still writing struct binary_container_frame { explicit binary_container_frame(const BasicJsonType* value_) noexcept @@ -22723,7 +22715,7 @@ class binary_writer // the key is written directly (not via a temporary basic_json), // with the object as diagnostics context, as in write_cbor - write_cbor_string(current.object_it->first, *current.value); + write_cbor_key(current.object_it->first, *current.value); const BasicJsonType* child = &(current.object_it->second); ++stack.back().object_it; write_cbor_value_or_push(*child, stack); @@ -22800,7 +22792,7 @@ class binary_writer // as in write_cbor_iterative, the key is written directly with // the object as diagnostics context - write_msgpack_string(current.object_it->first, *current.value); + write_msgpack_key(current.object_it->first, *current.value); const BasicJsonType* child = &(current.object_it->second); ++stack.back().object_it; write_msgpack_value_or_push(*child, stack); @@ -23405,6 +23397,21 @@ class binary_writer } } + /*! + @brief Writes the head of a BSON element with key @a name whose value is + the object or array @a j: its type, key, and @a size + + Called while the key is in scope: an object key is a key_type, which + may only convert to a temporary string_t, so it cannot be kept by + pointer until after the loop over the entries. + */ + void write_bson_nested_head(const string_t& name, const BasicJsonType& j, + const std::size_t size) + { + write_bson_entry_header(name, j.is_object() ? 0x03 : 0x04); + write_number(to_bson_length(size), true); + } + /// @brief an object or array of the BSON document being sized or written struct bson_frame { @@ -23569,7 +23576,6 @@ class binary_writer { // write entries until the current object or array is done, or an // entry is an object or array itself - const string_t* nested_name = nullptr; const BasicJsonType* nested = nullptr; if (current.value->is_object()) { @@ -23580,7 +23586,7 @@ class binary_writer ++current.member; if (el.second.is_structured()) { - nested_name = &el.first; + write_bson_nested_head(el.first, el.second, nested_sizes[next_size++]); nested = &el.second; } else @@ -23599,7 +23605,7 @@ class binary_writer ++current.index; if (el.is_structured()) { - nested_name = &index_name; + write_bson_nested_head(index_name, el, nested_sizes[next_size++]); nested = ⪙ } else @@ -23611,8 +23617,6 @@ class binary_writer if (nested != nullptr) { - write_bson_entry_header(*nested_name, nested->is_object() ? 0x03 : 0x04); - write_number(to_bson_length(nested_sizes[next_size++]), true); parents.push_back(std::move(current)); current = bson_frame(nested); continue; @@ -23680,6 +23684,43 @@ class binary_writer } } + /*! + @brief write an object key as a CBOR text string + + A key convertible to string_t is written directly. Other key types (only + an explicit conversion, or only a to_json overload) go through a temporary + basic_json, as in version 3.12.0; the temporary is then the diagnostics + context for strict UTF-8 checks. + */ + template::value, int> = 0> + void write_cbor_key(const typename BasicJsonType::object_t::key_type& key, const BasicJsonType& context) + { + write_cbor_string(key, context); + } + + template < typename Key = typename BasicJsonType::object_t::key_type, + enable_if_t < !std::is_convertible::value, int > = 0 > + void write_cbor_key(const typename BasicJsonType::object_t::key_type& key, const BasicJsonType& /*context*/) + { + write_cbor(BasicJsonType(key)); + } + + /// @brief write an object key as a MessagePack str, as in @ref write_cbor_key + template::value, int> = 0> + void write_msgpack_key(const typename BasicJsonType::object_t::key_type& key, const BasicJsonType& context) + { + write_msgpack_string(key, context); + } + + template < typename Key = typename BasicJsonType::object_t::key_type, + enable_if_t < !std::is_convertible::value, int > = 0 > + void write_msgpack_key(const typename BasicJsonType::object_t::key_type& key, const BasicJsonType& /*context*/) + { + write_msgpack(BasicJsonType(key)); + } + /*! @brief write a CBOR text string @@ -24301,11 +24342,27 @@ class binary_writer @param[in] j JSON value to serialize @param[in,out] string_open whether the output ends with a non-empty string that has not been terminated with 0xFF - + @param[in] depth nesting level of @a j, counted from the + top-level value passed to @ref basic_json::to_bon8 + @throw type_error.316 if a string value or an object key is not valid + UTF-8 + @throw out_of_range.407 if an unsigned integer does not fit int64 @throw type_error.321 if @a j or a value nested in it is discarded + + Values nested deeper than @ref recursion_depth_limit are written by + @ref write_bon8_iterative without the call stack. + + @sa @ref write_cbor + @sa https://github.com/nlohmann/json/issues/5392 */ - void write_bon8_value(const BasicJsonType& j, bool& string_open) + void write_bon8_value(const BasicJsonType& j, bool& string_open, const std::size_t depth = 0) { + if (JSON_HEDLEY_UNLIKELY(depth >= recursion_depth_limit()) && (j.is_array() || j.is_object())) + { + write_bon8_iterative(j, string_open); + return; + } + switch (j.type()) { case value_t::null: @@ -24359,7 +24416,7 @@ class binary_writer for (const auto& el : *j.m_data.m_value.array) { - write_bon8_value(el, string_open); + write_bon8_value(el, string_open, depth + 1); } if (N > 4) @@ -24378,7 +24435,7 @@ class binary_writer for (const auto& el : *j.m_data.m_value.object) { write_bon8_string(el.first, string_open, j); - write_bon8_value(el.second, string_open); + write_bon8_value(el.second, string_open, depth + 1); } if (N > 4) @@ -24415,6 +24472,83 @@ class binary_writer } } + /*! + @brief write @a j with @ref write_bon8_value, or write its marker and push + a frame for @ref write_bon8_iterative to continue with its elements + + A scalar, and an empty array or object, are written out in full and not + pushed. + + @sa @ref write_cbor_value_or_push + */ + void write_bon8_value_or_push(const BasicJsonType& j, bool& string_open, std::vector& stack) + { + if (j.is_array() || j.is_object()) + { + // arrays use the markers 0x80..0x85, objects 0x86..0x8B; the last + // one stands for more than four elements, closed later with 0xFE + const auto N = j.size(); + const std::size_t base = j.is_array() ? 0x80 : 0x86; + write_bon8_marker(static_cast(base + (N <= 4 ? N : 5)), string_open); + if (N != 0) + { + stack.emplace_back(&j); + } + return; + } + + write_bon8_value(j, string_open); + } + + /*! + @brief write out @a root and everything below it without the call stack + + A container with more than four elements is closed with 0xFE once its + last element is written, as in @ref write_bon8_value. + + @sa @ref write_cbor_iterative + */ + void write_bon8_iterative(const BasicJsonType& root, bool& string_open) + { + // only a container with elements is ever pushed; see write_bon8_value_or_push + std::vector stack; + write_bon8_value_or_push(root, string_open, stack); + + while (!stack.empty()) + { + const binary_container_frame current = stack.back(); + const bool is_array = current.value->is_array(); + const bool at_end = is_array + ? current.array_it == current.value->m_data.m_value.array->cend() + : current.object_it == current.value->m_data.m_value.object->cend(); + + if (at_end) + { + if (current.value->size() > 4) + { + write_bon8_marker(0xFE, string_open); + } + stack.pop_back(); + continue; + } + + // read the child before pushing: entering it can move every frame + const BasicJsonType* child = nullptr; + if (is_array) + { + child = &(*current.array_it); + ++stack.back().array_it; + } + else + { + write_bon8_string(current.object_it->first, string_open, *current.value); + child = &(current.object_it->second); + ++stack.back().object_it; + } + write_bon8_value_or_push(*child, string_open, stack); + } + } + /*! @brief write a single byte that is not part of a string @@ -29408,6 +29542,24 @@ public: return create(first, last); } + /// @brief compare two object keys for equality, if the key type supports it + /// @note object_t only needs operator< for its keys (std::map), so operator== + /// may not exist; the keys are then reported as different, which makes + /// copy_object_level pair the values via object_t::find() + template::value, int> = 0> + static bool copy_keys_equal(const K& a, const K& b) + { + return a == b; + } + + template < typename K = typename object_t::key_type, + detail::enable_if_t < !detail::is_detected::value, int > = 0 > + static bool copy_keys_equal(const K& /*a*/, const K& /*b*/) + { + return false; + } + /// @brief create the copy of the object @a src in @a dst /// @note structured values are appended to @a worklist instead static void copy_object_level(const basic_json& src, basic_json& dst, @@ -29440,7 +29592,7 @@ public: auto src_it = src_object.cbegin(); for (auto& element : *dst.m_data.m_value.object) { - if (JSON_HEDLEY_LIKELY(src_it != src_object.cend() && src_it->first == element.first)) + if (JSON_HEDLEY_LIKELY(src_it != src_object.cend() && copy_keys_equal(src_it->first, element.first))) { copy_shallow(src_it->second, element.second, worklist); ++src_it; @@ -31317,11 +31469,28 @@ public: // std::map or ordered_map) never moves from its argument, so key is still // valid here regardless of whether KeyType was deduced as an rvalue reference // NOLINTNEXTLINE(bugprone-use-after-move,hicpp-invalid-access-moved) - JSON_THROW(out_of_range::create(detail::exception_id::key_not_found, detail::concat("key '", string_t(key), "' not found"), &j)); + JSON_THROW(out_of_range::create(detail::exception_id::key_not_found, detail::concat("key '", key_for_message(key), "' not found"), &j)); } return it->second; } + /// @brief key as it is passed to detail::concat for an error message + /// @note keys with data() and size() (such as string_t itself or a string + /// view) are passed through unchanged, so a miss does not copy them; + /// other keys (such as string literals or key types that only convert + /// to string_t) are converted to string_t + template < typename KeyType, detail::enable_if_t < detail::detect_string_can_append_data::value, int > = 0 > + static const KeyType & key_for_message(const KeyType& key) + { + return key; // NOLINT(bugprone-return-const-ref-from-parameter): the result is only passed to concat() within the full-expression that holds key + } + + template < typename KeyType, detail::enable_if_t < !detail::detect_string_can_append_data::value, int > = 0 > + static string_t key_for_message(const KeyType& key) + { + return string_t(key); + } + /// @brief checked array element access used by the at() overloads taking an index /// @throw type_error.304 if @a j is not an array /// @throw out_of_range.401 if @a idx is out of range