From f9e2302af90245e5ece909ea84258f6143dcb3ff Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Fri, 16 Oct 2026 22:41:24 +0200 Subject: [PATCH] Write the members of an object with scalar values without a call each Every member of an object was a call of write_cbor()/write_msgpack()/ write_ubjson() for its value, however simple: a function with a large frame that saves and restores many registers around a single sink write. Objects are now written by a function of their own, like arrays, in which members with a null, boolean, integer, or string value (and a float, in UBJSON and BJData) are written in place; anything else is passed on to the general function. The depth limit and the recursion for nested values are unchanged. Instructions per call (Linux, GCC 13, thousands), to_ubjson: 20000 small objects 17861 -> 14661, twitter 3271 -> 2798; to_bjdata: 20000 small objects 17881 -> 14681, twitter 3277 -> 2804; to_msgpack: twitter 2948 -> 2646. Signed-off-by: Niels Lohmann --- .../nlohmann/detail/output/binary_writer.hpp | 189 +++++++++++++++--- single_include/nlohmann/json.hpp | 189 +++++++++++++++--- 2 files changed, 326 insertions(+), 52 deletions(-) diff --git a/include/nlohmann/detail/output/binary_writer.hpp b/include/nlohmann/detail/output/binary_writer.hpp index f42c10c75..b77ea75ff 100644 --- a/include/nlohmann/detail/output/binary_writer.hpp +++ b/include/nlohmann/detail/output/binary_writer.hpp @@ -305,16 +305,7 @@ class binary_writer case value_t::object: { - // step 1: write control byte and the object size - write_cbor_head(0xA0, j.m_data.m_value.object->size()); - - for (const auto& el : *j.m_data.m_value.object) - { - // el.first is written directly (not via a temporary - // basic_json), with the object as diagnostics context - write_cbor_key(el.first, j); - write_cbor(el.second, depth + 1); - } + write_cbor_object(j, depth); break; } @@ -530,15 +521,7 @@ class binary_writer case value_t::object: { // step 1: write control byte and the object size - write_msgpack_object_prefix(j.m_data.m_value.object->size(), j); - - for (const auto& el : *j.m_data.m_value.object) - { - // as in write_cbor, el.first is written directly with the - // object as diagnostics context - write_msgpack_key(el.first, j); - write_msgpack(el.second, depth + 1); - } + write_msgpack_object(j, depth); break; } @@ -708,13 +691,7 @@ class binary_writer bool prefix_required = true; const bool write_closer = write_ubjson_start_object(j, use_count, use_type, add_prefix, use_bjdata, prefix_required); - 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; - write_ubjson_text(key_string, j, false, use_bjdata); - write_ubjson(el.second, use_count, use_type, prefix_required, use_bjdata, bjdata_version, depth + 1); - } + write_ubjson_members(j, use_count, use_type, prefix_required, use_bjdata, bjdata_version, depth); if (write_closer) { @@ -2579,6 +2556,56 @@ class binary_writer return encode_first_byte_and_tail(out, first_byte, size_class, argument); } + /*! + @brief write an object value (head and members) as CBOR + + A member whose value is null, a boolean, an integer, or a string is written + without a call of @ref write_cbor. Kept out of line, like @ref write_cbor_array. + */ + JSON_HEDLEY_NEVER_INLINE + void write_cbor_object(const BasicJsonType& j, const std::size_t depth) + { + // step 1: write control byte and the object size + write_cbor_head(0xA0, j.m_data.m_value.object->size()); + + for (const auto& el : *j.m_data.m_value.object) + { + // el.first is written directly (not via a temporary + // basic_json), with the object as diagnostics context + write_cbor_key(el.first, j); + + const BasicJsonType& value = el.second; + switch (value.type()) + { + case value_t::null: + oa.write_character(to_char_type(0xF6)); + break; + + case value_t::boolean: + oa.write_character(value.m_data.m_value.boolean ? to_char_type(0xF5) : to_char_type(0xF4)); + break; + + case value_t::number_integer: + case value_t::number_unsigned: + write_cbor_integer(value, std::integral_constant()); + break; + + case value_t::string: + write_cbor_string(*value.m_data.m_value.string, value); + break; + + case value_t::object: + case value_t::array: + case value_t::binary: + case value_t::number_float: + case value_t::discarded: + default: + write_cbor(value, depth + 1); + break; + } + } + } + /*! @brief write an array value (head and elements) as CBOR @@ -2857,6 +2884,54 @@ class binary_writer } } + /*! + @brief write an object value (head and members) as MessagePack + + See @ref write_cbor_object. + */ + JSON_HEDLEY_NEVER_INLINE + void write_msgpack_object(const BasicJsonType& j, const std::size_t depth) + { + write_msgpack_object_prefix(j.m_data.m_value.object->size(), j); + + for (const auto& el : *j.m_data.m_value.object) + { + // as in write_cbor, el.first is written directly with the + // object as diagnostics context + write_msgpack_key(el.first, j); + + const BasicJsonType& value = el.second; + switch (value.type()) + { + case value_t::null: + oa.write_character(to_char_type(0xC0)); + break; + + case value_t::boolean: + oa.write_character(value.m_data.m_value.boolean ? to_char_type(0xC3) : to_char_type(0xC2)); + break; + + case value_t::number_integer: + case value_t::number_unsigned: + write_msgpack_integer(value, std::integral_constant()); + break; + + case value_t::string: + write_msgpack_string(*value.m_data.m_value.string, value); + break; + + case value_t::object: + case value_t::array: + case value_t::binary: + case value_t::number_float: + case value_t::discarded: + default: + write_msgpack(value, depth + 1); + break; + } + } + } + /*! @brief write an array value (head and elements) as MessagePack @@ -3248,6 +3323,68 @@ class binary_writer } } + /*! + @brief write the members of the object @a j of a UBJSON/BJData container + + A member whose value is null, a boolean, an integer, a float, or a string is + written without a call of @ref write_ubjson. Kept out of line, like + @ref write_cbor_object. + */ + JSON_HEDLEY_NEVER_INLINE + void write_ubjson_members(const BasicJsonType& j, const bool use_count, const bool use_type, + const bool prefix_required, const bool use_bjdata, const bjdata_version_t bjdata_version, + const std::size_t depth) + { + 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; + write_ubjson_text(key_string, j, false, use_bjdata); + + const BasicJsonType& value = el.second; + switch (value.type()) + { + case value_t::null: + if (prefix_required) + { + oa.write_character(to_char_type('Z')); + } + break; + + case value_t::boolean: + if (prefix_required) + { + oa.write_character(value.m_data.m_value.boolean ? to_char_type('T') : to_char_type('F')); + } + break; + + case value_t::number_integer: + write_number_with_ubjson_prefix(value.m_data.m_value.number_integer, prefix_required, use_bjdata); + break; + + case value_t::number_unsigned: + write_number_with_ubjson_prefix(value.m_data.m_value.number_unsigned, prefix_required, use_bjdata); + break; + + case value_t::number_float: + write_number_with_ubjson_prefix(value.m_data.m_value.number_float, prefix_required, use_bjdata); + break; + + case value_t::string: + write_ubjson_text(*value.m_data.m_value.string, value, prefix_required, use_bjdata); + break; + + case value_t::object: + case value_t::array: + case value_t::binary: + case value_t::discarded: + default: + write_ubjson(value, use_count, use_type, prefix_required, use_bjdata, bjdata_version, depth + 1); + break; + } + } + } + /*! @brief write a UBJSON/BJData string or object key: the optional string marker `S`, the length as an integer with its marker, and the characters diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 3cbe6cebd..3560d816b 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -25133,16 +25133,7 @@ class binary_writer case value_t::object: { - // step 1: write control byte and the object size - write_cbor_head(0xA0, j.m_data.m_value.object->size()); - - for (const auto& el : *j.m_data.m_value.object) - { - // el.first is written directly (not via a temporary - // basic_json), with the object as diagnostics context - write_cbor_key(el.first, j); - write_cbor(el.second, depth + 1); - } + write_cbor_object(j, depth); break; } @@ -25358,15 +25349,7 @@ class binary_writer case value_t::object: { // step 1: write control byte and the object size - write_msgpack_object_prefix(j.m_data.m_value.object->size(), j); - - for (const auto& el : *j.m_data.m_value.object) - { - // as in write_cbor, el.first is written directly with the - // object as diagnostics context - write_msgpack_key(el.first, j); - write_msgpack(el.second, depth + 1); - } + write_msgpack_object(j, depth); break; } @@ -25536,13 +25519,7 @@ class binary_writer bool prefix_required = true; const bool write_closer = write_ubjson_start_object(j, use_count, use_type, add_prefix, use_bjdata, prefix_required); - 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; - write_ubjson_text(key_string, j, false, use_bjdata); - write_ubjson(el.second, use_count, use_type, prefix_required, use_bjdata, bjdata_version, depth + 1); - } + write_ubjson_members(j, use_count, use_type, prefix_required, use_bjdata, bjdata_version, depth); if (write_closer) { @@ -27407,6 +27384,56 @@ class binary_writer return encode_first_byte_and_tail(out, first_byte, size_class, argument); } + /*! + @brief write an object value (head and members) as CBOR + + A member whose value is null, a boolean, an integer, or a string is written + without a call of @ref write_cbor. Kept out of line, like @ref write_cbor_array. + */ + JSON_HEDLEY_NEVER_INLINE + void write_cbor_object(const BasicJsonType& j, const std::size_t depth) + { + // step 1: write control byte and the object size + write_cbor_head(0xA0, j.m_data.m_value.object->size()); + + for (const auto& el : *j.m_data.m_value.object) + { + // el.first is written directly (not via a temporary + // basic_json), with the object as diagnostics context + write_cbor_key(el.first, j); + + const BasicJsonType& value = el.second; + switch (value.type()) + { + case value_t::null: + oa.write_character(to_char_type(0xF6)); + break; + + case value_t::boolean: + oa.write_character(value.m_data.m_value.boolean ? to_char_type(0xF5) : to_char_type(0xF4)); + break; + + case value_t::number_integer: + case value_t::number_unsigned: + write_cbor_integer(value, std::integral_constant()); + break; + + case value_t::string: + write_cbor_string(*value.m_data.m_value.string, value); + break; + + case value_t::object: + case value_t::array: + case value_t::binary: + case value_t::number_float: + case value_t::discarded: + default: + write_cbor(value, depth + 1); + break; + } + } + } + /*! @brief write an array value (head and elements) as CBOR @@ -27685,6 +27712,54 @@ class binary_writer } } + /*! + @brief write an object value (head and members) as MessagePack + + See @ref write_cbor_object. + */ + JSON_HEDLEY_NEVER_INLINE + void write_msgpack_object(const BasicJsonType& j, const std::size_t depth) + { + write_msgpack_object_prefix(j.m_data.m_value.object->size(), j); + + for (const auto& el : *j.m_data.m_value.object) + { + // as in write_cbor, el.first is written directly with the + // object as diagnostics context + write_msgpack_key(el.first, j); + + const BasicJsonType& value = el.second; + switch (value.type()) + { + case value_t::null: + oa.write_character(to_char_type(0xC0)); + break; + + case value_t::boolean: + oa.write_character(value.m_data.m_value.boolean ? to_char_type(0xC3) : to_char_type(0xC2)); + break; + + case value_t::number_integer: + case value_t::number_unsigned: + write_msgpack_integer(value, std::integral_constant()); + break; + + case value_t::string: + write_msgpack_string(*value.m_data.m_value.string, value); + break; + + case value_t::object: + case value_t::array: + case value_t::binary: + case value_t::number_float: + case value_t::discarded: + default: + write_msgpack(value, depth + 1); + break; + } + } + } + /*! @brief write an array value (head and elements) as MessagePack @@ -28076,6 +28151,68 @@ class binary_writer } } + /*! + @brief write the members of the object @a j of a UBJSON/BJData container + + A member whose value is null, a boolean, an integer, a float, or a string is + written without a call of @ref write_ubjson. Kept out of line, like + @ref write_cbor_object. + */ + JSON_HEDLEY_NEVER_INLINE + void write_ubjson_members(const BasicJsonType& j, const bool use_count, const bool use_type, + const bool prefix_required, const bool use_bjdata, const bjdata_version_t bjdata_version, + const std::size_t depth) + { + 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; + write_ubjson_text(key_string, j, false, use_bjdata); + + const BasicJsonType& value = el.second; + switch (value.type()) + { + case value_t::null: + if (prefix_required) + { + oa.write_character(to_char_type('Z')); + } + break; + + case value_t::boolean: + if (prefix_required) + { + oa.write_character(value.m_data.m_value.boolean ? to_char_type('T') : to_char_type('F')); + } + break; + + case value_t::number_integer: + write_number_with_ubjson_prefix(value.m_data.m_value.number_integer, prefix_required, use_bjdata); + break; + + case value_t::number_unsigned: + write_number_with_ubjson_prefix(value.m_data.m_value.number_unsigned, prefix_required, use_bjdata); + break; + + case value_t::number_float: + write_number_with_ubjson_prefix(value.m_data.m_value.number_float, prefix_required, use_bjdata); + break; + + case value_t::string: + write_ubjson_text(*value.m_data.m_value.string, value, prefix_required, use_bjdata); + break; + + case value_t::object: + case value_t::array: + case value_t::binary: + case value_t::discarded: + default: + write_ubjson(value, use_count, use_type, prefix_required, use_bjdata, bjdata_version, depth + 1); + break; + } + } + } + /*! @brief write a UBJSON/BJData string or object key: the optional string marker `S`, the length as an integer with its marker, and the characters