diff --git a/include/nlohmann/detail/output/binary_writer.hpp b/include/nlohmann/detail/output/binary_writer.hpp index 437a86cd8..5f0fb55d8 100644 --- a/include/nlohmann/detail/output/binary_writer.hpp +++ b/include/nlohmann/detail/output/binary_writer.hpp @@ -928,166 +928,10 @@ class binary_writer */ void write_bon8(const BasicJsonType& j) { - switch (j.type()) + const bool last_written_value_is_string = write_bon8_internal(j); + if (last_written_value_is_string) { - case value_t::null: - { - oa->write_character(to_char_type(0xFA)); - break; - } - - case value_t::boolean: - { - oa->write_character(j.m_value.boolean - ? to_char_type(0xF9) - : to_char_type(0xF8)); - break; - } - - case value_t::number_unsigned: - { - if (j.m_value.number_unsigned > static_cast((std::numeric_limits::max)())) - { - JSON_THROW(out_of_range::create(407, "integer number " + std::to_string(j.m_value.number_unsigned) + " cannot be represented by BON8 as it does not fit int64", j)); - } - write_bon8_integer(static_cast(j.m_value.number_unsigned)); - break; - } - - case value_t::number_integer: - { - write_bon8_integer(j.m_value.number_integer); - break; - } - - case value_t::number_float: - { - // special values - if (j.m_value.number_float == -1.0) - { - oa->write_character(to_char_type(0xFB)); - } - else if (j.m_value.number_float == 0.0 && !std::signbit(j.m_value.number_float)) - { - oa->write_character(to_char_type(0xFC)); - } - else if (j.m_value.number_float == 1.0) - { - oa->write_character(to_char_type(0xFD)); - } - else - { - // write float with prefix - write_compact_float(j.m_value.number_float, detail::input_format_t::bon8); - } - break; - } - - case value_t::string: - { - // empty string: use end-of-text symbol - if (j.m_value.string->empty()) - { - oa->write_character(to_char_type(0xFF)); - break; - } - - // write strings as is - oa->write_characters( - reinterpret_cast(j.m_value.string->c_str()), - j.m_value.string->size()); - break; - } - - case value_t::array: - { - const auto N = j.m_value.array->size(); - if (N <= 4) - { - // start array with count (80..84) - oa->write_character(static_cast(0x80 + N)); - } - else - { - // start array - oa->write_character(to_char_type(0x85)); - } - - // write each element - for (std::size_t i = 0; i < N; ++i) - { - const auto& el = j.m_value.array->operator[](i); - - // check if 0xFF after nonempty string and string is required - if (i > 0) - { - const auto& prev = j.m_value.array->operator[](i - 1); - if (el.is_string() && prev.is_string() && !prev.m_value.string->empty()) - { - oa->write_character(to_char_type(0xFF)); - } - } - - write_bon8(el); - } - - if (N > 4) - { - // end of container - oa->write_character(to_char_type(0xFE)); - } - break; - } - - case value_t::object: - { - const auto N = j.m_value.object->size(); - if (N <= 4) - { - // start object with count (86..8A) - oa->write_character(static_cast(0x86 + N)); - } - else - { - // start object - oa->write_character(to_char_type(0x8B)); - } - - // write each element - for (auto it = j.m_value.object->begin(); it != j.m_value.object->end(); ++it) - { - const auto& key = it->first; - const auto& value = it->second; - - write_bon8(key); - - // check if we need a 0xFF separator between key and value - if (!key.empty() && value.is_string()) - { - oa->write_character(to_char_type(0xFF)); - } - - write_bon8(value); - - // check if we need a 0xFF separator between the value and the next key - if (value.is_string() && !value.m_value.string->empty() && std::next(it) != j.m_value.object->end()) - { - oa->write_character(to_char_type(0xFF)); - } - } - - if (N > 4) - { - // end of container - oa->write_character(to_char_type(0xFE)); - } - break; - } - - case value_t::binary: - case value_t::discarded: - default: - break; + oa->write_character(to_char_type(0xFF)); } } @@ -1696,6 +1540,181 @@ class binary_writer // BON8 // ////////// + /*! + * @param j + * @return whether the last written value was a string + */ + bool write_bon8_internal(const BasicJsonType& j) + { + switch (j.type()) + { + case value_t::null: + { + oa->write_character(to_char_type(0xFA)); + return false; + } + + case value_t::boolean: + { + oa->write_character(j.m_value.boolean + ? to_char_type(0xF9) + : to_char_type(0xF8)); + return false; + } + + case value_t::number_unsigned: + { + if (j.m_value.number_unsigned > static_cast((std::numeric_limits::max)())) + { + JSON_THROW(out_of_range::create(407, "integer number " + std::to_string(j.m_value.number_unsigned) + " cannot be represented by BON8 as it does not fit int64", j)); + } + write_bon8_integer(static_cast(j.m_value.number_unsigned)); + return false; + } + + case value_t::number_integer: + { + write_bon8_integer(j.m_value.number_integer); + return false; + } + + case value_t::number_float: + { + // special values + if (j.m_value.number_float == -1.0) + { + oa->write_character(to_char_type(0xFB)); + } + else if (j.m_value.number_float == 0.0 && !std::signbit(j.m_value.number_float)) + { + oa->write_character(to_char_type(0xFC)); + } + else if (j.m_value.number_float == 1.0) + { + oa->write_character(to_char_type(0xFD)); + } + else + { + // write float with prefix + write_compact_float(j.m_value.number_float, detail::input_format_t::bon8); + } + return false; + } + + case value_t::string: + { + // empty string: use end-of-text symbol + if (j.m_value.string->empty()) + { + oa->write_character(to_char_type(0xFF)); + return false; // already wrote 0xFF byte + } + + // write strings as is + oa->write_characters( + reinterpret_cast(j.m_value.string->c_str()), + j.m_value.string->size()); + return true; + } + + case value_t::array: + { + bool last_written_value_is_string = false; + const auto N = j.m_value.array->size(); + if (N <= 4) + { + // start array with count (80..84) + oa->write_character(static_cast(0x80 + N)); + } + else + { + // start array + oa->write_character(to_char_type(0x85)); + } + + // write each element + for (std::size_t i = 0; i < N; ++i) + { + const auto& el = j.m_value.array->operator[](i); + + // check if 0xFF after nonempty string and string is required + if (i > 0) + { + const auto& prev = j.m_value.array->operator[](i - 1); + if (el.is_string() && prev.is_string() && !prev.m_value.string->empty()) + { + oa->write_character(to_char_type(0xFF)); + } + } + + last_written_value_is_string = write_bon8_internal(el); + } + + if (N > 4) + { + // end of container + oa->write_character(to_char_type(0xFE)); + last_written_value_is_string = false; // 0xFE is not a string byte + } + + return last_written_value_is_string; + } + + case value_t::object: + { + bool last_written_value_is_string = false; + const auto N = j.m_value.object->size(); + if (N <= 4) + { + // start object with count (86..8A) + oa->write_character(static_cast(0x86 + N)); + } + else + { + // start object + oa->write_character(to_char_type(0x8B)); + } + + // write each element + for (auto it = j.m_value.object->begin(); it != j.m_value.object->end(); ++it) + { + const auto& key = it->first; + const auto& value = it->second; + + write_bon8_internal(key); + + // check if we need a 0xFF separator between key and value + if (!key.empty() && value.is_string()) + { + oa->write_character(to_char_type(0xFF)); + } + + last_written_value_is_string = write_bon8_internal(value); + + // check if we need a 0xFF separator between the value and the next key + if (value.is_string() && !value.m_value.string->empty() && std::next(it) != j.m_value.object->end()) + { + oa->write_character(to_char_type(0xFF)); + } + } + + if (N > 4) + { + // end of container + oa->write_character(to_char_type(0xFE)); + last_written_value_is_string = false; // 0xFE is not a string byte + } + + return last_written_value_is_string; + } + + case value_t::binary: + case value_t::discarded: + default: + return false; + } + } + void write_bon8_integer(typename BasicJsonType::number_integer_t value) { if (value < (std::numeric_limits::min)() || value > (std::numeric_limits::max)()) diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 3876db03e..6d1a489a4 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -14502,166 +14502,10 @@ class binary_writer */ void write_bon8(const BasicJsonType& j) { - switch (j.type()) + const bool last_written_value_is_string = write_bon8_internal(j); + if (last_written_value_is_string) { - case value_t::null: - { - oa->write_character(to_char_type(0xFA)); - break; - } - - case value_t::boolean: - { - oa->write_character(j.m_value.boolean - ? to_char_type(0xF9) - : to_char_type(0xF8)); - break; - } - - case value_t::number_unsigned: - { - if (j.m_value.number_unsigned > static_cast((std::numeric_limits::max)())) - { - JSON_THROW(out_of_range::create(407, "integer number " + std::to_string(j.m_value.number_unsigned) + " cannot be represented by BON8 as it does not fit int64", j)); - } - write_bon8_integer(static_cast(j.m_value.number_unsigned)); - break; - } - - case value_t::number_integer: - { - write_bon8_integer(j.m_value.number_integer); - break; - } - - case value_t::number_float: - { - // special values - if (j.m_value.number_float == -1.0) - { - oa->write_character(to_char_type(0xFB)); - } - else if (j.m_value.number_float == 0.0 && !std::signbit(j.m_value.number_float)) - { - oa->write_character(to_char_type(0xFC)); - } - else if (j.m_value.number_float == 1.0) - { - oa->write_character(to_char_type(0xFD)); - } - else - { - // write float with prefix - write_compact_float(j.m_value.number_float, detail::input_format_t::bon8); - } - break; - } - - case value_t::string: - { - // empty string: use end-of-text symbol - if (j.m_value.string->empty()) - { - oa->write_character(to_char_type(0xFF)); - break; - } - - // write strings as is - oa->write_characters( - reinterpret_cast(j.m_value.string->c_str()), - j.m_value.string->size()); - break; - } - - case value_t::array: - { - const auto N = j.m_value.array->size(); - if (N <= 4) - { - // start array with count (80..84) - oa->write_character(static_cast(0x80 + N)); - } - else - { - // start array - oa->write_character(to_char_type(0x85)); - } - - // write each element - for (std::size_t i = 0; i < N; ++i) - { - const auto& el = j.m_value.array->operator[](i); - - // check if 0xFF after nonempty string and string is required - if (i > 0) - { - const auto& prev = j.m_value.array->operator[](i - 1); - if (el.is_string() && prev.is_string() && !prev.m_value.string->empty()) - { - oa->write_character(to_char_type(0xFF)); - } - } - - write_bon8(el); - } - - if (N > 4) - { - // end of container - oa->write_character(to_char_type(0xFE)); - } - break; - } - - case value_t::object: - { - const auto N = j.m_value.object->size(); - if (N <= 4) - { - // start object with count (86..8A) - oa->write_character(static_cast(0x86 + N)); - } - else - { - // start object - oa->write_character(to_char_type(0x8B)); - } - - // write each element - for (auto it = j.m_value.object->begin(); it != j.m_value.object->end(); ++it) - { - const auto& key = it->first; - const auto& value = it->second; - - write_bon8(key); - - // check if we need a 0xFF separator between key and value - if (!key.empty() && value.is_string()) - { - oa->write_character(to_char_type(0xFF)); - } - - write_bon8(value); - - // check if we need a 0xFF separator between the value and the next key - if (value.is_string() && !value.m_value.string->empty() && std::next(it) != j.m_value.object->end()) - { - oa->write_character(to_char_type(0xFF)); - } - } - - if (N > 4) - { - // end of container - oa->write_character(to_char_type(0xFE)); - } - break; - } - - case value_t::binary: - case value_t::discarded: - default: - break; + oa->write_character(to_char_type(0xFF)); } } @@ -15270,6 +15114,181 @@ class binary_writer // BON8 // ////////// + /*! + * @param j + * @return whether the last written value was a string + */ + bool write_bon8_internal(const BasicJsonType& j) + { + switch (j.type()) + { + case value_t::null: + { + oa->write_character(to_char_type(0xFA)); + return false; + } + + case value_t::boolean: + { + oa->write_character(j.m_value.boolean + ? to_char_type(0xF9) + : to_char_type(0xF8)); + return false; + } + + case value_t::number_unsigned: + { + if (j.m_value.number_unsigned > static_cast((std::numeric_limits::max)())) + { + JSON_THROW(out_of_range::create(407, "integer number " + std::to_string(j.m_value.number_unsigned) + " cannot be represented by BON8 as it does not fit int64", j)); + } + write_bon8_integer(static_cast(j.m_value.number_unsigned)); + return false; + } + + case value_t::number_integer: + { + write_bon8_integer(j.m_value.number_integer); + return false; + } + + case value_t::number_float: + { + // special values + if (j.m_value.number_float == -1.0) + { + oa->write_character(to_char_type(0xFB)); + } + else if (j.m_value.number_float == 0.0 && !std::signbit(j.m_value.number_float)) + { + oa->write_character(to_char_type(0xFC)); + } + else if (j.m_value.number_float == 1.0) + { + oa->write_character(to_char_type(0xFD)); + } + else + { + // write float with prefix + write_compact_float(j.m_value.number_float, detail::input_format_t::bon8); + } + return false; + } + + case value_t::string: + { + // empty string: use end-of-text symbol + if (j.m_value.string->empty()) + { + oa->write_character(to_char_type(0xFF)); + return false; // already wrote 0xFF byte + } + + // write strings as is + oa->write_characters( + reinterpret_cast(j.m_value.string->c_str()), + j.m_value.string->size()); + return true; + } + + case value_t::array: + { + bool last_written_value_is_string = false; + const auto N = j.m_value.array->size(); + if (N <= 4) + { + // start array with count (80..84) + oa->write_character(static_cast(0x80 + N)); + } + else + { + // start array + oa->write_character(to_char_type(0x85)); + } + + // write each element + for (std::size_t i = 0; i < N; ++i) + { + const auto& el = j.m_value.array->operator[](i); + + // check if 0xFF after nonempty string and string is required + if (i > 0) + { + const auto& prev = j.m_value.array->operator[](i - 1); + if (el.is_string() && prev.is_string() && !prev.m_value.string->empty()) + { + oa->write_character(to_char_type(0xFF)); + } + } + + last_written_value_is_string = write_bon8_internal(el); + } + + if (N > 4) + { + // end of container + oa->write_character(to_char_type(0xFE)); + last_written_value_is_string = false; // 0xFE is not a string byte + } + + return last_written_value_is_string; + } + + case value_t::object: + { + bool last_written_value_is_string = false; + const auto N = j.m_value.object->size(); + if (N <= 4) + { + // start object with count (86..8A) + oa->write_character(static_cast(0x86 + N)); + } + else + { + // start object + oa->write_character(to_char_type(0x8B)); + } + + // write each element + for (auto it = j.m_value.object->begin(); it != j.m_value.object->end(); ++it) + { + const auto& key = it->first; + const auto& value = it->second; + + write_bon8_internal(key); + + // check if we need a 0xFF separator between key and value + if (!key.empty() && value.is_string()) + { + oa->write_character(to_char_type(0xFF)); + } + + last_written_value_is_string = write_bon8_internal(value); + + // check if we need a 0xFF separator between the value and the next key + if (value.is_string() && !value.m_value.string->empty() && std::next(it) != j.m_value.object->end()) + { + oa->write_character(to_char_type(0xFF)); + } + } + + if (N > 4) + { + // end of container + oa->write_character(to_char_type(0xFE)); + last_written_value_is_string = false; // 0xFE is not a string byte + } + + return last_written_value_is_string; + } + + case value_t::binary: + case value_t::discarded: + default: + return false; + } + } + void write_bon8_integer(typename BasicJsonType::number_integer_t value) { if (value < (std::numeric_limits::min)() || value > (std::numeric_limits::max)()) diff --git a/test/src/unit-bon8.cpp b/test/src/unit-bon8.cpp index 4d8d2546a..7be727343 100644 --- a/test/src/unit-bon8.cpp +++ b/test/src/unit-bon8.cpp @@ -481,7 +481,7 @@ TEST_CASE("BON8") SECTION("other strings") { json j = "This is a string."; - std::vector expected = {'T', 'h', 'i', 's', ' ', 'i', 's', ' ', 'a', ' ', 's', 't', 'r', 'i', 'n', 'g', '.'}; + std::vector expected = {'T', 'h', 'i', 's', ' ', 'i', 's', ' ', 'a', ' ', 's', 't', 'r', 'i', 'n', 'g', '.', 0xFF}; const auto result = json::to_bon8(j); CHECK(result == expected); } @@ -534,7 +534,7 @@ TEST_CASE("BON8") SECTION("[\"s\", \"s\"]") { json j = {"s", "s"}; - std::vector expected = {0x82, 's', 0xFF, 's'}; + std::vector expected = {0x82, 's', 0xFF, 's', 0xFF}; const auto result = json::to_bon8(j); CHECK(result == expected); } @@ -542,7 +542,31 @@ TEST_CASE("BON8") SECTION("[\"\", \"s\"]") { json j = {"", "s"}; - std::vector expected = {0x82, 0xFF, 's'}; + std::vector expected = {0x82, 0xFF, 's', 0xFF}; + const auto result = json::to_bon8(j); + CHECK(result == expected); + } + + SECTION("[[[\"foo\"]]]") + { + json j = R"([[["foo"]]])"_json; + std::vector expected = {0x81, 0x81, 0x81, 'f', 'o', 'o', 0xFF}; + const auto result = json::to_bon8(j); + CHECK(result == expected); + } + + SECTION("[[[1]]]") + { + json j = R"([[[1]]])"_json; + std::vector expected = {0x81, 0x81, 0x81, 0x91}; + const auto result = json::to_bon8(j); + CHECK(result == expected); + } + + SECTION("[[[\"\"]]]") + { + json j = R"([[[""]]])"_json; + std::vector expected = {0x81, 0x81, 0x81, 0xFF}; const auto result = json::to_bon8(j); CHECK(result == expected); } @@ -591,7 +615,15 @@ TEST_CASE("BON8") SECTION("{\"a\": \"\", \"c\": \"d\"}") { json j = {{"a", ""}, {"c", "d"}}; - std::vector expected = {0x88, 'a', 0xFF, 0xFF, 'c', 0xFF, 'd'}; + std::vector expected = {0x88, 'a', 0xFF, 0xFF, 'c', 0xFF, 'd', 0xFF}; + const auto result = json::to_bon8(j); + CHECK(result == expected); + } + + SECTION("{\"a\": \"b\", \"c\": \"d\"}") + { + json j = {{"a", "b"}, {"c", "d"}}; + std::vector expected = {0x88, 'a', 0xFF, 'b', 0xFF, 'c', 0xFF, 'd', 0xFF}; const auto result = json::to_bon8(j); CHECK(result == expected); }