From fe90fa51813fd7179919c8b5f75e93e9e877e71d Mon Sep 17 00:00:00 2001 From: Evan Driscoll Date: Fri, 1 Jun 2018 23:11:34 -0500 Subject: [PATCH 01/41] Run 'make almalgamate' astyle reformatted some stuff... I... don't know why. --- .../nlohmann/detail/conversions/to_chars.hpp | 7 ++- include/nlohmann/detail/output/serializer.hpp | 2 +- include/nlohmann/json.hpp | 2 +- single_include/nlohmann/json.hpp | 45 +++++++++---------- test/src/unit-conversions.cpp | 6 +-- test/src/unit-inspection.cpp | 4 +- test/src/unit-noexcept.cpp | 14 +++--- test/src/unit-udt.cpp | 2 +- 8 files changed, 40 insertions(+), 42 deletions(-) diff --git a/include/nlohmann/detail/conversions/to_chars.hpp b/include/nlohmann/detail/conversions/to_chars.hpp index 186b62ef1..93837baa6 100644 --- a/include/nlohmann/detail/conversions/to_chars.hpp +++ b/include/nlohmann/detail/conversions/to_chars.hpp @@ -196,7 +196,7 @@ boundaries compute_boundaries(FloatType value) constexpr int kMinExp = 1 - kBias; constexpr uint64_t kHiddenBit = uint64_t{1} << (kPrecision - 1); // = 2^(p-1) - using bits_type = typename std::conditional< kPrecision == 24, uint32_t, uint64_t >::type; + using bits_type = typename std::conditional::type; const uint64_t bits = reinterpret_bits(value); const uint64_t E = bits >> (kPrecision - 1); @@ -607,7 +607,10 @@ inline void grisu2_digit_gen(char* buffer, int& length, int& decimal_exponent, // = ((p1 ) * 2^-e + (p2 )) * 2^e // = p1 + p2 * 2^e - const diyfp one(uint64_t{1} << -M_plus.e, M_plus.e); + const diyfp one(uint64_t + { + 1 + } << -M_plus.e, M_plus.e); uint32_t p1 = static_cast(M_plus.f >> -one.e); // p1 = f div 2^-e (Since -e >= 32, p1 fits into a 32-bit int.) uint64_t p2 = M_plus.f & (one.f - 1); // p2 = f mod 2^-e diff --git a/include/nlohmann/detail/output/serializer.hpp b/include/nlohmann/detail/output/serializer.hpp index 19ab9894a..c0f817766 100644 --- a/include/nlohmann/detail/output/serializer.hpp +++ b/include/nlohmann/detail/output/serializer.hpp @@ -414,7 +414,7 @@ class serializer else { // we finish reading, but do not accept: string was incomplete - std::string sn(3,'\0'); + std::string sn(3, '\0'); snprintf(&sn[0], sn.size(), "%.2X", static_cast(s.back())); JSON_THROW(type_error::create(316, "incomplete UTF-8 string; last byte: 0x" + sn)); } diff --git a/include/nlohmann/json.hpp b/include/nlohmann/json.hpp index f5b93632f..dedd2edc2 100644 --- a/include/nlohmann/json.hpp +++ b/include/nlohmann/json.hpp @@ -7667,7 +7667,7 @@ struct hash /// @note: do not remove the space after '<', /// see https://github.com/nlohmann/json/pull/679 template<> -struct less< ::nlohmann::detail::value_t> +struct less<::nlohmann::detail::value_t> { /*! @brief compare two value_t enum values diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 2b387a8b6..5191b474d 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -1973,10 +1973,8 @@ class input_adapter #include // localeconv #include // size_t #include // strtof, strtod, strtold, strtoll, strtoull +#include // snprintf #include // initializer_list -#include // hex, uppercase -#include // setw, setfill -#include // stringstream #include // char_traits, string #include // vector @@ -3146,10 +3144,9 @@ scan_number_done: if ('\x00' <= c and c <= '\x1F') { // escape control characters - std::stringstream ss; - ss << "(c) << ">"; - result += ss.str(); + char cs[9]; + snprintf(cs, 9, "", c); + result += cs; } else { @@ -5619,12 +5616,10 @@ class output_adapter #include // ldexp #include // size_t #include // uint8_t, uint16_t, uint32_t, uint64_t +#include // snprintf #include // memcpy -#include // setw, setfill -#include // hex #include // back_inserter #include // numeric_limits -#include // stringstream #include // char_traits, string #include // make_pair, move @@ -7283,9 +7278,9 @@ class binary_reader */ std::string get_token_string() const { - std::stringstream ss; - ss << std::setw(2) << std::uppercase << std::setfill('0') << std::hex << current; - return ss.str(); + char cr[3]; + snprintf(cr, 3, "%.2X", current); + return std::string{cr}; } private: @@ -8272,11 +8267,8 @@ class binary_writer #include // size_t, ptrdiff_t #include // uint8_t #include // snprintf -#include // setfill -#include // next #include // numeric_limits #include // string -#include // stringstream #include // is_same // #include @@ -8480,7 +8472,7 @@ boundaries compute_boundaries(FloatType value) constexpr int kMinExp = 1 - kBias; constexpr uint64_t kHiddenBit = uint64_t{1} << (kPrecision - 1); // = 2^(p-1) - using bits_type = typename std::conditional< kPrecision == 24, uint32_t, uint64_t >::type; + using bits_type = typename std::conditional::type; const uint64_t bits = reinterpret_bits(value); const uint64_t E = bits >> (kPrecision - 1); @@ -8891,7 +8883,10 @@ inline void grisu2_digit_gen(char* buffer, int& length, int& decimal_exponent, // = ((p1 ) * 2^-e + (p2 )) * 2^e // = p1 + p2 * 2^e - const diyfp one(uint64_t{1} << -M_plus.e, M_plus.e); + const diyfp one(uint64_t + { + 1 + } << -M_plus.e, M_plus.e); uint32_t p1 = static_cast(M_plus.f >> -one.e); // p1 = f div 2^-e (Since -e >= 32, p1 fits into a 32-bit int.) uint64_t p2 = M_plus.f & (one.f - 1); // p2 = f mod 2^-e @@ -9753,9 +9748,9 @@ class serializer case UTF8_REJECT: // decode found invalid UTF-8 byte { - std::stringstream ss; - ss << std::setw(2) << std::uppercase << std::setfill('0') << std::hex << static_cast(byte); - JSON_THROW(type_error::create(316, "invalid UTF-8 byte at index " + std::to_string(i) + ": 0x" + ss.str())); + std::string sn(3, '\0'); + snprintf(&sn[0], sn.size(), "%.2X", byte); + JSON_THROW(type_error::create(316, "invalid UTF-8 byte at index " + std::to_string(i) + ": 0x" + sn)); } default: // decode found yet incomplete multi-byte code point @@ -9781,9 +9776,9 @@ class serializer else { // we finish reading, but do not accept: string was incomplete - std::stringstream ss; - ss << std::setw(2) << std::uppercase << std::setfill('0') << std::hex << static_cast(static_cast(s.back())); - JSON_THROW(type_error::create(316, "incomplete UTF-8 string; last byte: 0x" + ss.str())); + std::string sn(3, '\0'); + snprintf(&sn[0], sn.size(), "%.2X", static_cast(s.back())); + JSON_THROW(type_error::create(316, "incomplete UTF-8 string; last byte: 0x" + sn)); } } @@ -18411,7 +18406,7 @@ struct hash /// @note: do not remove the space after '<', /// see https://github.com/nlohmann/json/pull/679 template<> -struct less< ::nlohmann::detail::value_t> +struct less<::nlohmann::detail::value_t> { /*! @brief compare two value_t enum values diff --git a/test/src/unit-conversions.cpp b/test/src/unit-conversions.cpp index 79ccf6810..0e1a4874c 100644 --- a/test/src/unit-conversions.cpp +++ b/test/src/unit-conversions.cpp @@ -1093,9 +1093,9 @@ TEST_CASE("value conversion") SECTION("superfluous entries") { - json j8 = {{0, 1, 2}, {1, 2, 3}, {2, 3, 4}}; - m2 = j8.get>(); - CHECK(m == m2); + json j8 = {{0, 1, 2}, {1, 2, 3}, {2, 3, 4}}; + m2 = j8.get>(); + CHECK(m == m2); } } diff --git a/test/src/unit-inspection.cpp b/test/src/unit-inspection.cpp index 4c03cf968..e50c7338e 100644 --- a/test/src/unit-inspection.cpp +++ b/test/src/unit-inspection.cpp @@ -317,8 +317,8 @@ TEST_CASE("object inspection") SECTION("round trips") { for (const auto& s : - {"3.141592653589793", "1000000000000000010E5" - }) + {"3.141592653589793", "1000000000000000010E5" + }) { json j1 = json::parse(s); std::string s1 = j1.dump(); diff --git a/test/src/unit-noexcept.cpp b/test/src/unit-noexcept.cpp index 2d49b3272..358d6bb93 100644 --- a/test/src/unit-noexcept.cpp +++ b/test/src/unit-noexcept.cpp @@ -45,19 +45,19 @@ void from_json(const json&, pod) noexcept; void from_json(const json&, pod_bis); static json j; -static_assert(noexcept(json{}), ""); +static_assert(noexcept(json {}), ""); static_assert(noexcept(nlohmann::to_json(j, 2)), ""); static_assert(noexcept(nlohmann::to_json(j, 2.5)), ""); static_assert(noexcept(nlohmann::to_json(j, true)), ""); -static_assert(noexcept(nlohmann::to_json(j, test{})), ""); -static_assert(noexcept(nlohmann::to_json(j, pod{})), ""); -static_assert(not noexcept(nlohmann::to_json(j, pod_bis{})), ""); +static_assert(noexcept(nlohmann::to_json(j, test {})), ""); +static_assert(noexcept(nlohmann::to_json(j, pod {})), ""); +static_assert(not noexcept(nlohmann::to_json(j, pod_bis {})), ""); static_assert(noexcept(json(2)), ""); -static_assert(noexcept(json(test{})), ""); -static_assert(noexcept(json(pod{})), ""); +static_assert(noexcept(json(test {})), ""); +static_assert(noexcept(json(pod {})), ""); static_assert(noexcept(j.get()), ""); static_assert(not noexcept(j.get()), ""); -static_assert(noexcept(json(pod{})), ""); +static_assert(noexcept(json(pod {})), ""); TEST_CASE("runtime checks") { diff --git a/test/src/unit-udt.cpp b/test/src/unit-udt.cpp index 040c3e980..3807d212f 100644 --- a/test/src/unit-udt.cpp +++ b/test/src/unit-udt.cpp @@ -585,7 +585,7 @@ struct pod_serializer std::is_pod::value and std::is_class::value, int>::type = 0> static void to_json(BasicJsonType& j, const T& t) noexcept { - auto bytes = static_cast< const unsigned char*>(static_cast(&t)); + auto bytes = static_cast(static_cast(&t)); std::uint64_t value = bytes[0]; for (auto i = 1; i < 8; ++i) value |= std::uint64_t{bytes[i]} << 8 * i; From b584000e7c33b386d64556bfbfbdc27a1ed73ff5 Mon Sep 17 00:00:00 2001 From: Evan Driscoll Date: Fri, 1 Jun 2018 22:21:27 -0500 Subject: [PATCH 02/41] Fancy serialiaztion: copy old serialization test file Just copy unit-serialization to unit-fancy-serialization --- test/src/unit-fancy-serialization.cpp | 97 +++++++++++++++++++++++++++ 1 file changed, 97 insertions(+) create mode 100644 test/src/unit-fancy-serialization.cpp diff --git a/test/src/unit-fancy-serialization.cpp b/test/src/unit-fancy-serialization.cpp new file mode 100644 index 000000000..3abc7fb19 --- /dev/null +++ b/test/src/unit-fancy-serialization.cpp @@ -0,0 +1,97 @@ +/* + __ _____ _____ _____ + __| | __| | | | JSON for Modern C++ (test suite) +| | |__ | | | | | | version 3.1.2 +|_____|_____|_____|_|___| https://github.com/nlohmann/json + +Licensed under the MIT License . +SPDX-License-Identifier: MIT +Copyright (c) 2013-2018 Niels Lohmann . + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#include "catch.hpp" + +#include +using nlohmann::json; + +TEST_CASE("serialization") +{ + SECTION("operator<<") + { + SECTION("no given width") + { + std::stringstream ss; + json j = {"foo", 1, 2, 3, false, {{"one", 1}}}; + ss << j; + CHECK(ss.str() == "[\"foo\",1,2,3,false,{\"one\":1}]"); + } + + SECTION("given width") + { + std::stringstream ss; + json j = {"foo", 1, 2, 3, false, {{"one", 1}}}; + ss << std::setw(4) << j; + CHECK(ss.str() == + "[\n \"foo\",\n 1,\n 2,\n 3,\n false,\n {\n \"one\": 1\n }\n]"); + } + + SECTION("given fill") + { + std::stringstream ss; + json j = {"foo", 1, 2, 3, false, {{"one", 1}}}; + ss << std::setw(1) << std::setfill('\t') << j; + CHECK(ss.str() == + "[\n\t\"foo\",\n\t1,\n\t2,\n\t3,\n\tfalse,\n\t{\n\t\t\"one\": 1\n\t}\n]"); + } + } + + SECTION("operator>>") + { + SECTION("no given width") + { + std::stringstream ss; + json j = {"foo", 1, 2, 3, false, {{"one", 1}}}; + j >> ss; + CHECK(ss.str() == "[\"foo\",1,2,3,false,{\"one\":1}]"); + } + + SECTION("given width") + { + std::stringstream ss; + json j = {"foo", 1, 2, 3, false, {{"one", 1}}}; + ss.width(4); + j >> ss; + CHECK(ss.str() == + "[\n \"foo\",\n 1,\n 2,\n 3,\n false,\n {\n \"one\": 1\n }\n]"); + } + + SECTION("given fill") + { + std::stringstream ss; + json j = {"foo", 1, 2, 3, false, {{"one", 1}}}; + ss.width(1); + ss.fill('\t'); + j >> ss; + CHECK(ss.str() == + "[\n\t\"foo\",\n\t1,\n\t2,\n\t3,\n\tfalse,\n\t{\n\t\t\"one\": 1\n\t}\n]"); + } + } +} From 607b9730359df55194a7dbe298d789f67f86c9fc Mon Sep 17 00:00:00 2001 From: Evan Driscoll Date: Fri, 1 Jun 2018 23:01:15 -0500 Subject: [PATCH 03/41] Fancy Serialization: copy serializer to fancy_serializer 1. Copy header and rename; add to Makefile; amalgamate 2. Add as friend to basic_json 3. Copy operator<< to 'fancy_dump' and simplify 4. Change test to refer to that --- Makefile | 1 + .../detail/output/fancy_serializer.hpp | 641 +++++++++++++++++ include/nlohmann/json.hpp | 2 + single_include/nlohmann/json.hpp | 650 ++++++++++++++++++ test/src/unit-fancy-serialization.cpp | 54 +- 5 files changed, 1297 insertions(+), 51 deletions(-) create mode 100644 include/nlohmann/detail/output/fancy_serializer.hpp diff --git a/Makefile b/Makefile index 1e1217880..26a5ae5f9 100644 --- a/Makefile +++ b/Makefile @@ -25,6 +25,7 @@ SRCS = include/nlohmann/json.hpp \ include/nlohmann/detail/output/binary_writer.hpp \ include/nlohmann/detail/output/output_adapters.hpp \ include/nlohmann/detail/output/serializer.hpp \ + include/nlohmann/detail/output/fancy_serializer.hpp \ include/nlohmann/detail/value_t.hpp UNAME = $(shell uname) diff --git a/include/nlohmann/detail/output/fancy_serializer.hpp b/include/nlohmann/detail/output/fancy_serializer.hpp new file mode 100644 index 000000000..97191d6fe --- /dev/null +++ b/include/nlohmann/detail/output/fancy_serializer.hpp @@ -0,0 +1,641 @@ +#pragma once + +#include // reverse, remove, fill, find, none_of +#include // array +#include // assert +#include // and, or +#include // localeconv, lconv +#include // labs, isfinite, isnan, signbit +#include // size_t, ptrdiff_t +#include // uint8_t +#include // snprintf +#include // numeric_limits +#include // string +#include // is_same + +#include +#include +#include +#include +#include +#include + +namespace nlohmann +{ +namespace detail +{ +/////////////////// +// serialization // +/////////////////// + +template +class fancy_serializer +{ + using string_t = typename BasicJsonType::string_t; + using number_float_t = typename BasicJsonType::number_float_t; + using number_integer_t = typename BasicJsonType::number_integer_t; + using number_unsigned_t = typename BasicJsonType::number_unsigned_t; + static constexpr uint8_t UTF8_ACCEPT = 0; + static constexpr uint8_t UTF8_REJECT = 1; + + public: + /*! + @param[in] s output stream to serialize to + @param[in] ichar indentation character to use + */ + fancy_serializer(output_adapter_t s, const char ichar) + : o(std::move(s)), loc(std::localeconv()), + thousands_sep(loc->thousands_sep == nullptr ? '\0' : * (loc->thousands_sep)), + decimal_point(loc->decimal_point == nullptr ? '\0' : * (loc->decimal_point)), + indent_char(ichar), indent_string(512, indent_char) + {} + + // delete because of pointer members + fancy_serializer(const fancy_serializer&) = delete; + fancy_serializer& operator=(const fancy_serializer&) = delete; + + /*! + @brief internal implementation of the serialization function + + This function is called by the public member function dump and organizes + the serialization internally. The indentation level is propagated as + additional parameter. In case of arrays and objects, the function is + called recursively. + + - strings and object keys are escaped using `escape_string()` + - integer numbers are converted implicitly via `operator<<` + - floating-point numbers are converted to a string using `"%g"` format + + @param[in] val value to serialize + @param[in] pretty_print whether the output shall be pretty-printed + @param[in] indent_step the indent level + @param[in] current_indent the current indent level (only used internally) + */ + void dump(const BasicJsonType& val, const bool pretty_print, + const bool ensure_ascii, + const unsigned int indent_step, + const unsigned int current_indent = 0) + { + switch (val.m_type) + { + case value_t::object: + { + if (val.m_value.object->empty()) + { + o->write_characters("{}", 2); + return; + } + + if (pretty_print) + { + o->write_characters("{\n", 2); + + // variable to hold indentation for recursive calls + const auto new_indent = current_indent + indent_step; + if (JSON_UNLIKELY(indent_string.size() < new_indent)) + { + indent_string.resize(indent_string.size() * 2, ' '); + } + + // first n-1 elements + auto i = val.m_value.object->cbegin(); + for (std::size_t cnt = 0; cnt < val.m_value.object->size() - 1; ++cnt, ++i) + { + o->write_characters(indent_string.c_str(), new_indent); + o->write_character('\"'); + dump_escaped(i->first, ensure_ascii); + o->write_characters("\": ", 3); + dump(i->second, true, ensure_ascii, indent_step, new_indent); + o->write_characters(",\n", 2); + } + + // last element + assert(i != val.m_value.object->cend()); + assert(std::next(i) == val.m_value.object->cend()); + o->write_characters(indent_string.c_str(), new_indent); + o->write_character('\"'); + dump_escaped(i->first, ensure_ascii); + o->write_characters("\": ", 3); + dump(i->second, true, ensure_ascii, indent_step, new_indent); + + o->write_character('\n'); + o->write_characters(indent_string.c_str(), current_indent); + o->write_character('}'); + } + else + { + o->write_character('{'); + + // first n-1 elements + auto i = val.m_value.object->cbegin(); + for (std::size_t cnt = 0; cnt < val.m_value.object->size() - 1; ++cnt, ++i) + { + o->write_character('\"'); + dump_escaped(i->first, ensure_ascii); + o->write_characters("\":", 2); + dump(i->second, false, ensure_ascii, indent_step, current_indent); + o->write_character(','); + } + + // last element + assert(i != val.m_value.object->cend()); + assert(std::next(i) == val.m_value.object->cend()); + o->write_character('\"'); + dump_escaped(i->first, ensure_ascii); + o->write_characters("\":", 2); + dump(i->second, false, ensure_ascii, indent_step, current_indent); + + o->write_character('}'); + } + + return; + } + + case value_t::array: + { + if (val.m_value.array->empty()) + { + o->write_characters("[]", 2); + return; + } + + if (pretty_print) + { + o->write_characters("[\n", 2); + + // variable to hold indentation for recursive calls + const auto new_indent = current_indent + indent_step; + if (JSON_UNLIKELY(indent_string.size() < new_indent)) + { + indent_string.resize(indent_string.size() * 2, ' '); + } + + // first n-1 elements + for (auto i = val.m_value.array->cbegin(); + i != val.m_value.array->cend() - 1; ++i) + { + o->write_characters(indent_string.c_str(), new_indent); + dump(*i, true, ensure_ascii, indent_step, new_indent); + o->write_characters(",\n", 2); + } + + // last element + assert(not val.m_value.array->empty()); + o->write_characters(indent_string.c_str(), new_indent); + dump(val.m_value.array->back(), true, ensure_ascii, indent_step, new_indent); + + o->write_character('\n'); + o->write_characters(indent_string.c_str(), current_indent); + o->write_character(']'); + } + else + { + o->write_character('['); + + // first n-1 elements + for (auto i = val.m_value.array->cbegin(); + i != val.m_value.array->cend() - 1; ++i) + { + dump(*i, false, ensure_ascii, indent_step, current_indent); + o->write_character(','); + } + + // last element + assert(not val.m_value.array->empty()); + dump(val.m_value.array->back(), false, ensure_ascii, indent_step, current_indent); + + o->write_character(']'); + } + + return; + } + + case value_t::string: + { + o->write_character('\"'); + dump_escaped(*val.m_value.string, ensure_ascii); + o->write_character('\"'); + return; + } + + case value_t::boolean: + { + if (val.m_value.boolean) + { + o->write_characters("true", 4); + } + else + { + o->write_characters("false", 5); + } + return; + } + + case value_t::number_integer: + { + dump_integer(val.m_value.number_integer); + return; + } + + case value_t::number_unsigned: + { + dump_integer(val.m_value.number_unsigned); + return; + } + + case value_t::number_float: + { + dump_float(val.m_value.number_float); + return; + } + + case value_t::discarded: + { + o->write_characters("", 11); + return; + } + + case value_t::null: + { + o->write_characters("null", 4); + return; + } + } + } + + private: + /*! + @brief dump escaped string + + Escape a string by replacing certain special characters by a sequence of an + escape character (backslash) and another character and other control + characters by a sequence of "\u" followed by a four-digit hex + representation. The escaped string is written to output stream @a o. + + @param[in] s the string to escape + @param[in] ensure_ascii whether to escape non-ASCII characters with + \uXXXX sequences + + @complexity Linear in the length of string @a s. + */ + void dump_escaped(const string_t& s, const bool ensure_ascii) + { + uint32_t codepoint; + uint8_t state = UTF8_ACCEPT; + std::size_t bytes = 0; // number of bytes written to string_buffer + + for (std::size_t i = 0; i < s.size(); ++i) + { + const auto byte = static_cast(s[i]); + + switch (decode(state, codepoint, byte)) + { + case UTF8_ACCEPT: // decode found a new code point + { + switch (codepoint) + { + case 0x08: // backspace + { + string_buffer[bytes++] = '\\'; + string_buffer[bytes++] = 'b'; + break; + } + + case 0x09: // horizontal tab + { + string_buffer[bytes++] = '\\'; + string_buffer[bytes++] = 't'; + break; + } + + case 0x0A: // newline + { + string_buffer[bytes++] = '\\'; + string_buffer[bytes++] = 'n'; + break; + } + + case 0x0C: // formfeed + { + string_buffer[bytes++] = '\\'; + string_buffer[bytes++] = 'f'; + break; + } + + case 0x0D: // carriage return + { + string_buffer[bytes++] = '\\'; + string_buffer[bytes++] = 'r'; + break; + } + + case 0x22: // quotation mark + { + string_buffer[bytes++] = '\\'; + string_buffer[bytes++] = '\"'; + break; + } + + case 0x5C: // reverse solidus + { + string_buffer[bytes++] = '\\'; + string_buffer[bytes++] = '\\'; + break; + } + + default: + { + // escape control characters (0x00..0x1F) or, if + // ensure_ascii parameter is used, non-ASCII characters + if ((codepoint <= 0x1F) or (ensure_ascii and (codepoint >= 0x7F))) + { + if (codepoint <= 0xFFFF) + { + std::snprintf(string_buffer.data() + bytes, 7, "\\u%04x", + static_cast(codepoint)); + bytes += 6; + } + else + { + std::snprintf(string_buffer.data() + bytes, 13, "\\u%04x\\u%04x", + static_cast(0xD7C0 + (codepoint >> 10)), + static_cast(0xDC00 + (codepoint & 0x3FF))); + bytes += 12; + } + } + else + { + // copy byte to buffer (all previous bytes + // been copied have in default case above) + string_buffer[bytes++] = s[i]; + } + break; + } + } + + // write buffer and reset index; there must be 13 bytes + // left, as this is the maximal number of bytes to be + // written ("\uxxxx\uxxxx\0") for one code point + if (string_buffer.size() - bytes < 13) + { + o->write_characters(string_buffer.data(), bytes); + bytes = 0; + } + break; + } + + case UTF8_REJECT: // decode found invalid UTF-8 byte + { + std::string sn(3, '\0'); + snprintf(&sn[0], sn.size(), "%.2X", byte); + JSON_THROW(type_error::create(316, "invalid UTF-8 byte at index " + std::to_string(i) + ": 0x" + sn)); + } + + default: // decode found yet incomplete multi-byte code point + { + if (not ensure_ascii) + { + // code point will not be escaped - copy byte to buffer + string_buffer[bytes++] = s[i]; + } + break; + } + } + } + + if (JSON_LIKELY(state == UTF8_ACCEPT)) + { + // write buffer + if (bytes > 0) + { + o->write_characters(string_buffer.data(), bytes); + } + } + else + { + // we finish reading, but do not accept: string was incomplete + std::string sn(3, '\0'); + snprintf(&sn[0], sn.size(), "%.2X", static_cast(s.back())); + JSON_THROW(type_error::create(316, "incomplete UTF-8 string; last byte: 0x" + sn)); + } + } + + /*! + @brief dump an integer + + Dump a given integer to output stream @a o. Works internally with + @a number_buffer. + + @param[in] x integer number (signed or unsigned) to dump + @tparam NumberType either @a number_integer_t or @a number_unsigned_t + */ + template::value or + std::is_same::value, + int> = 0> + void dump_integer(NumberType x) + { + // special case for "0" + if (x == 0) + { + o->write_character('0'); + return; + } + + const bool is_negative = (x <= 0) and (x != 0); // see issue #755 + std::size_t i = 0; + + while (x != 0) + { + // spare 1 byte for '\0' + assert(i < number_buffer.size() - 1); + + const auto digit = std::labs(static_cast(x % 10)); + number_buffer[i++] = static_cast('0' + digit); + x /= 10; + } + + if (is_negative) + { + // make sure there is capacity for the '-' + assert(i < number_buffer.size() - 2); + number_buffer[i++] = '-'; + } + + std::reverse(number_buffer.begin(), number_buffer.begin() + i); + o->write_characters(number_buffer.data(), i); + } + + /*! + @brief dump a floating-point number + + Dump a given floating-point number to output stream @a o. Works internally + with @a number_buffer. + + @param[in] x floating-point number to dump + */ + void dump_float(number_float_t x) + { + // NaN / inf + if (not std::isfinite(x)) + { + o->write_characters("null", 4); + return; + } + + // If number_float_t is an IEEE-754 single or double precision number, + // use the Grisu2 algorithm to produce short numbers which are + // guaranteed to round-trip, using strtof and strtod, resp. + // + // NB: The test below works if == . + static constexpr bool is_ieee_single_or_double + = (std::numeric_limits::is_iec559 and std::numeric_limits::digits == 24 and std::numeric_limits::max_exponent == 128) or + (std::numeric_limits::is_iec559 and std::numeric_limits::digits == 53 and std::numeric_limits::max_exponent == 1024); + + dump_float(x, std::integral_constant()); + } + + void dump_float(number_float_t x, std::true_type /*is_ieee_single_or_double*/) + { + char* begin = number_buffer.data(); + char* end = ::nlohmann::detail::to_chars(begin, begin + number_buffer.size(), x); + + o->write_characters(begin, static_cast(end - begin)); + } + + void dump_float(number_float_t x, std::false_type /*is_ieee_single_or_double*/) + { + // get number of digits for a float -> text -> float round-trip + static constexpr auto d = std::numeric_limits::max_digits10; + + // the actual conversion + std::ptrdiff_t len = snprintf(number_buffer.data(), number_buffer.size(), "%.*g", d, x); + + // negative value indicates an error + assert(len > 0); + // check if buffer was large enough + assert(static_cast(len) < number_buffer.size()); + + // erase thousands separator + if (thousands_sep != '\0') + { + const auto end = std::remove(number_buffer.begin(), + number_buffer.begin() + len, thousands_sep); + std::fill(end, number_buffer.end(), '\0'); + assert((end - number_buffer.begin()) <= len); + len = (end - number_buffer.begin()); + } + + // convert decimal point to '.' + if (decimal_point != '\0' and decimal_point != '.') + { + const auto dec_pos = std::find(number_buffer.begin(), number_buffer.end(), decimal_point); + if (dec_pos != number_buffer.end()) + { + *dec_pos = '.'; + } + } + + o->write_characters(number_buffer.data(), static_cast(len)); + + // determine if need to append ".0" + const bool value_is_int_like = + std::none_of(number_buffer.begin(), number_buffer.begin() + len + 1, + [](char c) + { + return (c == '.' or c == 'e'); + }); + + if (value_is_int_like) + { + o->write_characters(".0", 2); + } + } + + /*! + @brief check whether a string is UTF-8 encoded + + The function checks each byte of a string whether it is UTF-8 encoded. The + result of the check is stored in the @a state parameter. The function must + be called initially with state 0 (accept). State 1 means the string must + be rejected, because the current byte is not allowed. If the string is + completely processed, but the state is non-zero, the string ended + prematurely; that is, the last byte indicated more bytes should have + followed. + + @param[in,out] state the state of the decoding + @param[in,out] codep codepoint (valid only if resulting state is UTF8_ACCEPT) + @param[in] byte next byte to decode + @return new state + + @note The function has been edited: a std::array is used. + + @copyright Copyright (c) 2008-2009 Bjoern Hoehrmann + @sa http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ + */ + static uint8_t decode(uint8_t& state, uint32_t& codep, const uint8_t byte) noexcept + { + static const std::array utf8d = + { + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 00..1F + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 20..3F + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 40..5F + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 60..7F + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, // 80..9F + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, // A0..BF + 8, 8, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // C0..DF + 0xA, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x4, 0x3, 0x3, // E0..EF + 0xB, 0x6, 0x6, 0x6, 0x5, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, // F0..FF + 0x0, 0x1, 0x2, 0x3, 0x5, 0x8, 0x7, 0x1, 0x1, 0x1, 0x4, 0x6, 0x1, 0x1, 0x1, 0x1, // s0..s0 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, // s1..s2 + 1, 2, 1, 1, 1, 1, 1, 2, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, // s3..s4 + 1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, // s5..s6 + 1, 3, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 // s7..s8 + } + }; + + const uint8_t type = utf8d[byte]; + + codep = (state != UTF8_ACCEPT) + ? (byte & 0x3fu) | (codep << 6) + : static_cast(0xff >> type) & (byte); + + state = utf8d[256u + state * 16u + type]; + return state; + } + + private: + /// the output of the fancy_serializer + output_adapter_t o = nullptr; + + /// a (hopefully) large enough character buffer + std::array number_buffer{{}}; + + /// the locale + const std::lconv* loc = nullptr; + /// the locale's thousand separator character + const char thousands_sep = '\0'; + /// the locale's decimal point character + const char decimal_point = '\0'; + + /// string buffer + std::array string_buffer{{}}; + + /// the indentation character + const char indent_char; + /// the indentation string + string_t indent_string; +}; +} + +template +std::ostream& fancy_dump(std::ostream& o, const BasicJsonType& j) +{ + // do the actual serialization + detail::fancy_serializer s(detail::output_adapter(o), o.fill()); + s.dump(j, false, false, 0u); + return o; +} + +} diff --git a/include/nlohmann/json.hpp b/include/nlohmann/json.hpp index dedd2edc2..b2836b6ca 100644 --- a/include/nlohmann/json.hpp +++ b/include/nlohmann/json.hpp @@ -65,6 +65,7 @@ SOFTWARE. #include #include #include +#include #include #include #include @@ -166,6 +167,7 @@ class basic_json friend ::nlohmann::json_pointer; friend ::nlohmann::detail::parser; friend ::nlohmann::detail::serializer; + friend ::nlohmann::detail::fancy_serializer; template friend class ::nlohmann::detail::iter_impl; template diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 5191b474d..c4cad9886 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -9992,6 +9992,655 @@ class serializer } } +// #include + + +#include // reverse, remove, fill, find, none_of +#include // array +#include // assert +#include // and, or +#include // localeconv, lconv +#include // labs, isfinite, isnan, signbit +#include // size_t, ptrdiff_t +#include // uint8_t +#include // snprintf +#include // numeric_limits +#include // string +#include // is_same + +// #include + +// #include + +// #include + +// #include + +// #include + +// #include + + +namespace nlohmann +{ +namespace detail +{ +/////////////////// +// serialization // +/////////////////// + +template +class fancy_serializer +{ + using string_t = typename BasicJsonType::string_t; + using number_float_t = typename BasicJsonType::number_float_t; + using number_integer_t = typename BasicJsonType::number_integer_t; + using number_unsigned_t = typename BasicJsonType::number_unsigned_t; + static constexpr uint8_t UTF8_ACCEPT = 0; + static constexpr uint8_t UTF8_REJECT = 1; + + public: + /*! + @param[in] s output stream to serialize to + @param[in] ichar indentation character to use + */ + fancy_serializer(output_adapter_t s, const char ichar) + : o(std::move(s)), loc(std::localeconv()), + thousands_sep(loc->thousands_sep == nullptr ? '\0' : * (loc->thousands_sep)), + decimal_point(loc->decimal_point == nullptr ? '\0' : * (loc->decimal_point)), + indent_char(ichar), indent_string(512, indent_char) + {} + + // delete because of pointer members + fancy_serializer(const fancy_serializer&) = delete; + fancy_serializer& operator=(const fancy_serializer&) = delete; + + /*! + @brief internal implementation of the serialization function + + This function is called by the public member function dump and organizes + the serialization internally. The indentation level is propagated as + additional parameter. In case of arrays and objects, the function is + called recursively. + + - strings and object keys are escaped using `escape_string()` + - integer numbers are converted implicitly via `operator<<` + - floating-point numbers are converted to a string using `"%g"` format + + @param[in] val value to serialize + @param[in] pretty_print whether the output shall be pretty-printed + @param[in] indent_step the indent level + @param[in] current_indent the current indent level (only used internally) + */ + void dump(const BasicJsonType& val, const bool pretty_print, + const bool ensure_ascii, + const unsigned int indent_step, + const unsigned int current_indent = 0) + { + switch (val.m_type) + { + case value_t::object: + { + if (val.m_value.object->empty()) + { + o->write_characters("{}", 2); + return; + } + + if (pretty_print) + { + o->write_characters("{\n", 2); + + // variable to hold indentation for recursive calls + const auto new_indent = current_indent + indent_step; + if (JSON_UNLIKELY(indent_string.size() < new_indent)) + { + indent_string.resize(indent_string.size() * 2, ' '); + } + + // first n-1 elements + auto i = val.m_value.object->cbegin(); + for (std::size_t cnt = 0; cnt < val.m_value.object->size() - 1; ++cnt, ++i) + { + o->write_characters(indent_string.c_str(), new_indent); + o->write_character('\"'); + dump_escaped(i->first, ensure_ascii); + o->write_characters("\": ", 3); + dump(i->second, true, ensure_ascii, indent_step, new_indent); + o->write_characters(",\n", 2); + } + + // last element + assert(i != val.m_value.object->cend()); + assert(std::next(i) == val.m_value.object->cend()); + o->write_characters(indent_string.c_str(), new_indent); + o->write_character('\"'); + dump_escaped(i->first, ensure_ascii); + o->write_characters("\": ", 3); + dump(i->second, true, ensure_ascii, indent_step, new_indent); + + o->write_character('\n'); + o->write_characters(indent_string.c_str(), current_indent); + o->write_character('}'); + } + else + { + o->write_character('{'); + + // first n-1 elements + auto i = val.m_value.object->cbegin(); + for (std::size_t cnt = 0; cnt < val.m_value.object->size() - 1; ++cnt, ++i) + { + o->write_character('\"'); + dump_escaped(i->first, ensure_ascii); + o->write_characters("\":", 2); + dump(i->second, false, ensure_ascii, indent_step, current_indent); + o->write_character(','); + } + + // last element + assert(i != val.m_value.object->cend()); + assert(std::next(i) == val.m_value.object->cend()); + o->write_character('\"'); + dump_escaped(i->first, ensure_ascii); + o->write_characters("\":", 2); + dump(i->second, false, ensure_ascii, indent_step, current_indent); + + o->write_character('}'); + } + + return; + } + + case value_t::array: + { + if (val.m_value.array->empty()) + { + o->write_characters("[]", 2); + return; + } + + if (pretty_print) + { + o->write_characters("[\n", 2); + + // variable to hold indentation for recursive calls + const auto new_indent = current_indent + indent_step; + if (JSON_UNLIKELY(indent_string.size() < new_indent)) + { + indent_string.resize(indent_string.size() * 2, ' '); + } + + // first n-1 elements + for (auto i = val.m_value.array->cbegin(); + i != val.m_value.array->cend() - 1; ++i) + { + o->write_characters(indent_string.c_str(), new_indent); + dump(*i, true, ensure_ascii, indent_step, new_indent); + o->write_characters(",\n", 2); + } + + // last element + assert(not val.m_value.array->empty()); + o->write_characters(indent_string.c_str(), new_indent); + dump(val.m_value.array->back(), true, ensure_ascii, indent_step, new_indent); + + o->write_character('\n'); + o->write_characters(indent_string.c_str(), current_indent); + o->write_character(']'); + } + else + { + o->write_character('['); + + // first n-1 elements + for (auto i = val.m_value.array->cbegin(); + i != val.m_value.array->cend() - 1; ++i) + { + dump(*i, false, ensure_ascii, indent_step, current_indent); + o->write_character(','); + } + + // last element + assert(not val.m_value.array->empty()); + dump(val.m_value.array->back(), false, ensure_ascii, indent_step, current_indent); + + o->write_character(']'); + } + + return; + } + + case value_t::string: + { + o->write_character('\"'); + dump_escaped(*val.m_value.string, ensure_ascii); + o->write_character('\"'); + return; + } + + case value_t::boolean: + { + if (val.m_value.boolean) + { + o->write_characters("true", 4); + } + else + { + o->write_characters("false", 5); + } + return; + } + + case value_t::number_integer: + { + dump_integer(val.m_value.number_integer); + return; + } + + case value_t::number_unsigned: + { + dump_integer(val.m_value.number_unsigned); + return; + } + + case value_t::number_float: + { + dump_float(val.m_value.number_float); + return; + } + + case value_t::discarded: + { + o->write_characters("", 11); + return; + } + + case value_t::null: + { + o->write_characters("null", 4); + return; + } + } + } + + private: + /*! + @brief dump escaped string + + Escape a string by replacing certain special characters by a sequence of an + escape character (backslash) and another character and other control + characters by a sequence of "\u" followed by a four-digit hex + representation. The escaped string is written to output stream @a o. + + @param[in] s the string to escape + @param[in] ensure_ascii whether to escape non-ASCII characters with + \uXXXX sequences + + @complexity Linear in the length of string @a s. + */ + void dump_escaped(const string_t& s, const bool ensure_ascii) + { + uint32_t codepoint; + uint8_t state = UTF8_ACCEPT; + std::size_t bytes = 0; // number of bytes written to string_buffer + + for (std::size_t i = 0; i < s.size(); ++i) + { + const auto byte = static_cast(s[i]); + + switch (decode(state, codepoint, byte)) + { + case UTF8_ACCEPT: // decode found a new code point + { + switch (codepoint) + { + case 0x08: // backspace + { + string_buffer[bytes++] = '\\'; + string_buffer[bytes++] = 'b'; + break; + } + + case 0x09: // horizontal tab + { + string_buffer[bytes++] = '\\'; + string_buffer[bytes++] = 't'; + break; + } + + case 0x0A: // newline + { + string_buffer[bytes++] = '\\'; + string_buffer[bytes++] = 'n'; + break; + } + + case 0x0C: // formfeed + { + string_buffer[bytes++] = '\\'; + string_buffer[bytes++] = 'f'; + break; + } + + case 0x0D: // carriage return + { + string_buffer[bytes++] = '\\'; + string_buffer[bytes++] = 'r'; + break; + } + + case 0x22: // quotation mark + { + string_buffer[bytes++] = '\\'; + string_buffer[bytes++] = '\"'; + break; + } + + case 0x5C: // reverse solidus + { + string_buffer[bytes++] = '\\'; + string_buffer[bytes++] = '\\'; + break; + } + + default: + { + // escape control characters (0x00..0x1F) or, if + // ensure_ascii parameter is used, non-ASCII characters + if ((codepoint <= 0x1F) or (ensure_ascii and (codepoint >= 0x7F))) + { + if (codepoint <= 0xFFFF) + { + std::snprintf(string_buffer.data() + bytes, 7, "\\u%04x", + static_cast(codepoint)); + bytes += 6; + } + else + { + std::snprintf(string_buffer.data() + bytes, 13, "\\u%04x\\u%04x", + static_cast(0xD7C0 + (codepoint >> 10)), + static_cast(0xDC00 + (codepoint & 0x3FF))); + bytes += 12; + } + } + else + { + // copy byte to buffer (all previous bytes + // been copied have in default case above) + string_buffer[bytes++] = s[i]; + } + break; + } + } + + // write buffer and reset index; there must be 13 bytes + // left, as this is the maximal number of bytes to be + // written ("\uxxxx\uxxxx\0") for one code point + if (string_buffer.size() - bytes < 13) + { + o->write_characters(string_buffer.data(), bytes); + bytes = 0; + } + break; + } + + case UTF8_REJECT: // decode found invalid UTF-8 byte + { + std::string sn(3, '\0'); + snprintf(&sn[0], sn.size(), "%.2X", byte); + JSON_THROW(type_error::create(316, "invalid UTF-8 byte at index " + std::to_string(i) + ": 0x" + sn)); + } + + default: // decode found yet incomplete multi-byte code point + { + if (not ensure_ascii) + { + // code point will not be escaped - copy byte to buffer + string_buffer[bytes++] = s[i]; + } + break; + } + } + } + + if (JSON_LIKELY(state == UTF8_ACCEPT)) + { + // write buffer + if (bytes > 0) + { + o->write_characters(string_buffer.data(), bytes); + } + } + else + { + // we finish reading, but do not accept: string was incomplete + std::string sn(3, '\0'); + snprintf(&sn[0], sn.size(), "%.2X", static_cast(s.back())); + JSON_THROW(type_error::create(316, "incomplete UTF-8 string; last byte: 0x" + sn)); + } + } + + /*! + @brief dump an integer + + Dump a given integer to output stream @a o. Works internally with + @a number_buffer. + + @param[in] x integer number (signed or unsigned) to dump + @tparam NumberType either @a number_integer_t or @a number_unsigned_t + */ + template::value or + std::is_same::value, + int> = 0> + void dump_integer(NumberType x) + { + // special case for "0" + if (x == 0) + { + o->write_character('0'); + return; + } + + const bool is_negative = (x <= 0) and (x != 0); // see issue #755 + std::size_t i = 0; + + while (x != 0) + { + // spare 1 byte for '\0' + assert(i < number_buffer.size() - 1); + + const auto digit = std::labs(static_cast(x % 10)); + number_buffer[i++] = static_cast('0' + digit); + x /= 10; + } + + if (is_negative) + { + // make sure there is capacity for the '-' + assert(i < number_buffer.size() - 2); + number_buffer[i++] = '-'; + } + + std::reverse(number_buffer.begin(), number_buffer.begin() + i); + o->write_characters(number_buffer.data(), i); + } + + /*! + @brief dump a floating-point number + + Dump a given floating-point number to output stream @a o. Works internally + with @a number_buffer. + + @param[in] x floating-point number to dump + */ + void dump_float(number_float_t x) + { + // NaN / inf + if (not std::isfinite(x)) + { + o->write_characters("null", 4); + return; + } + + // If number_float_t is an IEEE-754 single or double precision number, + // use the Grisu2 algorithm to produce short numbers which are + // guaranteed to round-trip, using strtof and strtod, resp. + // + // NB: The test below works if == . + static constexpr bool is_ieee_single_or_double + = (std::numeric_limits::is_iec559 and std::numeric_limits::digits == 24 and std::numeric_limits::max_exponent == 128) or + (std::numeric_limits::is_iec559 and std::numeric_limits::digits == 53 and std::numeric_limits::max_exponent == 1024); + + dump_float(x, std::integral_constant()); + } + + void dump_float(number_float_t x, std::true_type /*is_ieee_single_or_double*/) + { + char* begin = number_buffer.data(); + char* end = ::nlohmann::detail::to_chars(begin, begin + number_buffer.size(), x); + + o->write_characters(begin, static_cast(end - begin)); + } + + void dump_float(number_float_t x, std::false_type /*is_ieee_single_or_double*/) + { + // get number of digits for a float -> text -> float round-trip + static constexpr auto d = std::numeric_limits::max_digits10; + + // the actual conversion + std::ptrdiff_t len = snprintf(number_buffer.data(), number_buffer.size(), "%.*g", d, x); + + // negative value indicates an error + assert(len > 0); + // check if buffer was large enough + assert(static_cast(len) < number_buffer.size()); + + // erase thousands separator + if (thousands_sep != '\0') + { + const auto end = std::remove(number_buffer.begin(), + number_buffer.begin() + len, thousands_sep); + std::fill(end, number_buffer.end(), '\0'); + assert((end - number_buffer.begin()) <= len); + len = (end - number_buffer.begin()); + } + + // convert decimal point to '.' + if (decimal_point != '\0' and decimal_point != '.') + { + const auto dec_pos = std::find(number_buffer.begin(), number_buffer.end(), decimal_point); + if (dec_pos != number_buffer.end()) + { + *dec_pos = '.'; + } + } + + o->write_characters(number_buffer.data(), static_cast(len)); + + // determine if need to append ".0" + const bool value_is_int_like = + std::none_of(number_buffer.begin(), number_buffer.begin() + len + 1, + [](char c) + { + return (c == '.' or c == 'e'); + }); + + if (value_is_int_like) + { + o->write_characters(".0", 2); + } + } + + /*! + @brief check whether a string is UTF-8 encoded + + The function checks each byte of a string whether it is UTF-8 encoded. The + result of the check is stored in the @a state parameter. The function must + be called initially with state 0 (accept). State 1 means the string must + be rejected, because the current byte is not allowed. If the string is + completely processed, but the state is non-zero, the string ended + prematurely; that is, the last byte indicated more bytes should have + followed. + + @param[in,out] state the state of the decoding + @param[in,out] codep codepoint (valid only if resulting state is UTF8_ACCEPT) + @param[in] byte next byte to decode + @return new state + + @note The function has been edited: a std::array is used. + + @copyright Copyright (c) 2008-2009 Bjoern Hoehrmann + @sa http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ + */ + static uint8_t decode(uint8_t& state, uint32_t& codep, const uint8_t byte) noexcept + { + static const std::array utf8d = + { + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 00..1F + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 20..3F + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 40..5F + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 60..7F + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, // 80..9F + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, // A0..BF + 8, 8, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // C0..DF + 0xA, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x4, 0x3, 0x3, // E0..EF + 0xB, 0x6, 0x6, 0x6, 0x5, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, // F0..FF + 0x0, 0x1, 0x2, 0x3, 0x5, 0x8, 0x7, 0x1, 0x1, 0x1, 0x4, 0x6, 0x1, 0x1, 0x1, 0x1, // s0..s0 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, // s1..s2 + 1, 2, 1, 1, 1, 1, 1, 2, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, // s3..s4 + 1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, // s5..s6 + 1, 3, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 // s7..s8 + } + }; + + const uint8_t type = utf8d[byte]; + + codep = (state != UTF8_ACCEPT) + ? (byte & 0x3fu) | (codep << 6) + : static_cast(0xff >> type) & (byte); + + state = utf8d[256u + state * 16u + type]; + return state; + } + + private: + /// the output of the fancy_serializer + output_adapter_t o = nullptr; + + /// a (hopefully) large enough character buffer + std::array number_buffer{{}}; + + /// the locale + const std::lconv* loc = nullptr; + /// the locale's thousand separator character + const char thousands_sep = '\0'; + /// the locale's decimal point character + const char decimal_point = '\0'; + + /// string buffer + std::array string_buffer{{}}; + + /// the indentation character + const char indent_char; + /// the indentation string + string_t indent_string; +}; +} + +template +std::ostream& fancy_dump(std::ostream& o, const BasicJsonType& j) +{ + // do the actual serialization + detail::fancy_serializer s(detail::output_adapter(o), o.fill()); + s.dump(j, false, false, 0u); + return o; +} + +} + // #include @@ -10905,6 +11554,7 @@ class basic_json friend ::nlohmann::json_pointer; friend ::nlohmann::detail::parser; friend ::nlohmann::detail::serializer; + friend ::nlohmann::detail::fancy_serializer; template friend class ::nlohmann::detail::iter_impl; template diff --git a/test/src/unit-fancy-serialization.cpp b/test/src/unit-fancy-serialization.cpp index 3abc7fb19..2ce700d97 100644 --- a/test/src/unit-fancy-serialization.cpp +++ b/test/src/unit-fancy-serialization.cpp @@ -30,7 +30,9 @@ SOFTWARE. #include "catch.hpp" #include + using nlohmann::json; +using nlohmann::fancy_dump; TEST_CASE("serialization") { @@ -40,58 +42,8 @@ TEST_CASE("serialization") { std::stringstream ss; json j = {"foo", 1, 2, 3, false, {{"one", 1}}}; - ss << j; + fancy_dump(ss, j); CHECK(ss.str() == "[\"foo\",1,2,3,false,{\"one\":1}]"); } - - SECTION("given width") - { - std::stringstream ss; - json j = {"foo", 1, 2, 3, false, {{"one", 1}}}; - ss << std::setw(4) << j; - CHECK(ss.str() == - "[\n \"foo\",\n 1,\n 2,\n 3,\n false,\n {\n \"one\": 1\n }\n]"); - } - - SECTION("given fill") - { - std::stringstream ss; - json j = {"foo", 1, 2, 3, false, {{"one", 1}}}; - ss << std::setw(1) << std::setfill('\t') << j; - CHECK(ss.str() == - "[\n\t\"foo\",\n\t1,\n\t2,\n\t3,\n\tfalse,\n\t{\n\t\t\"one\": 1\n\t}\n]"); - } - } - - SECTION("operator>>") - { - SECTION("no given width") - { - std::stringstream ss; - json j = {"foo", 1, 2, 3, false, {{"one", 1}}}; - j >> ss; - CHECK(ss.str() == "[\"foo\",1,2,3,false,{\"one\":1}]"); - } - - SECTION("given width") - { - std::stringstream ss; - json j = {"foo", 1, 2, 3, false, {{"one", 1}}}; - ss.width(4); - j >> ss; - CHECK(ss.str() == - "[\n \"foo\",\n 1,\n 2,\n 3,\n false,\n {\n \"one\": 1\n }\n]"); - } - - SECTION("given fill") - { - std::stringstream ss; - json j = {"foo", 1, 2, 3, false, {{"one", 1}}}; - ss.width(1); - ss.fill('\t'); - j >> ss; - CHECK(ss.str() == - "[\n\t\"foo\",\n\t1,\n\t2,\n\t3,\n\tfalse,\n\t{\n\t\t\"one\": 1\n\t}\n]"); - } } } From a2231324e9cb8d2b7a60ef47ccb41828a46a96bb Mon Sep 17 00:00:00 2001 From: Evan Driscoll Date: Fri, 1 Jun 2018 23:26:33 -0500 Subject: [PATCH 04/41] Start reworking fancy serialization tests --- test/src/unit-fancy-serialization.cpp | 42 +++++++++++++++++++++++---- 1 file changed, 37 insertions(+), 5 deletions(-) diff --git a/test/src/unit-fancy-serialization.cpp b/test/src/unit-fancy-serialization.cpp index 2ce700d97..88f6b5931 100644 --- a/test/src/unit-fancy-serialization.cpp +++ b/test/src/unit-fancy-serialization.cpp @@ -6,7 +6,7 @@ Licensed under the MIT License . SPDX-License-Identifier: MIT -Copyright (c) 2013-2018 Niels Lohmann . +Copyright (c) 2018 Evan Driscoll Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -36,14 +36,46 @@ using nlohmann::fancy_dump; TEST_CASE("serialization") { - SECTION("operator<<") + SECTION("primitives") { - SECTION("no given width") + SECTION("null") { std::stringstream ss; - json j = {"foo", 1, 2, 3, false, {{"one", 1}}}; + json j; fancy_dump(ss, j); - CHECK(ss.str() == "[\"foo\",1,2,3,false,{\"one\":1}]"); + CHECK(ss.str() == "null"); + } + + SECTION("true") + { + std::stringstream ss; + json j = true; + fancy_dump(ss, j); + CHECK(ss.str() == "true"); + } + + SECTION("false") + { + std::stringstream ss; + json j = false; + fancy_dump(ss, j); + CHECK(ss.str() == "false"); + } + + SECTION("integer") + { + std::stringstream ss; + json j = 10; + fancy_dump(ss, j); + CHECK(ss.str() == "10"); + } + + SECTION("floating point") + { + std::stringstream ss; + json j = 7.5; + fancy_dump(ss, j); + CHECK(ss.str() == "7.5"); } } } From e9cb8f604d4c017fb092fccfa18cdd424f35c3e9 Mon Sep 17 00:00:00 2001 From: Evan Driscoll Date: Fri, 1 Jun 2018 23:58:03 -0500 Subject: [PATCH 05/41] Split primitive serialization into own class --- .../detail/output/primitive_serializer.hpp | 414 ++++++++++++++ include/nlohmann/detail/output/serializer.hpp | 381 +------------ single_include/nlohmann/json.hpp | 536 ++++++++++-------- test/src/unit-convenience.cpp | 6 +- 4 files changed, 734 insertions(+), 603 deletions(-) create mode 100644 include/nlohmann/detail/output/primitive_serializer.hpp diff --git a/include/nlohmann/detail/output/primitive_serializer.hpp b/include/nlohmann/detail/output/primitive_serializer.hpp new file mode 100644 index 000000000..d45abf125 --- /dev/null +++ b/include/nlohmann/detail/output/primitive_serializer.hpp @@ -0,0 +1,414 @@ +#pragma once + +#include // reverse, remove, fill, find, none_of +#include // array +#include // assert +#include // and, or +#include // localeconv, lconv +#include // labs, isfinite, isnan, signbit +#include // size_t, ptrdiff_t +#include // uint8_t +#include // snprintf +#include // numeric_limits +#include // string +#include // is_same + +#include +#include +#include +#include +#include +#include + +namespace nlohmann +{ +namespace detail +{ +/////////////////// +// serialization // +/////////////////// + +template +class primitive_serializer +{ + using string_t = typename BasicJsonType::string_t; + using number_float_t = typename BasicJsonType::number_float_t; + using number_integer_t = typename BasicJsonType::number_integer_t; + using number_unsigned_t = typename BasicJsonType::number_unsigned_t; + static constexpr uint8_t UTF8_ACCEPT = 0; + static constexpr uint8_t UTF8_REJECT = 1; + using output_adapter_protocol_t = output_adapter_protocol; + + public: + /*! + @param[in] s output stream to serialize to + @param[in] ichar indentation character to use + */ + primitive_serializer() + : loc(std::localeconv()), + thousands_sep(loc->thousands_sep == nullptr ? '\0' : * (loc->thousands_sep)), + decimal_point(loc->decimal_point == nullptr ? '\0' : * (loc->decimal_point)) + {} + + // delete because of pointer members + primitive_serializer(const primitive_serializer&) = delete; + primitive_serializer& operator=(const primitive_serializer&) = delete; + + /*! + @brief dump escaped string + + Escape a string by replacing certain special characters by a sequence of an + escape character (backslash) and another character and other control + characters by a sequence of "\u" followed by a four-digit hex + representation. The escaped string is written to output stream @a o. + + @param[in] s the string to escape + @param[in] ensure_ascii whether to escape non-ASCII characters with + \uXXXX sequences + + @complexity Linear in the length of string @a s. + */ + void dump_escaped(output_adapter_protocol_t& o, const string_t& s, const bool ensure_ascii) + { + uint32_t codepoint; + uint8_t state = UTF8_ACCEPT; + std::size_t bytes = 0; // number of bytes written to string_buffer + + for (std::size_t i = 0; i < s.size(); ++i) + { + const auto byte = static_cast(s[i]); + + switch (decode(state, codepoint, byte)) + { + case UTF8_ACCEPT: // decode found a new code point + { + switch (codepoint) + { + case 0x08: // backspace + { + string_buffer[bytes++] = '\\'; + string_buffer[bytes++] = 'b'; + break; + } + + case 0x09: // horizontal tab + { + string_buffer[bytes++] = '\\'; + string_buffer[bytes++] = 't'; + break; + } + + case 0x0A: // newline + { + string_buffer[bytes++] = '\\'; + string_buffer[bytes++] = 'n'; + break; + } + + case 0x0C: // formfeed + { + string_buffer[bytes++] = '\\'; + string_buffer[bytes++] = 'f'; + break; + } + + case 0x0D: // carriage return + { + string_buffer[bytes++] = '\\'; + string_buffer[bytes++] = 'r'; + break; + } + + case 0x22: // quotation mark + { + string_buffer[bytes++] = '\\'; + string_buffer[bytes++] = '\"'; + break; + } + + case 0x5C: // reverse solidus + { + string_buffer[bytes++] = '\\'; + string_buffer[bytes++] = '\\'; + break; + } + + default: + { + // escape control characters (0x00..0x1F) or, if + // ensure_ascii parameter is used, non-ASCII characters + if ((codepoint <= 0x1F) or (ensure_ascii and (codepoint >= 0x7F))) + { + if (codepoint <= 0xFFFF) + { + std::snprintf(string_buffer.data() + bytes, 7, "\\u%04x", + static_cast(codepoint)); + bytes += 6; + } + else + { + std::snprintf(string_buffer.data() + bytes, 13, "\\u%04x\\u%04x", + static_cast(0xD7C0 + (codepoint >> 10)), + static_cast(0xDC00 + (codepoint & 0x3FF))); + bytes += 12; + } + } + else + { + // copy byte to buffer (all previous bytes + // been copied have in default case above) + string_buffer[bytes++] = s[i]; + } + break; + } + } + + // write buffer and reset index; there must be 13 bytes + // left, as this is the maximal number of bytes to be + // written ("\uxxxx\uxxxx\0") for one code point + if (string_buffer.size() - bytes < 13) + { + o.write_characters(string_buffer.data(), bytes); + bytes = 0; + } + break; + } + + case UTF8_REJECT: // decode found invalid UTF-8 byte + { + std::string sn(3, '\0'); + snprintf(&sn[0], sn.size(), "%.2X", byte); + JSON_THROW(type_error::create(316, "invalid UTF-8 byte at index " + std::to_string(i) + ": 0x" + sn)); + } + + default: // decode found yet incomplete multi-byte code point + { + if (not ensure_ascii) + { + // code point will not be escaped - copy byte to buffer + string_buffer[bytes++] = s[i]; + } + break; + } + } + } + + if (JSON_LIKELY(state == UTF8_ACCEPT)) + { + // write buffer + if (bytes > 0) + { + o.write_characters(string_buffer.data(), bytes); + } + } + else + { + // we finish reading, but do not accept: string was incomplete + std::string sn(3, '\0'); + snprintf(&sn[0], sn.size(), "%.2X", static_cast(s.back())); + JSON_THROW(type_error::create(316, "incomplete UTF-8 string; last byte: 0x" + sn)); + } + } + + /*! + @brief dump an integer + + Dump a given integer to output stream @a o. Works internally with + @a number_buffer. + + @param[in] x integer number (signed or unsigned) to dump + @tparam NumberType either @a number_integer_t or @a number_unsigned_t + */ + template::value or + std::is_same::value, + int> = 0> + void dump_integer(output_adapter_protocol_t& o, NumberType x) + { + // special case for "0" + if (x == 0) + { + o.write_character('0'); + return; + } + + const bool is_negative = (x <= 0) and (x != 0); // see issue #755 + std::size_t i = 0; + + while (x != 0) + { + // spare 1 byte for '\0' + assert(i < number_buffer.size() - 1); + + const auto digit = std::labs(static_cast(x % 10)); + number_buffer[i++] = static_cast('0' + digit); + x /= 10; + } + + if (is_negative) + { + // make sure there is capacity for the '-' + assert(i < number_buffer.size() - 2); + number_buffer[i++] = '-'; + } + + std::reverse(number_buffer.begin(), number_buffer.begin() + i); + o.write_characters(number_buffer.data(), i); + } + + /*! + @brief dump a floating-point number + + Dump a given floating-point number to output stream @a o. Works internally + with @a number_buffer. + + @param[in] x floating-point number to dump + */ + void dump_float(output_adapter_protocol_t& o, number_float_t x) + { + // NaN / inf + if (not std::isfinite(x)) + { + o.write_characters("null", 4); + return; + } + + // If number_float_t is an IEEE-754 single or double precision number, + // use the Grisu2 algorithm to produce short numbers which are + // guaranteed to round-trip, using strtof and strtod, resp. + // + // NB: The test below works if == . + static constexpr bool is_ieee_single_or_double + = (std::numeric_limits::is_iec559 and std::numeric_limits::digits == 24 and std::numeric_limits::max_exponent == 128) or + (std::numeric_limits::is_iec559 and std::numeric_limits::digits == 53 and std::numeric_limits::max_exponent == 1024); + + dump_float(o, x, std::integral_constant()); + } + + private: + void dump_float(output_adapter_protocol_t& o, number_float_t x, std::true_type /*is_ieee_single_or_double*/) + { + char* begin = number_buffer.data(); + char* end = ::nlohmann::detail::to_chars(begin, begin + number_buffer.size(), x); + + o.write_characters(begin, static_cast(end - begin)); + } + + void dump_float(output_adapter_protocol_t& o, number_float_t x, std::false_type /*is_ieee_single_or_double*/) + { + // get number of digits for a float -> text -> float round-trip + static constexpr auto d = std::numeric_limits::max_digits10; + + // the actual conversion + std::ptrdiff_t len = snprintf(number_buffer.data(), number_buffer.size(), "%.*g", d, x); + + // negative value indicates an error + assert(len > 0); + // check if buffer was large enough + assert(static_cast(len) < number_buffer.size()); + + // erase thousands separator + if (thousands_sep != '\0') + { + const auto end = std::remove(number_buffer.begin(), + number_buffer.begin() + len, thousands_sep); + std::fill(end, number_buffer.end(), '\0'); + assert((end - number_buffer.begin()) <= len); + len = (end - number_buffer.begin()); + } + + // convert decimal point to '.' + if (decimal_point != '\0' and decimal_point != '.') + { + const auto dec_pos = std::find(number_buffer.begin(), number_buffer.end(), decimal_point); + if (dec_pos != number_buffer.end()) + { + *dec_pos = '.'; + } + } + + o.write_characters(number_buffer.data(), static_cast(len)); + + // determine if need to append ".0" + const bool value_is_int_like = + std::none_of(number_buffer.begin(), number_buffer.begin() + len + 1, + [](char c) + { + return (c == '.' or c == 'e'); + }); + + if (value_is_int_like) + { + o.write_characters(".0", 2); + } + } + + /*! + @brief check whether a string is UTF-8 encoded + + The function checks each byte of a string whether it is UTF-8 encoded. The + result of the check is stored in the @a state parameter. The function must + be called initially with state 0 (accept). State 1 means the string must + be rejected, because the current byte is not allowed. If the string is + completely processed, but the state is non-zero, the string ended + prematurely; that is, the last byte indicated more bytes should have + followed. + + @param[in,out] state the state of the decoding + @param[in,out] codep codepoint (valid only if resulting state is UTF8_ACCEPT) + @param[in] byte next byte to decode + @return new state + + @note The function has been edited: a std::array is used. + + @copyright Copyright (c) 2008-2009 Bjoern Hoehrmann + @sa http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ + */ + static uint8_t decode(uint8_t& state, uint32_t& codep, const uint8_t byte) noexcept + { + static const std::array utf8d = + { + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 00..1F + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 20..3F + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 40..5F + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 60..7F + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, // 80..9F + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, // A0..BF + 8, 8, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // C0..DF + 0xA, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x4, 0x3, 0x3, // E0..EF + 0xB, 0x6, 0x6, 0x6, 0x5, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, // F0..FF + 0x0, 0x1, 0x2, 0x3, 0x5, 0x8, 0x7, 0x1, 0x1, 0x1, 0x4, 0x6, 0x1, 0x1, 0x1, 0x1, // s0..s0 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, // s1..s2 + 1, 2, 1, 1, 1, 1, 1, 2, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, // s3..s4 + 1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, // s5..s6 + 1, 3, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 // s7..s8 + } + }; + + const uint8_t type = utf8d[byte]; + + codep = (state != UTF8_ACCEPT) + ? (byte & 0x3fu) | (codep << 6) + : static_cast(0xff >> type) & (byte); + + state = utf8d[256u + state * 16u + type]; + return state; + } + + private: + /// a (hopefully) large enough character buffer + std::array number_buffer{{}}; + + /// the locale + const std::lconv* loc = nullptr; + /// the locale's thousand separator character + const char thousands_sep = '\0'; + /// the locale's decimal point character + const char decimal_point = '\0'; + + /// string buffer + std::array string_buffer{{}}; +}; +} +} diff --git a/include/nlohmann/detail/output/serializer.hpp b/include/nlohmann/detail/output/serializer.hpp index c0f817766..b4997adac 100644 --- a/include/nlohmann/detail/output/serializer.hpp +++ b/include/nlohmann/detail/output/serializer.hpp @@ -18,6 +18,7 @@ #include #include #include +#include #include namespace nlohmann @@ -31,6 +32,7 @@ namespace detail template class serializer { + using primitive_serializer_t = primitive_serializer; using string_t = typename BasicJsonType::string_t; using number_float_t = typename BasicJsonType::number_float_t; using number_integer_t = typename BasicJsonType::number_integer_t; @@ -44,10 +46,7 @@ class serializer @param[in] ichar indentation character to use */ serializer(output_adapter_t s, const char ichar) - : o(std::move(s)), loc(std::localeconv()), - thousands_sep(loc->thousands_sep == nullptr ? '\0' : * (loc->thousands_sep)), - decimal_point(loc->decimal_point == nullptr ? '\0' : * (loc->decimal_point)), - indent_char(ichar), indent_string(512, indent_char) + : o(std::move(s)), indent_char(ichar), indent_string(512, indent_char) {} // delete because of pointer members @@ -103,7 +102,7 @@ class serializer { o->write_characters(indent_string.c_str(), new_indent); o->write_character('\"'); - dump_escaped(i->first, ensure_ascii); + prim_serializer.dump_escaped(*o, i->first, ensure_ascii); o->write_characters("\": ", 3); dump(i->second, true, ensure_ascii, indent_step, new_indent); o->write_characters(",\n", 2); @@ -114,7 +113,7 @@ class serializer assert(std::next(i) == val.m_value.object->cend()); o->write_characters(indent_string.c_str(), new_indent); o->write_character('\"'); - dump_escaped(i->first, ensure_ascii); + prim_serializer.dump_escaped(*o, i->first, ensure_ascii); o->write_characters("\": ", 3); dump(i->second, true, ensure_ascii, indent_step, new_indent); @@ -131,7 +130,7 @@ class serializer for (std::size_t cnt = 0; cnt < val.m_value.object->size() - 1; ++cnt, ++i) { o->write_character('\"'); - dump_escaped(i->first, ensure_ascii); + prim_serializer.dump_escaped(*o, i->first, ensure_ascii); o->write_characters("\":", 2); dump(i->second, false, ensure_ascii, indent_step, current_indent); o->write_character(','); @@ -141,7 +140,7 @@ class serializer assert(i != val.m_value.object->cend()); assert(std::next(i) == val.m_value.object->cend()); o->write_character('\"'); - dump_escaped(i->first, ensure_ascii); + prim_serializer.dump_escaped(*o, i->first, ensure_ascii); o->write_characters("\":", 2); dump(i->second, false, ensure_ascii, indent_step, current_indent); @@ -213,7 +212,7 @@ class serializer case value_t::string: { o->write_character('\"'); - dump_escaped(*val.m_value.string, ensure_ascii); + prim_serializer.dump_escaped(*o, *val.m_value.string, ensure_ascii); o->write_character('\"'); return; } @@ -233,19 +232,19 @@ class serializer case value_t::number_integer: { - dump_integer(val.m_value.number_integer); + prim_serializer.dump_integer(*o, val.m_value.number_integer); return; } case value_t::number_unsigned: { - dump_integer(val.m_value.number_unsigned); + prim_serializer.dump_integer(*o, val.m_value.number_unsigned); return; } case value_t::number_float: { - dump_float(val.m_value.number_float); + prim_serializer.dump_float(*o, val.m_value.number_float); return; } @@ -263,369 +262,17 @@ class serializer } } - private: - /*! - @brief dump escaped string - - Escape a string by replacing certain special characters by a sequence of an - escape character (backslash) and another character and other control - characters by a sequence of "\u" followed by a four-digit hex - representation. The escaped string is written to output stream @a o. - - @param[in] s the string to escape - @param[in] ensure_ascii whether to escape non-ASCII characters with - \uXXXX sequences - - @complexity Linear in the length of string @a s. - */ - void dump_escaped(const string_t& s, const bool ensure_ascii) - { - uint32_t codepoint; - uint8_t state = UTF8_ACCEPT; - std::size_t bytes = 0; // number of bytes written to string_buffer - - for (std::size_t i = 0; i < s.size(); ++i) - { - const auto byte = static_cast(s[i]); - - switch (decode(state, codepoint, byte)) - { - case UTF8_ACCEPT: // decode found a new code point - { - switch (codepoint) - { - case 0x08: // backspace - { - string_buffer[bytes++] = '\\'; - string_buffer[bytes++] = 'b'; - break; - } - - case 0x09: // horizontal tab - { - string_buffer[bytes++] = '\\'; - string_buffer[bytes++] = 't'; - break; - } - - case 0x0A: // newline - { - string_buffer[bytes++] = '\\'; - string_buffer[bytes++] = 'n'; - break; - } - - case 0x0C: // formfeed - { - string_buffer[bytes++] = '\\'; - string_buffer[bytes++] = 'f'; - break; - } - - case 0x0D: // carriage return - { - string_buffer[bytes++] = '\\'; - string_buffer[bytes++] = 'r'; - break; - } - - case 0x22: // quotation mark - { - string_buffer[bytes++] = '\\'; - string_buffer[bytes++] = '\"'; - break; - } - - case 0x5C: // reverse solidus - { - string_buffer[bytes++] = '\\'; - string_buffer[bytes++] = '\\'; - break; - } - - default: - { - // escape control characters (0x00..0x1F) or, if - // ensure_ascii parameter is used, non-ASCII characters - if ((codepoint <= 0x1F) or (ensure_ascii and (codepoint >= 0x7F))) - { - if (codepoint <= 0xFFFF) - { - std::snprintf(string_buffer.data() + bytes, 7, "\\u%04x", - static_cast(codepoint)); - bytes += 6; - } - else - { - std::snprintf(string_buffer.data() + bytes, 13, "\\u%04x\\u%04x", - static_cast(0xD7C0 + (codepoint >> 10)), - static_cast(0xDC00 + (codepoint & 0x3FF))); - bytes += 12; - } - } - else - { - // copy byte to buffer (all previous bytes - // been copied have in default case above) - string_buffer[bytes++] = s[i]; - } - break; - } - } - - // write buffer and reset index; there must be 13 bytes - // left, as this is the maximal number of bytes to be - // written ("\uxxxx\uxxxx\0") for one code point - if (string_buffer.size() - bytes < 13) - { - o->write_characters(string_buffer.data(), bytes); - bytes = 0; - } - break; - } - - case UTF8_REJECT: // decode found invalid UTF-8 byte - { - std::string sn(3, '\0'); - snprintf(&sn[0], sn.size(), "%.2X", byte); - JSON_THROW(type_error::create(316, "invalid UTF-8 byte at index " + std::to_string(i) + ": 0x" + sn)); - } - - default: // decode found yet incomplete multi-byte code point - { - if (not ensure_ascii) - { - // code point will not be escaped - copy byte to buffer - string_buffer[bytes++] = s[i]; - } - break; - } - } - } - - if (JSON_LIKELY(state == UTF8_ACCEPT)) - { - // write buffer - if (bytes > 0) - { - o->write_characters(string_buffer.data(), bytes); - } - } - else - { - // we finish reading, but do not accept: string was incomplete - std::string sn(3, '\0'); - snprintf(&sn[0], sn.size(), "%.2X", static_cast(s.back())); - JSON_THROW(type_error::create(316, "incomplete UTF-8 string; last byte: 0x" + sn)); - } - } - - /*! - @brief dump an integer - - Dump a given integer to output stream @a o. Works internally with - @a number_buffer. - - @param[in] x integer number (signed or unsigned) to dump - @tparam NumberType either @a number_integer_t or @a number_unsigned_t - */ - template::value or - std::is_same::value, - int> = 0> - void dump_integer(NumberType x) - { - // special case for "0" - if (x == 0) - { - o->write_character('0'); - return; - } - - const bool is_negative = (x <= 0) and (x != 0); // see issue #755 - std::size_t i = 0; - - while (x != 0) - { - // spare 1 byte for '\0' - assert(i < number_buffer.size() - 1); - - const auto digit = std::labs(static_cast(x % 10)); - number_buffer[i++] = static_cast('0' + digit); - x /= 10; - } - - if (is_negative) - { - // make sure there is capacity for the '-' - assert(i < number_buffer.size() - 2); - number_buffer[i++] = '-'; - } - - std::reverse(number_buffer.begin(), number_buffer.begin() + i); - o->write_characters(number_buffer.data(), i); - } - - /*! - @brief dump a floating-point number - - Dump a given floating-point number to output stream @a o. Works internally - with @a number_buffer. - - @param[in] x floating-point number to dump - */ - void dump_float(number_float_t x) - { - // NaN / inf - if (not std::isfinite(x)) - { - o->write_characters("null", 4); - return; - } - - // If number_float_t is an IEEE-754 single or double precision number, - // use the Grisu2 algorithm to produce short numbers which are - // guaranteed to round-trip, using strtof and strtod, resp. - // - // NB: The test below works if == . - static constexpr bool is_ieee_single_or_double - = (std::numeric_limits::is_iec559 and std::numeric_limits::digits == 24 and std::numeric_limits::max_exponent == 128) or - (std::numeric_limits::is_iec559 and std::numeric_limits::digits == 53 and std::numeric_limits::max_exponent == 1024); - - dump_float(x, std::integral_constant()); - } - - void dump_float(number_float_t x, std::true_type /*is_ieee_single_or_double*/) - { - char* begin = number_buffer.data(); - char* end = ::nlohmann::detail::to_chars(begin, begin + number_buffer.size(), x); - - o->write_characters(begin, static_cast(end - begin)); - } - - void dump_float(number_float_t x, std::false_type /*is_ieee_single_or_double*/) - { - // get number of digits for a float -> text -> float round-trip - static constexpr auto d = std::numeric_limits::max_digits10; - - // the actual conversion - std::ptrdiff_t len = snprintf(number_buffer.data(), number_buffer.size(), "%.*g", d, x); - - // negative value indicates an error - assert(len > 0); - // check if buffer was large enough - assert(static_cast(len) < number_buffer.size()); - - // erase thousands separator - if (thousands_sep != '\0') - { - const auto end = std::remove(number_buffer.begin(), - number_buffer.begin() + len, thousands_sep); - std::fill(end, number_buffer.end(), '\0'); - assert((end - number_buffer.begin()) <= len); - len = (end - number_buffer.begin()); - } - - // convert decimal point to '.' - if (decimal_point != '\0' and decimal_point != '.') - { - const auto dec_pos = std::find(number_buffer.begin(), number_buffer.end(), decimal_point); - if (dec_pos != number_buffer.end()) - { - *dec_pos = '.'; - } - } - - o->write_characters(number_buffer.data(), static_cast(len)); - - // determine if need to append ".0" - const bool value_is_int_like = - std::none_of(number_buffer.begin(), number_buffer.begin() + len + 1, - [](char c) - { - return (c == '.' or c == 'e'); - }); - - if (value_is_int_like) - { - o->write_characters(".0", 2); - } - } - - /*! - @brief check whether a string is UTF-8 encoded - - The function checks each byte of a string whether it is UTF-8 encoded. The - result of the check is stored in the @a state parameter. The function must - be called initially with state 0 (accept). State 1 means the string must - be rejected, because the current byte is not allowed. If the string is - completely processed, but the state is non-zero, the string ended - prematurely; that is, the last byte indicated more bytes should have - followed. - - @param[in,out] state the state of the decoding - @param[in,out] codep codepoint (valid only if resulting state is UTF8_ACCEPT) - @param[in] byte next byte to decode - @return new state - - @note The function has been edited: a std::array is used. - - @copyright Copyright (c) 2008-2009 Bjoern Hoehrmann - @sa http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ - */ - static uint8_t decode(uint8_t& state, uint32_t& codep, const uint8_t byte) noexcept - { - static const std::array utf8d = - { - { - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 00..1F - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 20..3F - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 40..5F - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 60..7F - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, // 80..9F - 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, // A0..BF - 8, 8, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // C0..DF - 0xA, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x4, 0x3, 0x3, // E0..EF - 0xB, 0x6, 0x6, 0x6, 0x5, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, // F0..FF - 0x0, 0x1, 0x2, 0x3, 0x5, 0x8, 0x7, 0x1, 0x1, 0x1, 0x4, 0x6, 0x1, 0x1, 0x1, 0x1, // s0..s0 - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, // s1..s2 - 1, 2, 1, 1, 1, 1, 1, 2, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, // s3..s4 - 1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, // s5..s6 - 1, 3, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 // s7..s8 - } - }; - - const uint8_t type = utf8d[byte]; - - codep = (state != UTF8_ACCEPT) - ? (byte & 0x3fu) | (codep << 6) - : static_cast(0xff >> type) & (byte); - - state = utf8d[256u + state * 16u + type]; - return state; - } - private: /// the output of the serializer output_adapter_t o = nullptr; - /// a (hopefully) large enough character buffer - std::array number_buffer{{}}; - - /// the locale - const std::lconv* loc = nullptr; - /// the locale's thousand separator character - const char thousands_sep = '\0'; - /// the locale's decimal point character - const char decimal_point = '\0'; - - /// string buffer - std::array string_buffer{{}}; - /// the indentation character const char indent_char; /// the indentation string string_t indent_string; + + /// used for serializing non-object non-arrays + primitive_serializer_t prim_serializer; }; } } diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index c4cad9886..3bfe9ff52 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -9379,6 +9379,32 @@ char* to_chars(char* first, char* last, FloatType value) // #include +// #include + + +#include // reverse, remove, fill, find, none_of +#include // array +#include // assert +#include // and, or +#include // localeconv, lconv +#include // labs, isfinite, isnan, signbit +#include // size_t, ptrdiff_t +#include // uint8_t +#include // snprintf +#include // numeric_limits +#include // string +#include // is_same + +// #include + +// #include + +// #include + +// #include + +// #include + // #include @@ -9391,7 +9417,7 @@ namespace detail /////////////////// template -class serializer +class primitive_serializer { using string_t = typename BasicJsonType::string_t; using number_float_t = typename BasicJsonType::number_float_t; @@ -9399,233 +9425,23 @@ class serializer using number_unsigned_t = typename BasicJsonType::number_unsigned_t; static constexpr uint8_t UTF8_ACCEPT = 0; static constexpr uint8_t UTF8_REJECT = 1; + using output_adapter_protocol_t = output_adapter_protocol; public: /*! @param[in] s output stream to serialize to @param[in] ichar indentation character to use */ - serializer(output_adapter_t s, const char ichar) - : o(std::move(s)), loc(std::localeconv()), + primitive_serializer() + : loc(std::localeconv()), thousands_sep(loc->thousands_sep == nullptr ? '\0' : * (loc->thousands_sep)), - decimal_point(loc->decimal_point == nullptr ? '\0' : * (loc->decimal_point)), - indent_char(ichar), indent_string(512, indent_char) + decimal_point(loc->decimal_point == nullptr ? '\0' : * (loc->decimal_point)) {} // delete because of pointer members - serializer(const serializer&) = delete; - serializer& operator=(const serializer&) = delete; + primitive_serializer(const primitive_serializer&) = delete; + primitive_serializer& operator=(const primitive_serializer&) = delete; - /*! - @brief internal implementation of the serialization function - - This function is called by the public member function dump and organizes - the serialization internally. The indentation level is propagated as - additional parameter. In case of arrays and objects, the function is - called recursively. - - - strings and object keys are escaped using `escape_string()` - - integer numbers are converted implicitly via `operator<<` - - floating-point numbers are converted to a string using `"%g"` format - - @param[in] val value to serialize - @param[in] pretty_print whether the output shall be pretty-printed - @param[in] indent_step the indent level - @param[in] current_indent the current indent level (only used internally) - */ - void dump(const BasicJsonType& val, const bool pretty_print, - const bool ensure_ascii, - const unsigned int indent_step, - const unsigned int current_indent = 0) - { - switch (val.m_type) - { - case value_t::object: - { - if (val.m_value.object->empty()) - { - o->write_characters("{}", 2); - return; - } - - if (pretty_print) - { - o->write_characters("{\n", 2); - - // variable to hold indentation for recursive calls - const auto new_indent = current_indent + indent_step; - if (JSON_UNLIKELY(indent_string.size() < new_indent)) - { - indent_string.resize(indent_string.size() * 2, ' '); - } - - // first n-1 elements - auto i = val.m_value.object->cbegin(); - for (std::size_t cnt = 0; cnt < val.m_value.object->size() - 1; ++cnt, ++i) - { - o->write_characters(indent_string.c_str(), new_indent); - o->write_character('\"'); - dump_escaped(i->first, ensure_ascii); - o->write_characters("\": ", 3); - dump(i->second, true, ensure_ascii, indent_step, new_indent); - o->write_characters(",\n", 2); - } - - // last element - assert(i != val.m_value.object->cend()); - assert(std::next(i) == val.m_value.object->cend()); - o->write_characters(indent_string.c_str(), new_indent); - o->write_character('\"'); - dump_escaped(i->first, ensure_ascii); - o->write_characters("\": ", 3); - dump(i->second, true, ensure_ascii, indent_step, new_indent); - - o->write_character('\n'); - o->write_characters(indent_string.c_str(), current_indent); - o->write_character('}'); - } - else - { - o->write_character('{'); - - // first n-1 elements - auto i = val.m_value.object->cbegin(); - for (std::size_t cnt = 0; cnt < val.m_value.object->size() - 1; ++cnt, ++i) - { - o->write_character('\"'); - dump_escaped(i->first, ensure_ascii); - o->write_characters("\":", 2); - dump(i->second, false, ensure_ascii, indent_step, current_indent); - o->write_character(','); - } - - // last element - assert(i != val.m_value.object->cend()); - assert(std::next(i) == val.m_value.object->cend()); - o->write_character('\"'); - dump_escaped(i->first, ensure_ascii); - o->write_characters("\":", 2); - dump(i->second, false, ensure_ascii, indent_step, current_indent); - - o->write_character('}'); - } - - return; - } - - case value_t::array: - { - if (val.m_value.array->empty()) - { - o->write_characters("[]", 2); - return; - } - - if (pretty_print) - { - o->write_characters("[\n", 2); - - // variable to hold indentation for recursive calls - const auto new_indent = current_indent + indent_step; - if (JSON_UNLIKELY(indent_string.size() < new_indent)) - { - indent_string.resize(indent_string.size() * 2, ' '); - } - - // first n-1 elements - for (auto i = val.m_value.array->cbegin(); - i != val.m_value.array->cend() - 1; ++i) - { - o->write_characters(indent_string.c_str(), new_indent); - dump(*i, true, ensure_ascii, indent_step, new_indent); - o->write_characters(",\n", 2); - } - - // last element - assert(not val.m_value.array->empty()); - o->write_characters(indent_string.c_str(), new_indent); - dump(val.m_value.array->back(), true, ensure_ascii, indent_step, new_indent); - - o->write_character('\n'); - o->write_characters(indent_string.c_str(), current_indent); - o->write_character(']'); - } - else - { - o->write_character('['); - - // first n-1 elements - for (auto i = val.m_value.array->cbegin(); - i != val.m_value.array->cend() - 1; ++i) - { - dump(*i, false, ensure_ascii, indent_step, current_indent); - o->write_character(','); - } - - // last element - assert(not val.m_value.array->empty()); - dump(val.m_value.array->back(), false, ensure_ascii, indent_step, current_indent); - - o->write_character(']'); - } - - return; - } - - case value_t::string: - { - o->write_character('\"'); - dump_escaped(*val.m_value.string, ensure_ascii); - o->write_character('\"'); - return; - } - - case value_t::boolean: - { - if (val.m_value.boolean) - { - o->write_characters("true", 4); - } - else - { - o->write_characters("false", 5); - } - return; - } - - case value_t::number_integer: - { - dump_integer(val.m_value.number_integer); - return; - } - - case value_t::number_unsigned: - { - dump_integer(val.m_value.number_unsigned); - return; - } - - case value_t::number_float: - { - dump_float(val.m_value.number_float); - return; - } - - case value_t::discarded: - { - o->write_characters("", 11); - return; - } - - case value_t::null: - { - o->write_characters("null", 4); - return; - } - } - } - - private: /*! @brief dump escaped string @@ -9640,7 +9456,7 @@ class serializer @complexity Linear in the length of string @a s. */ - void dump_escaped(const string_t& s, const bool ensure_ascii) + void dump_escaped(output_adapter_protocol_t& o, const string_t& s, const bool ensure_ascii) { uint32_t codepoint; uint8_t state = UTF8_ACCEPT; @@ -9740,7 +9556,7 @@ class serializer // written ("\uxxxx\uxxxx\0") for one code point if (string_buffer.size() - bytes < 13) { - o->write_characters(string_buffer.data(), bytes); + o.write_characters(string_buffer.data(), bytes); bytes = 0; } break; @@ -9770,7 +9586,7 @@ class serializer // write buffer if (bytes > 0) { - o->write_characters(string_buffer.data(), bytes); + o.write_characters(string_buffer.data(), bytes); } } else @@ -9795,12 +9611,12 @@ class serializer std::is_same::value or std::is_same::value, int> = 0> - void dump_integer(NumberType x) + void dump_integer(output_adapter_protocol_t& o, NumberType x) { // special case for "0" if (x == 0) { - o->write_character('0'); + o.write_character('0'); return; } @@ -9825,7 +9641,7 @@ class serializer } std::reverse(number_buffer.begin(), number_buffer.begin() + i); - o->write_characters(number_buffer.data(), i); + o.write_characters(number_buffer.data(), i); } /*! @@ -9836,12 +9652,12 @@ class serializer @param[in] x floating-point number to dump */ - void dump_float(number_float_t x) + void dump_float(output_adapter_protocol_t& o, number_float_t x) { // NaN / inf if (not std::isfinite(x)) { - o->write_characters("null", 4); + o.write_characters("null", 4); return; } @@ -9854,18 +9670,19 @@ class serializer = (std::numeric_limits::is_iec559 and std::numeric_limits::digits == 24 and std::numeric_limits::max_exponent == 128) or (std::numeric_limits::is_iec559 and std::numeric_limits::digits == 53 and std::numeric_limits::max_exponent == 1024); - dump_float(x, std::integral_constant()); + dump_float(o, x, std::integral_constant()); } - void dump_float(number_float_t x, std::true_type /*is_ieee_single_or_double*/) + private: + void dump_float(output_adapter_protocol_t& o, number_float_t x, std::true_type /*is_ieee_single_or_double*/) { char* begin = number_buffer.data(); char* end = ::nlohmann::detail::to_chars(begin, begin + number_buffer.size(), x); - o->write_characters(begin, static_cast(end - begin)); + o.write_characters(begin, static_cast(end - begin)); } - void dump_float(number_float_t x, std::false_type /*is_ieee_single_or_double*/) + void dump_float(output_adapter_protocol_t& o, number_float_t x, std::false_type /*is_ieee_single_or_double*/) { // get number of digits for a float -> text -> float round-trip static constexpr auto d = std::numeric_limits::max_digits10; @@ -9898,7 +9715,7 @@ class serializer } } - o->write_characters(number_buffer.data(), static_cast(len)); + o.write_characters(number_buffer.data(), static_cast(len)); // determine if need to append ".0" const bool value_is_int_like = @@ -9910,7 +9727,7 @@ class serializer if (value_is_int_like) { - o->write_characters(".0", 2); + o.write_characters(".0", 2); } } @@ -9968,9 +9785,6 @@ class serializer } private: - /// the output of the serializer - output_adapter_t o = nullptr; - /// a (hopefully) large enough character buffer std::array number_buffer{{}}; @@ -9983,11 +9797,265 @@ class serializer /// string buffer std::array string_buffer{{}}; +}; +} +} + +// #include + + +namespace nlohmann +{ +namespace detail +{ +/////////////////// +// serialization // +/////////////////// + +template +class serializer +{ + using primitive_serializer_t = primitive_serializer; + using string_t = typename BasicJsonType::string_t; + using number_float_t = typename BasicJsonType::number_float_t; + using number_integer_t = typename BasicJsonType::number_integer_t; + using number_unsigned_t = typename BasicJsonType::number_unsigned_t; + static constexpr uint8_t UTF8_ACCEPT = 0; + static constexpr uint8_t UTF8_REJECT = 1; + + public: + /*! + @param[in] s output stream to serialize to + @param[in] ichar indentation character to use + */ + serializer(output_adapter_t s, const char ichar) + : o(std::move(s)), indent_char(ichar), indent_string(512, indent_char) + {} + + // delete because of pointer members + serializer(const serializer&) = delete; + serializer& operator=(const serializer&) = delete; + + /*! + @brief internal implementation of the serialization function + + This function is called by the public member function dump and organizes + the serialization internally. The indentation level is propagated as + additional parameter. In case of arrays and objects, the function is + called recursively. + + - strings and object keys are escaped using `escape_string()` + - integer numbers are converted implicitly via `operator<<` + - floating-point numbers are converted to a string using `"%g"` format + + @param[in] val value to serialize + @param[in] pretty_print whether the output shall be pretty-printed + @param[in] indent_step the indent level + @param[in] current_indent the current indent level (only used internally) + */ + void dump(const BasicJsonType& val, const bool pretty_print, + const bool ensure_ascii, + const unsigned int indent_step, + const unsigned int current_indent = 0) + { + switch (val.m_type) + { + case value_t::object: + { + if (val.m_value.object->empty()) + { + o->write_characters("{}", 2); + return; + } + + if (pretty_print) + { + o->write_characters("{\n", 2); + + // variable to hold indentation for recursive calls + const auto new_indent = current_indent + indent_step; + if (JSON_UNLIKELY(indent_string.size() < new_indent)) + { + indent_string.resize(indent_string.size() * 2, ' '); + } + + // first n-1 elements + auto i = val.m_value.object->cbegin(); + for (std::size_t cnt = 0; cnt < val.m_value.object->size() - 1; ++cnt, ++i) + { + o->write_characters(indent_string.c_str(), new_indent); + o->write_character('\"'); + prim_serializer.dump_escaped(*o, i->first, ensure_ascii); + o->write_characters("\": ", 3); + dump(i->second, true, ensure_ascii, indent_step, new_indent); + o->write_characters(",\n", 2); + } + + // last element + assert(i != val.m_value.object->cend()); + assert(std::next(i) == val.m_value.object->cend()); + o->write_characters(indent_string.c_str(), new_indent); + o->write_character('\"'); + prim_serializer.dump_escaped(*o, i->first, ensure_ascii); + o->write_characters("\": ", 3); + dump(i->second, true, ensure_ascii, indent_step, new_indent); + + o->write_character('\n'); + o->write_characters(indent_string.c_str(), current_indent); + o->write_character('}'); + } + else + { + o->write_character('{'); + + // first n-1 elements + auto i = val.m_value.object->cbegin(); + for (std::size_t cnt = 0; cnt < val.m_value.object->size() - 1; ++cnt, ++i) + { + o->write_character('\"'); + prim_serializer.dump_escaped(*o, i->first, ensure_ascii); + o->write_characters("\":", 2); + dump(i->second, false, ensure_ascii, indent_step, current_indent); + o->write_character(','); + } + + // last element + assert(i != val.m_value.object->cend()); + assert(std::next(i) == val.m_value.object->cend()); + o->write_character('\"'); + prim_serializer.dump_escaped(*o, i->first, ensure_ascii); + o->write_characters("\":", 2); + dump(i->second, false, ensure_ascii, indent_step, current_indent); + + o->write_character('}'); + } + + return; + } + + case value_t::array: + { + if (val.m_value.array->empty()) + { + o->write_characters("[]", 2); + return; + } + + if (pretty_print) + { + o->write_characters("[\n", 2); + + // variable to hold indentation for recursive calls + const auto new_indent = current_indent + indent_step; + if (JSON_UNLIKELY(indent_string.size() < new_indent)) + { + indent_string.resize(indent_string.size() * 2, ' '); + } + + // first n-1 elements + for (auto i = val.m_value.array->cbegin(); + i != val.m_value.array->cend() - 1; ++i) + { + o->write_characters(indent_string.c_str(), new_indent); + dump(*i, true, ensure_ascii, indent_step, new_indent); + o->write_characters(",\n", 2); + } + + // last element + assert(not val.m_value.array->empty()); + o->write_characters(indent_string.c_str(), new_indent); + dump(val.m_value.array->back(), true, ensure_ascii, indent_step, new_indent); + + o->write_character('\n'); + o->write_characters(indent_string.c_str(), current_indent); + o->write_character(']'); + } + else + { + o->write_character('['); + + // first n-1 elements + for (auto i = val.m_value.array->cbegin(); + i != val.m_value.array->cend() - 1; ++i) + { + dump(*i, false, ensure_ascii, indent_step, current_indent); + o->write_character(','); + } + + // last element + assert(not val.m_value.array->empty()); + dump(val.m_value.array->back(), false, ensure_ascii, indent_step, current_indent); + + o->write_character(']'); + } + + return; + } + + case value_t::string: + { + o->write_character('\"'); + prim_serializer.dump_escaped(*o, *val.m_value.string, ensure_ascii); + o->write_character('\"'); + return; + } + + case value_t::boolean: + { + if (val.m_value.boolean) + { + o->write_characters("true", 4); + } + else + { + o->write_characters("false", 5); + } + return; + } + + case value_t::number_integer: + { + prim_serializer.dump_integer(*o, val.m_value.number_integer); + return; + } + + case value_t::number_unsigned: + { + prim_serializer.dump_integer(*o, val.m_value.number_unsigned); + return; + } + + case value_t::number_float: + { + prim_serializer.dump_float(*o, val.m_value.number_float); + return; + } + + case value_t::discarded: + { + o->write_characters("", 11); + return; + } + + case value_t::null: + { + o->write_characters("null", 4); + return; + } + } + } + + private: + /// the output of the serializer + output_adapter_t o = nullptr; /// the indentation character const char indent_char; /// the indentation string string_t indent_string; + + /// used for serializing non-object non-arrays + primitive_serializer_t prim_serializer; }; } } diff --git a/test/src/unit-convenience.cpp b/test/src/unit-convenience.cpp index 6711fdd9d..cc1955bbf 100644 --- a/test/src/unit-convenience.cpp +++ b/test/src/unit-convenience.cpp @@ -37,8 +37,10 @@ void check_escaped(const char* original, const char* escaped = "", const bool en void check_escaped(const char* original, const char* escaped, const bool ensure_ascii) { std::stringstream ss; - json::serializer s(nlohmann::detail::output_adapter(ss), ' '); - s.dump_escaped(original, ensure_ascii); + nlohmann::detail::output_adapter o(ss); + nlohmann::detail::output_adapter_t oo = o; + nlohmann::detail::primitive_serializer s; + s.dump_escaped(*oo, original, ensure_ascii); CHECK(ss.str() == escaped); } From 297ff8e53f2f95bb745410699599361ea68e7562 Mon Sep 17 00:00:00 2001 From: Evan Driscoll Date: Sat, 2 Jun 2018 00:22:09 -0500 Subject: [PATCH 06/41] Use primitive_serializer in fancy_serializer --- .../detail/output/fancy_serializer.hpp | 382 +----------------- 1 file changed, 15 insertions(+), 367 deletions(-) diff --git a/include/nlohmann/detail/output/fancy_serializer.hpp b/include/nlohmann/detail/output/fancy_serializer.hpp index 97191d6fe..a7a573c30 100644 --- a/include/nlohmann/detail/output/fancy_serializer.hpp +++ b/include/nlohmann/detail/output/fancy_serializer.hpp @@ -19,6 +19,7 @@ #include #include #include +#include namespace nlohmann { @@ -31,6 +32,7 @@ namespace detail template class fancy_serializer { + using primitive_serializer_t = primitive_serializer; using string_t = typename BasicJsonType::string_t; using number_float_t = typename BasicJsonType::number_float_t; using number_integer_t = typename BasicJsonType::number_integer_t; @@ -44,10 +46,7 @@ class fancy_serializer @param[in] ichar indentation character to use */ fancy_serializer(output_adapter_t s, const char ichar) - : o(std::move(s)), loc(std::localeconv()), - thousands_sep(loc->thousands_sep == nullptr ? '\0' : * (loc->thousands_sep)), - decimal_point(loc->decimal_point == nullptr ? '\0' : * (loc->decimal_point)), - indent_char(ichar), indent_string(512, indent_char) + : o(std::move(s)), indent_char(ichar), indent_string(512, indent_char) {} // delete because of pointer members @@ -103,7 +102,7 @@ class fancy_serializer { o->write_characters(indent_string.c_str(), new_indent); o->write_character('\"'); - dump_escaped(i->first, ensure_ascii); + prim_serializer.dump_escaped(*o, i->first, ensure_ascii); o->write_characters("\": ", 3); dump(i->second, true, ensure_ascii, indent_step, new_indent); o->write_characters(",\n", 2); @@ -114,7 +113,7 @@ class fancy_serializer assert(std::next(i) == val.m_value.object->cend()); o->write_characters(indent_string.c_str(), new_indent); o->write_character('\"'); - dump_escaped(i->first, ensure_ascii); + prim_serializer.dump_escaped(*o, i->first, ensure_ascii); o->write_characters("\": ", 3); dump(i->second, true, ensure_ascii, indent_step, new_indent); @@ -131,7 +130,7 @@ class fancy_serializer for (std::size_t cnt = 0; cnt < val.m_value.object->size() - 1; ++cnt, ++i) { o->write_character('\"'); - dump_escaped(i->first, ensure_ascii); + prim_serializer.dump_escaped(*o, i->first, ensure_ascii); o->write_characters("\":", 2); dump(i->second, false, ensure_ascii, indent_step, current_indent); o->write_character(','); @@ -141,7 +140,7 @@ class fancy_serializer assert(i != val.m_value.object->cend()); assert(std::next(i) == val.m_value.object->cend()); o->write_character('\"'); - dump_escaped(i->first, ensure_ascii); + prim_serializer.dump_escaped(*o, i->first, ensure_ascii); o->write_characters("\":", 2); dump(i->second, false, ensure_ascii, indent_step, current_indent); @@ -213,7 +212,7 @@ class fancy_serializer case value_t::string: { o->write_character('\"'); - dump_escaped(*val.m_value.string, ensure_ascii); + prim_serializer.dump_escaped(*o, *val.m_value.string, ensure_ascii); o->write_character('\"'); return; } @@ -233,19 +232,19 @@ class fancy_serializer case value_t::number_integer: { - dump_integer(val.m_value.number_integer); + prim_serializer.dump_integer(*o, val.m_value.number_integer); return; } case value_t::number_unsigned: { - dump_integer(val.m_value.number_unsigned); + prim_serializer.dump_integer(*o, val.m_value.number_unsigned); return; } case value_t::number_float: { - dump_float(val.m_value.number_float); + prim_serializer.dump_float(*o, val.m_value.number_float); return; } @@ -263,369 +262,18 @@ class fancy_serializer } } - private: - /*! - @brief dump escaped string - - Escape a string by replacing certain special characters by a sequence of an - escape character (backslash) and another character and other control - characters by a sequence of "\u" followed by a four-digit hex - representation. The escaped string is written to output stream @a o. - - @param[in] s the string to escape - @param[in] ensure_ascii whether to escape non-ASCII characters with - \uXXXX sequences - - @complexity Linear in the length of string @a s. - */ - void dump_escaped(const string_t& s, const bool ensure_ascii) - { - uint32_t codepoint; - uint8_t state = UTF8_ACCEPT; - std::size_t bytes = 0; // number of bytes written to string_buffer - - for (std::size_t i = 0; i < s.size(); ++i) - { - const auto byte = static_cast(s[i]); - - switch (decode(state, codepoint, byte)) - { - case UTF8_ACCEPT: // decode found a new code point - { - switch (codepoint) - { - case 0x08: // backspace - { - string_buffer[bytes++] = '\\'; - string_buffer[bytes++] = 'b'; - break; - } - - case 0x09: // horizontal tab - { - string_buffer[bytes++] = '\\'; - string_buffer[bytes++] = 't'; - break; - } - - case 0x0A: // newline - { - string_buffer[bytes++] = '\\'; - string_buffer[bytes++] = 'n'; - break; - } - - case 0x0C: // formfeed - { - string_buffer[bytes++] = '\\'; - string_buffer[bytes++] = 'f'; - break; - } - - case 0x0D: // carriage return - { - string_buffer[bytes++] = '\\'; - string_buffer[bytes++] = 'r'; - break; - } - - case 0x22: // quotation mark - { - string_buffer[bytes++] = '\\'; - string_buffer[bytes++] = '\"'; - break; - } - - case 0x5C: // reverse solidus - { - string_buffer[bytes++] = '\\'; - string_buffer[bytes++] = '\\'; - break; - } - - default: - { - // escape control characters (0x00..0x1F) or, if - // ensure_ascii parameter is used, non-ASCII characters - if ((codepoint <= 0x1F) or (ensure_ascii and (codepoint >= 0x7F))) - { - if (codepoint <= 0xFFFF) - { - std::snprintf(string_buffer.data() + bytes, 7, "\\u%04x", - static_cast(codepoint)); - bytes += 6; - } - else - { - std::snprintf(string_buffer.data() + bytes, 13, "\\u%04x\\u%04x", - static_cast(0xD7C0 + (codepoint >> 10)), - static_cast(0xDC00 + (codepoint & 0x3FF))); - bytes += 12; - } - } - else - { - // copy byte to buffer (all previous bytes - // been copied have in default case above) - string_buffer[bytes++] = s[i]; - } - break; - } - } - - // write buffer and reset index; there must be 13 bytes - // left, as this is the maximal number of bytes to be - // written ("\uxxxx\uxxxx\0") for one code point - if (string_buffer.size() - bytes < 13) - { - o->write_characters(string_buffer.data(), bytes); - bytes = 0; - } - break; - } - - case UTF8_REJECT: // decode found invalid UTF-8 byte - { - std::string sn(3, '\0'); - snprintf(&sn[0], sn.size(), "%.2X", byte); - JSON_THROW(type_error::create(316, "invalid UTF-8 byte at index " + std::to_string(i) + ": 0x" + sn)); - } - - default: // decode found yet incomplete multi-byte code point - { - if (not ensure_ascii) - { - // code point will not be escaped - copy byte to buffer - string_buffer[bytes++] = s[i]; - } - break; - } - } - } - - if (JSON_LIKELY(state == UTF8_ACCEPT)) - { - // write buffer - if (bytes > 0) - { - o->write_characters(string_buffer.data(), bytes); - } - } - else - { - // we finish reading, but do not accept: string was incomplete - std::string sn(3, '\0'); - snprintf(&sn[0], sn.size(), "%.2X", static_cast(s.back())); - JSON_THROW(type_error::create(316, "incomplete UTF-8 string; last byte: 0x" + sn)); - } - } - - /*! - @brief dump an integer - - Dump a given integer to output stream @a o. Works internally with - @a number_buffer. - - @param[in] x integer number (signed or unsigned) to dump - @tparam NumberType either @a number_integer_t or @a number_unsigned_t - */ - template::value or - std::is_same::value, - int> = 0> - void dump_integer(NumberType x) - { - // special case for "0" - if (x == 0) - { - o->write_character('0'); - return; - } - - const bool is_negative = (x <= 0) and (x != 0); // see issue #755 - std::size_t i = 0; - - while (x != 0) - { - // spare 1 byte for '\0' - assert(i < number_buffer.size() - 1); - - const auto digit = std::labs(static_cast(x % 10)); - number_buffer[i++] = static_cast('0' + digit); - x /= 10; - } - - if (is_negative) - { - // make sure there is capacity for the '-' - assert(i < number_buffer.size() - 2); - number_buffer[i++] = '-'; - } - - std::reverse(number_buffer.begin(), number_buffer.begin() + i); - o->write_characters(number_buffer.data(), i); - } - - /*! - @brief dump a floating-point number - - Dump a given floating-point number to output stream @a o. Works internally - with @a number_buffer. - - @param[in] x floating-point number to dump - */ - void dump_float(number_float_t x) - { - // NaN / inf - if (not std::isfinite(x)) - { - o->write_characters("null", 4); - return; - } - - // If number_float_t is an IEEE-754 single or double precision number, - // use the Grisu2 algorithm to produce short numbers which are - // guaranteed to round-trip, using strtof and strtod, resp. - // - // NB: The test below works if == . - static constexpr bool is_ieee_single_or_double - = (std::numeric_limits::is_iec559 and std::numeric_limits::digits == 24 and std::numeric_limits::max_exponent == 128) or - (std::numeric_limits::is_iec559 and std::numeric_limits::digits == 53 and std::numeric_limits::max_exponent == 1024); - - dump_float(x, std::integral_constant()); - } - - void dump_float(number_float_t x, std::true_type /*is_ieee_single_or_double*/) - { - char* begin = number_buffer.data(); - char* end = ::nlohmann::detail::to_chars(begin, begin + number_buffer.size(), x); - - o->write_characters(begin, static_cast(end - begin)); - } - - void dump_float(number_float_t x, std::false_type /*is_ieee_single_or_double*/) - { - // get number of digits for a float -> text -> float round-trip - static constexpr auto d = std::numeric_limits::max_digits10; - - // the actual conversion - std::ptrdiff_t len = snprintf(number_buffer.data(), number_buffer.size(), "%.*g", d, x); - - // negative value indicates an error - assert(len > 0); - // check if buffer was large enough - assert(static_cast(len) < number_buffer.size()); - - // erase thousands separator - if (thousands_sep != '\0') - { - const auto end = std::remove(number_buffer.begin(), - number_buffer.begin() + len, thousands_sep); - std::fill(end, number_buffer.end(), '\0'); - assert((end - number_buffer.begin()) <= len); - len = (end - number_buffer.begin()); - } - - // convert decimal point to '.' - if (decimal_point != '\0' and decimal_point != '.') - { - const auto dec_pos = std::find(number_buffer.begin(), number_buffer.end(), decimal_point); - if (dec_pos != number_buffer.end()) - { - *dec_pos = '.'; - } - } - - o->write_characters(number_buffer.data(), static_cast(len)); - - // determine if need to append ".0" - const bool value_is_int_like = - std::none_of(number_buffer.begin(), number_buffer.begin() + len + 1, - [](char c) - { - return (c == '.' or c == 'e'); - }); - - if (value_is_int_like) - { - o->write_characters(".0", 2); - } - } - - /*! - @brief check whether a string is UTF-8 encoded - - The function checks each byte of a string whether it is UTF-8 encoded. The - result of the check is stored in the @a state parameter. The function must - be called initially with state 0 (accept). State 1 means the string must - be rejected, because the current byte is not allowed. If the string is - completely processed, but the state is non-zero, the string ended - prematurely; that is, the last byte indicated more bytes should have - followed. - - @param[in,out] state the state of the decoding - @param[in,out] codep codepoint (valid only if resulting state is UTF8_ACCEPT) - @param[in] byte next byte to decode - @return new state - - @note The function has been edited: a std::array is used. - - @copyright Copyright (c) 2008-2009 Bjoern Hoehrmann - @sa http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ - */ - static uint8_t decode(uint8_t& state, uint32_t& codep, const uint8_t byte) noexcept - { - static const std::array utf8d = - { - { - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 00..1F - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 20..3F - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 40..5F - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 60..7F - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, // 80..9F - 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, // A0..BF - 8, 8, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // C0..DF - 0xA, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x4, 0x3, 0x3, // E0..EF - 0xB, 0x6, 0x6, 0x6, 0x5, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, // F0..FF - 0x0, 0x1, 0x2, 0x3, 0x5, 0x8, 0x7, 0x1, 0x1, 0x1, 0x4, 0x6, 0x1, 0x1, 0x1, 0x1, // s0..s0 - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, // s1..s2 - 1, 2, 1, 1, 1, 1, 1, 2, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, // s3..s4 - 1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, // s5..s6 - 1, 3, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 // s7..s8 - } - }; - - const uint8_t type = utf8d[byte]; - - codep = (state != UTF8_ACCEPT) - ? (byte & 0x3fu) | (codep << 6) - : static_cast(0xff >> type) & (byte); - - state = utf8d[256u + state * 16u + type]; - return state; - } - private: /// the output of the fancy_serializer output_adapter_t o = nullptr; - /// a (hopefully) large enough character buffer - std::array number_buffer{{}}; - - /// the locale - const std::lconv* loc = nullptr; - /// the locale's thousand separator character - const char thousands_sep = '\0'; - /// the locale's decimal point character - const char decimal_point = '\0'; - - /// string buffer - std::array string_buffer{{}}; - /// the indentation character const char indent_char; /// the indentation string string_t indent_string; + + /// Used for serializing "base" objects. Strings are sort of + /// counted in this, but not completely. + primitive_serializer_t prim_serializer; }; } From e38b4e803194a5677e86febb2b525c140a499756 Mon Sep 17 00:00:00 2001 From: Evan Driscoll Date: Sat, 2 Jun 2018 01:07:25 -0500 Subject: [PATCH 07/41] Re-support indentation in fancy_serializer --- .../detail/output/fancy_serializer.hpp | 7 +- single_include/nlohmann/json.hpp | 390 +----------------- test/src/unit-fancy-serialization.cpp | 71 +++- 3 files changed, 75 insertions(+), 393 deletions(-) diff --git a/include/nlohmann/detail/output/fancy_serializer.hpp b/include/nlohmann/detail/output/fancy_serializer.hpp index a7a573c30..ad785f079 100644 --- a/include/nlohmann/detail/output/fancy_serializer.hpp +++ b/include/nlohmann/detail/output/fancy_serializer.hpp @@ -278,11 +278,12 @@ class fancy_serializer } template -std::ostream& fancy_dump(std::ostream& o, const BasicJsonType& j) +std::ostream& fancy_dump(std::ostream& o, const BasicJsonType& j, + unsigned int indent_step, char indent_char) { // do the actual serialization - detail::fancy_serializer s(detail::output_adapter(o), o.fill()); - s.dump(j, false, false, 0u); + detail::fancy_serializer s(detail::output_adapter(o), indent_char); + s.dump(j, indent_step > 0, false, indent_step); return o; } diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 3bfe9ff52..0a9ada08c 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -10088,6 +10088,8 @@ class serializer // #include +// #include + namespace nlohmann { @@ -10100,6 +10102,7 @@ namespace detail template class fancy_serializer { + using primitive_serializer_t = primitive_serializer; using string_t = typename BasicJsonType::string_t; using number_float_t = typename BasicJsonType::number_float_t; using number_integer_t = typename BasicJsonType::number_integer_t; @@ -10113,10 +10116,7 @@ class fancy_serializer @param[in] ichar indentation character to use */ fancy_serializer(output_adapter_t s, const char ichar) - : o(std::move(s)), loc(std::localeconv()), - thousands_sep(loc->thousands_sep == nullptr ? '\0' : * (loc->thousands_sep)), - decimal_point(loc->decimal_point == nullptr ? '\0' : * (loc->decimal_point)), - indent_char(ichar), indent_string(512, indent_char) + : o(std::move(s)), indent_char(ichar), indent_string(512, indent_char) {} // delete because of pointer members @@ -10172,7 +10172,7 @@ class fancy_serializer { o->write_characters(indent_string.c_str(), new_indent); o->write_character('\"'); - dump_escaped(i->first, ensure_ascii); + prim_serializer.dump_escaped(*o, i->first, ensure_ascii); o->write_characters("\": ", 3); dump(i->second, true, ensure_ascii, indent_step, new_indent); o->write_characters(",\n", 2); @@ -10183,7 +10183,7 @@ class fancy_serializer assert(std::next(i) == val.m_value.object->cend()); o->write_characters(indent_string.c_str(), new_indent); o->write_character('\"'); - dump_escaped(i->first, ensure_ascii); + prim_serializer.dump_escaped(*o, i->first, ensure_ascii); o->write_characters("\": ", 3); dump(i->second, true, ensure_ascii, indent_step, new_indent); @@ -10200,7 +10200,7 @@ class fancy_serializer for (std::size_t cnt = 0; cnt < val.m_value.object->size() - 1; ++cnt, ++i) { o->write_character('\"'); - dump_escaped(i->first, ensure_ascii); + prim_serializer.dump_escaped(*o, i->first, ensure_ascii); o->write_characters("\":", 2); dump(i->second, false, ensure_ascii, indent_step, current_indent); o->write_character(','); @@ -10210,7 +10210,7 @@ class fancy_serializer assert(i != val.m_value.object->cend()); assert(std::next(i) == val.m_value.object->cend()); o->write_character('\"'); - dump_escaped(i->first, ensure_ascii); + prim_serializer.dump_escaped(*o, i->first, ensure_ascii); o->write_characters("\":", 2); dump(i->second, false, ensure_ascii, indent_step, current_indent); @@ -10282,7 +10282,7 @@ class fancy_serializer case value_t::string: { o->write_character('\"'); - dump_escaped(*val.m_value.string, ensure_ascii); + prim_serializer.dump_escaped(*o, *val.m_value.string, ensure_ascii); o->write_character('\"'); return; } @@ -10302,19 +10302,19 @@ class fancy_serializer case value_t::number_integer: { - dump_integer(val.m_value.number_integer); + prim_serializer.dump_integer(*o, val.m_value.number_integer); return; } case value_t::number_unsigned: { - dump_integer(val.m_value.number_unsigned); + prim_serializer.dump_integer(*o, val.m_value.number_unsigned); return; } case value_t::number_float: { - dump_float(val.m_value.number_float); + prim_serializer.dump_float(*o, val.m_value.number_float); return; } @@ -10332,378 +10332,28 @@ class fancy_serializer } } - private: - /*! - @brief dump escaped string - - Escape a string by replacing certain special characters by a sequence of an - escape character (backslash) and another character and other control - characters by a sequence of "\u" followed by a four-digit hex - representation. The escaped string is written to output stream @a o. - - @param[in] s the string to escape - @param[in] ensure_ascii whether to escape non-ASCII characters with - \uXXXX sequences - - @complexity Linear in the length of string @a s. - */ - void dump_escaped(const string_t& s, const bool ensure_ascii) - { - uint32_t codepoint; - uint8_t state = UTF8_ACCEPT; - std::size_t bytes = 0; // number of bytes written to string_buffer - - for (std::size_t i = 0; i < s.size(); ++i) - { - const auto byte = static_cast(s[i]); - - switch (decode(state, codepoint, byte)) - { - case UTF8_ACCEPT: // decode found a new code point - { - switch (codepoint) - { - case 0x08: // backspace - { - string_buffer[bytes++] = '\\'; - string_buffer[bytes++] = 'b'; - break; - } - - case 0x09: // horizontal tab - { - string_buffer[bytes++] = '\\'; - string_buffer[bytes++] = 't'; - break; - } - - case 0x0A: // newline - { - string_buffer[bytes++] = '\\'; - string_buffer[bytes++] = 'n'; - break; - } - - case 0x0C: // formfeed - { - string_buffer[bytes++] = '\\'; - string_buffer[bytes++] = 'f'; - break; - } - - case 0x0D: // carriage return - { - string_buffer[bytes++] = '\\'; - string_buffer[bytes++] = 'r'; - break; - } - - case 0x22: // quotation mark - { - string_buffer[bytes++] = '\\'; - string_buffer[bytes++] = '\"'; - break; - } - - case 0x5C: // reverse solidus - { - string_buffer[bytes++] = '\\'; - string_buffer[bytes++] = '\\'; - break; - } - - default: - { - // escape control characters (0x00..0x1F) or, if - // ensure_ascii parameter is used, non-ASCII characters - if ((codepoint <= 0x1F) or (ensure_ascii and (codepoint >= 0x7F))) - { - if (codepoint <= 0xFFFF) - { - std::snprintf(string_buffer.data() + bytes, 7, "\\u%04x", - static_cast(codepoint)); - bytes += 6; - } - else - { - std::snprintf(string_buffer.data() + bytes, 13, "\\u%04x\\u%04x", - static_cast(0xD7C0 + (codepoint >> 10)), - static_cast(0xDC00 + (codepoint & 0x3FF))); - bytes += 12; - } - } - else - { - // copy byte to buffer (all previous bytes - // been copied have in default case above) - string_buffer[bytes++] = s[i]; - } - break; - } - } - - // write buffer and reset index; there must be 13 bytes - // left, as this is the maximal number of bytes to be - // written ("\uxxxx\uxxxx\0") for one code point - if (string_buffer.size() - bytes < 13) - { - o->write_characters(string_buffer.data(), bytes); - bytes = 0; - } - break; - } - - case UTF8_REJECT: // decode found invalid UTF-8 byte - { - std::string sn(3, '\0'); - snprintf(&sn[0], sn.size(), "%.2X", byte); - JSON_THROW(type_error::create(316, "invalid UTF-8 byte at index " + std::to_string(i) + ": 0x" + sn)); - } - - default: // decode found yet incomplete multi-byte code point - { - if (not ensure_ascii) - { - // code point will not be escaped - copy byte to buffer - string_buffer[bytes++] = s[i]; - } - break; - } - } - } - - if (JSON_LIKELY(state == UTF8_ACCEPT)) - { - // write buffer - if (bytes > 0) - { - o->write_characters(string_buffer.data(), bytes); - } - } - else - { - // we finish reading, but do not accept: string was incomplete - std::string sn(3, '\0'); - snprintf(&sn[0], sn.size(), "%.2X", static_cast(s.back())); - JSON_THROW(type_error::create(316, "incomplete UTF-8 string; last byte: 0x" + sn)); - } - } - - /*! - @brief dump an integer - - Dump a given integer to output stream @a o. Works internally with - @a number_buffer. - - @param[in] x integer number (signed or unsigned) to dump - @tparam NumberType either @a number_integer_t or @a number_unsigned_t - */ - template::value or - std::is_same::value, - int> = 0> - void dump_integer(NumberType x) - { - // special case for "0" - if (x == 0) - { - o->write_character('0'); - return; - } - - const bool is_negative = (x <= 0) and (x != 0); // see issue #755 - std::size_t i = 0; - - while (x != 0) - { - // spare 1 byte for '\0' - assert(i < number_buffer.size() - 1); - - const auto digit = std::labs(static_cast(x % 10)); - number_buffer[i++] = static_cast('0' + digit); - x /= 10; - } - - if (is_negative) - { - // make sure there is capacity for the '-' - assert(i < number_buffer.size() - 2); - number_buffer[i++] = '-'; - } - - std::reverse(number_buffer.begin(), number_buffer.begin() + i); - o->write_characters(number_buffer.data(), i); - } - - /*! - @brief dump a floating-point number - - Dump a given floating-point number to output stream @a o. Works internally - with @a number_buffer. - - @param[in] x floating-point number to dump - */ - void dump_float(number_float_t x) - { - // NaN / inf - if (not std::isfinite(x)) - { - o->write_characters("null", 4); - return; - } - - // If number_float_t is an IEEE-754 single or double precision number, - // use the Grisu2 algorithm to produce short numbers which are - // guaranteed to round-trip, using strtof and strtod, resp. - // - // NB: The test below works if == . - static constexpr bool is_ieee_single_or_double - = (std::numeric_limits::is_iec559 and std::numeric_limits::digits == 24 and std::numeric_limits::max_exponent == 128) or - (std::numeric_limits::is_iec559 and std::numeric_limits::digits == 53 and std::numeric_limits::max_exponent == 1024); - - dump_float(x, std::integral_constant()); - } - - void dump_float(number_float_t x, std::true_type /*is_ieee_single_or_double*/) - { - char* begin = number_buffer.data(); - char* end = ::nlohmann::detail::to_chars(begin, begin + number_buffer.size(), x); - - o->write_characters(begin, static_cast(end - begin)); - } - - void dump_float(number_float_t x, std::false_type /*is_ieee_single_or_double*/) - { - // get number of digits for a float -> text -> float round-trip - static constexpr auto d = std::numeric_limits::max_digits10; - - // the actual conversion - std::ptrdiff_t len = snprintf(number_buffer.data(), number_buffer.size(), "%.*g", d, x); - - // negative value indicates an error - assert(len > 0); - // check if buffer was large enough - assert(static_cast(len) < number_buffer.size()); - - // erase thousands separator - if (thousands_sep != '\0') - { - const auto end = std::remove(number_buffer.begin(), - number_buffer.begin() + len, thousands_sep); - std::fill(end, number_buffer.end(), '\0'); - assert((end - number_buffer.begin()) <= len); - len = (end - number_buffer.begin()); - } - - // convert decimal point to '.' - if (decimal_point != '\0' and decimal_point != '.') - { - const auto dec_pos = std::find(number_buffer.begin(), number_buffer.end(), decimal_point); - if (dec_pos != number_buffer.end()) - { - *dec_pos = '.'; - } - } - - o->write_characters(number_buffer.data(), static_cast(len)); - - // determine if need to append ".0" - const bool value_is_int_like = - std::none_of(number_buffer.begin(), number_buffer.begin() + len + 1, - [](char c) - { - return (c == '.' or c == 'e'); - }); - - if (value_is_int_like) - { - o->write_characters(".0", 2); - } - } - - /*! - @brief check whether a string is UTF-8 encoded - - The function checks each byte of a string whether it is UTF-8 encoded. The - result of the check is stored in the @a state parameter. The function must - be called initially with state 0 (accept). State 1 means the string must - be rejected, because the current byte is not allowed. If the string is - completely processed, but the state is non-zero, the string ended - prematurely; that is, the last byte indicated more bytes should have - followed. - - @param[in,out] state the state of the decoding - @param[in,out] codep codepoint (valid only if resulting state is UTF8_ACCEPT) - @param[in] byte next byte to decode - @return new state - - @note The function has been edited: a std::array is used. - - @copyright Copyright (c) 2008-2009 Bjoern Hoehrmann - @sa http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ - */ - static uint8_t decode(uint8_t& state, uint32_t& codep, const uint8_t byte) noexcept - { - static const std::array utf8d = - { - { - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 00..1F - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 20..3F - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 40..5F - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 60..7F - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, // 80..9F - 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, // A0..BF - 8, 8, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // C0..DF - 0xA, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x4, 0x3, 0x3, // E0..EF - 0xB, 0x6, 0x6, 0x6, 0x5, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, // F0..FF - 0x0, 0x1, 0x2, 0x3, 0x5, 0x8, 0x7, 0x1, 0x1, 0x1, 0x4, 0x6, 0x1, 0x1, 0x1, 0x1, // s0..s0 - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, // s1..s2 - 1, 2, 1, 1, 1, 1, 1, 2, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, // s3..s4 - 1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, // s5..s6 - 1, 3, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 // s7..s8 - } - }; - - const uint8_t type = utf8d[byte]; - - codep = (state != UTF8_ACCEPT) - ? (byte & 0x3fu) | (codep << 6) - : static_cast(0xff >> type) & (byte); - - state = utf8d[256u + state * 16u + type]; - return state; - } - private: /// the output of the fancy_serializer output_adapter_t o = nullptr; - /// a (hopefully) large enough character buffer - std::array number_buffer{{}}; - - /// the locale - const std::lconv* loc = nullptr; - /// the locale's thousand separator character - const char thousands_sep = '\0'; - /// the locale's decimal point character - const char decimal_point = '\0'; - - /// string buffer - std::array string_buffer{{}}; - /// the indentation character const char indent_char; /// the indentation string string_t indent_string; + + /// Used for serializing "base" objects. Strings are sort of + /// counted in this, but not completely. + primitive_serializer_t prim_serializer; }; } template -std::ostream& fancy_dump(std::ostream& o, const BasicJsonType& j) +std::ostream& fancy_dump(std::ostream& o, const BasicJsonType& j, + unsigned int indent_step, char indent_char) { // do the actual serialization - detail::fancy_serializer s(detail::output_adapter(o), o.fill()); - s.dump(j, false, false, 0u); + detail::fancy_serializer s(detail::output_adapter(o), indent_char); + s.dump(j, indent_step > 0, false, indent_step); return o; } diff --git a/test/src/unit-fancy-serialization.cpp b/test/src/unit-fancy-serialization.cpp index 88f6b5931..46c1f4cea 100644 --- a/test/src/unit-fancy-serialization.cpp +++ b/test/src/unit-fancy-serialization.cpp @@ -34,48 +34,79 @@ SOFTWARE. using nlohmann::json; using nlohmann::fancy_dump; +std::string fancy_to_string(json j, unsigned int indent_step = 0, char indent_char = ' ') +{ + std::stringstream ss; + fancy_dump(ss, j, indent_step, indent_char); + return ss.str(); +} + TEST_CASE("serialization") { SECTION("primitives") { SECTION("null") { - std::stringstream ss; - json j; - fancy_dump(ss, j); - CHECK(ss.str() == "null"); + auto str = fancy_to_string({}); + CHECK(str == "null"); } SECTION("true") { - std::stringstream ss; - json j = true; - fancy_dump(ss, j); - CHECK(ss.str() == "true"); + auto str = fancy_to_string(true); + CHECK(str == "true"); } SECTION("false") { - std::stringstream ss; - json j = false; - fancy_dump(ss, j); - CHECK(ss.str() == "false"); + auto str = fancy_to_string(false); + CHECK(str == "false"); } SECTION("integer") { - std::stringstream ss; - json j = 10; - fancy_dump(ss, j); - CHECK(ss.str() == "10"); + auto str = fancy_to_string(10); + CHECK(str == "10"); } SECTION("floating point") { - std::stringstream ss; - json j = 7.5; - fancy_dump(ss, j); - CHECK(ss.str() == "7.5"); + auto str = fancy_to_string(7.5); + CHECK(str == "7.5"); } } + + SECTION("given width") + { + auto str = fancy_to_string({"foo", 1, 2, 3, false, {{"one", 1}}}, 4); + CHECK(str == + "[\n" + " \"foo\",\n" + " 1,\n" + " 2,\n" + " 3,\n" + " false,\n" + " {\n" + " \"one\": 1\n" + " }\n" + "]" + ); + } + + SECTION("given fill") + { + auto str = fancy_to_string({"foo", 1, 2, 3, false, {{"one", 1}}}, 1, '\t'); + CHECK(str == + "[\n" + "\t\"foo\",\n" + "\t1,\n" + "\t2,\n" + "\t3,\n" + "\tfalse,\n" + "\t{\n" + "\t\t\"one\": 1\n" + "\t}\n" + "]" + ); + } } From 003f3e298b6b6954d3d478c62db285c85caa3801 Mon Sep 17 00:00:00 2001 From: Evan Driscoll Date: Sat, 2 Jun 2018 01:18:55 -0500 Subject: [PATCH 08/41] Introduce fancy_serializer_style --- .../detail/output/fancy_serializer.hpp | 50 +++++++++++-------- single_include/nlohmann/json.hpp | 50 +++++++++++-------- test/src/unit-fancy-serialization.cpp | 15 ++++-- 3 files changed, 69 insertions(+), 46 deletions(-) diff --git a/include/nlohmann/detail/output/fancy_serializer.hpp b/include/nlohmann/detail/output/fancy_serializer.hpp index ad785f079..c3fba10fd 100644 --- a/include/nlohmann/detail/output/fancy_serializer.hpp +++ b/include/nlohmann/detail/output/fancy_serializer.hpp @@ -23,6 +23,13 @@ namespace nlohmann { + +struct fancy_serializer_style +{ + unsigned int indent_step = 0; + char indent_char = ' '; +}; + namespace detail { /////////////////// @@ -45,8 +52,9 @@ class fancy_serializer @param[in] s output stream to serialize to @param[in] ichar indentation character to use */ - fancy_serializer(output_adapter_t s, const char ichar) - : o(std::move(s)), indent_char(ichar), indent_string(512, indent_char) + fancy_serializer(output_adapter_t s, + const fancy_serializer_style& st) + : o(std::move(s)), indent_string(512, st.indent_char), style(st) {} // delete because of pointer members @@ -70,9 +78,8 @@ class fancy_serializer @param[in] indent_step the indent level @param[in] current_indent the current indent level (only used internally) */ - void dump(const BasicJsonType& val, const bool pretty_print, + void dump(const BasicJsonType& val, const bool ensure_ascii, - const unsigned int indent_step, const unsigned int current_indent = 0) { switch (val.m_type) @@ -85,12 +92,12 @@ class fancy_serializer return; } - if (pretty_print) + if (style.indent_step > 0) { o->write_characters("{\n", 2); // variable to hold indentation for recursive calls - const auto new_indent = current_indent + indent_step; + const auto new_indent = current_indent + style.indent_step; if (JSON_UNLIKELY(indent_string.size() < new_indent)) { indent_string.resize(indent_string.size() * 2, ' '); @@ -104,7 +111,7 @@ class fancy_serializer o->write_character('\"'); prim_serializer.dump_escaped(*o, i->first, ensure_ascii); o->write_characters("\": ", 3); - dump(i->second, true, ensure_ascii, indent_step, new_indent); + dump(i->second, ensure_ascii, new_indent); o->write_characters(",\n", 2); } @@ -115,7 +122,7 @@ class fancy_serializer o->write_character('\"'); prim_serializer.dump_escaped(*o, i->first, ensure_ascii); o->write_characters("\": ", 3); - dump(i->second, true, ensure_ascii, indent_step, new_indent); + dump(i->second, ensure_ascii, new_indent); o->write_character('\n'); o->write_characters(indent_string.c_str(), current_indent); @@ -132,7 +139,7 @@ class fancy_serializer o->write_character('\"'); prim_serializer.dump_escaped(*o, i->first, ensure_ascii); o->write_characters("\":", 2); - dump(i->second, false, ensure_ascii, indent_step, current_indent); + dump(i->second, ensure_ascii, current_indent); o->write_character(','); } @@ -142,7 +149,7 @@ class fancy_serializer o->write_character('\"'); prim_serializer.dump_escaped(*o, i->first, ensure_ascii); o->write_characters("\":", 2); - dump(i->second, false, ensure_ascii, indent_step, current_indent); + dump(i->second, ensure_ascii, current_indent); o->write_character('}'); } @@ -158,12 +165,12 @@ class fancy_serializer return; } - if (pretty_print) + if (style.indent_step > 0) { o->write_characters("[\n", 2); // variable to hold indentation for recursive calls - const auto new_indent = current_indent + indent_step; + const auto new_indent = current_indent + style.indent_step; if (JSON_UNLIKELY(indent_string.size() < new_indent)) { indent_string.resize(indent_string.size() * 2, ' '); @@ -174,14 +181,14 @@ class fancy_serializer i != val.m_value.array->cend() - 1; ++i) { o->write_characters(indent_string.c_str(), new_indent); - dump(*i, true, ensure_ascii, indent_step, new_indent); + dump(*i, ensure_ascii, new_indent); o->write_characters(",\n", 2); } // last element assert(not val.m_value.array->empty()); o->write_characters(indent_string.c_str(), new_indent); - dump(val.m_value.array->back(), true, ensure_ascii, indent_step, new_indent); + dump(val.m_value.array->back(), ensure_ascii, new_indent); o->write_character('\n'); o->write_characters(indent_string.c_str(), current_indent); @@ -195,13 +202,13 @@ class fancy_serializer for (auto i = val.m_value.array->cbegin(); i != val.m_value.array->cend() - 1; ++i) { - dump(*i, false, ensure_ascii, indent_step, current_indent); + dump(*i, ensure_ascii, current_indent); o->write_character(','); } // last element assert(not val.m_value.array->empty()); - dump(val.m_value.array->back(), false, ensure_ascii, indent_step, current_indent); + dump(val.m_value.array->back(), ensure_ascii, current_indent); o->write_character(']'); } @@ -266,24 +273,25 @@ class fancy_serializer /// the output of the fancy_serializer output_adapter_t o = nullptr; - /// the indentation character - const char indent_char; /// the indentation string string_t indent_string; /// Used for serializing "base" objects. Strings are sort of /// counted in this, but not completely. primitive_serializer_t prim_serializer; + + /// Output style + fancy_serializer_style style; }; } template std::ostream& fancy_dump(std::ostream& o, const BasicJsonType& j, - unsigned int indent_step, char indent_char) + fancy_serializer_style style) { // do the actual serialization - detail::fancy_serializer s(detail::output_adapter(o), indent_char); - s.dump(j, indent_step > 0, false, indent_step); + detail::fancy_serializer s(detail::output_adapter(o), style); + s.dump(j, false, 0u); return o; } diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 0a9ada08c..c6fc6a283 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -10093,6 +10093,13 @@ class serializer namespace nlohmann { + +struct fancy_serializer_style +{ + unsigned int indent_step = 0; + char indent_char = ' '; +}; + namespace detail { /////////////////// @@ -10115,8 +10122,9 @@ class fancy_serializer @param[in] s output stream to serialize to @param[in] ichar indentation character to use */ - fancy_serializer(output_adapter_t s, const char ichar) - : o(std::move(s)), indent_char(ichar), indent_string(512, indent_char) + fancy_serializer(output_adapter_t s, + const fancy_serializer_style& st) + : o(std::move(s)), indent_string(512, st.indent_char), style(st) {} // delete because of pointer members @@ -10140,9 +10148,8 @@ class fancy_serializer @param[in] indent_step the indent level @param[in] current_indent the current indent level (only used internally) */ - void dump(const BasicJsonType& val, const bool pretty_print, + void dump(const BasicJsonType& val, const bool ensure_ascii, - const unsigned int indent_step, const unsigned int current_indent = 0) { switch (val.m_type) @@ -10155,12 +10162,12 @@ class fancy_serializer return; } - if (pretty_print) + if (style.indent_step > 0) { o->write_characters("{\n", 2); // variable to hold indentation for recursive calls - const auto new_indent = current_indent + indent_step; + const auto new_indent = current_indent + style.indent_step; if (JSON_UNLIKELY(indent_string.size() < new_indent)) { indent_string.resize(indent_string.size() * 2, ' '); @@ -10174,7 +10181,7 @@ class fancy_serializer o->write_character('\"'); prim_serializer.dump_escaped(*o, i->first, ensure_ascii); o->write_characters("\": ", 3); - dump(i->second, true, ensure_ascii, indent_step, new_indent); + dump(i->second, ensure_ascii, new_indent); o->write_characters(",\n", 2); } @@ -10185,7 +10192,7 @@ class fancy_serializer o->write_character('\"'); prim_serializer.dump_escaped(*o, i->first, ensure_ascii); o->write_characters("\": ", 3); - dump(i->second, true, ensure_ascii, indent_step, new_indent); + dump(i->second, ensure_ascii, new_indent); o->write_character('\n'); o->write_characters(indent_string.c_str(), current_indent); @@ -10202,7 +10209,7 @@ class fancy_serializer o->write_character('\"'); prim_serializer.dump_escaped(*o, i->first, ensure_ascii); o->write_characters("\":", 2); - dump(i->second, false, ensure_ascii, indent_step, current_indent); + dump(i->second, ensure_ascii, current_indent); o->write_character(','); } @@ -10212,7 +10219,7 @@ class fancy_serializer o->write_character('\"'); prim_serializer.dump_escaped(*o, i->first, ensure_ascii); o->write_characters("\":", 2); - dump(i->second, false, ensure_ascii, indent_step, current_indent); + dump(i->second, ensure_ascii, current_indent); o->write_character('}'); } @@ -10228,12 +10235,12 @@ class fancy_serializer return; } - if (pretty_print) + if (style.indent_step > 0) { o->write_characters("[\n", 2); // variable to hold indentation for recursive calls - const auto new_indent = current_indent + indent_step; + const auto new_indent = current_indent + style.indent_step; if (JSON_UNLIKELY(indent_string.size() < new_indent)) { indent_string.resize(indent_string.size() * 2, ' '); @@ -10244,14 +10251,14 @@ class fancy_serializer i != val.m_value.array->cend() - 1; ++i) { o->write_characters(indent_string.c_str(), new_indent); - dump(*i, true, ensure_ascii, indent_step, new_indent); + dump(*i, ensure_ascii, new_indent); o->write_characters(",\n", 2); } // last element assert(not val.m_value.array->empty()); o->write_characters(indent_string.c_str(), new_indent); - dump(val.m_value.array->back(), true, ensure_ascii, indent_step, new_indent); + dump(val.m_value.array->back(), ensure_ascii, new_indent); o->write_character('\n'); o->write_characters(indent_string.c_str(), current_indent); @@ -10265,13 +10272,13 @@ class fancy_serializer for (auto i = val.m_value.array->cbegin(); i != val.m_value.array->cend() - 1; ++i) { - dump(*i, false, ensure_ascii, indent_step, current_indent); + dump(*i, ensure_ascii, current_indent); o->write_character(','); } // last element assert(not val.m_value.array->empty()); - dump(val.m_value.array->back(), false, ensure_ascii, indent_step, current_indent); + dump(val.m_value.array->back(), ensure_ascii, current_indent); o->write_character(']'); } @@ -10336,24 +10343,25 @@ class fancy_serializer /// the output of the fancy_serializer output_adapter_t o = nullptr; - /// the indentation character - const char indent_char; /// the indentation string string_t indent_string; /// Used for serializing "base" objects. Strings are sort of /// counted in this, but not completely. primitive_serializer_t prim_serializer; + + /// Output style + fancy_serializer_style style; }; } template std::ostream& fancy_dump(std::ostream& o, const BasicJsonType& j, - unsigned int indent_step, char indent_char) + fancy_serializer_style style) { // do the actual serialization - detail::fancy_serializer s(detail::output_adapter(o), indent_char); - s.dump(j, indent_step > 0, false, indent_step); + detail::fancy_serializer s(detail::output_adapter(o), style); + s.dump(j, false, 0u); return o; } diff --git a/test/src/unit-fancy-serialization.cpp b/test/src/unit-fancy-serialization.cpp index 46c1f4cea..17eb6ba3d 100644 --- a/test/src/unit-fancy-serialization.cpp +++ b/test/src/unit-fancy-serialization.cpp @@ -33,11 +33,12 @@ SOFTWARE. using nlohmann::json; using nlohmann::fancy_dump; +using nlohmann::fancy_serializer_style; -std::string fancy_to_string(json j, unsigned int indent_step = 0, char indent_char = ' ') +std::string fancy_to_string(json j, fancy_serializer_style style = fancy_serializer_style()) { std::stringstream ss; - fancy_dump(ss, j, indent_step, indent_char); + fancy_dump(ss, j, style); return ss.str(); } @@ -78,7 +79,9 @@ TEST_CASE("serialization") SECTION("given width") { - auto str = fancy_to_string({"foo", 1, 2, 3, false, {{"one", 1}}}, 4); + fancy_serializer_style style; + style.indent_step = 4; + auto str = fancy_to_string({"foo", 1, 2, 3, false, {{"one", 1}}}, style); CHECK(str == "[\n" " \"foo\",\n" @@ -95,7 +98,11 @@ TEST_CASE("serialization") SECTION("given fill") { - auto str = fancy_to_string({"foo", 1, 2, 3, false, {{"one", 1}}}, 1, '\t'); + fancy_serializer_style style; + style.indent_step = 1; + style.indent_char = '\t'; + + auto str = fancy_to_string({"foo", 1, 2, 3, false, {{"one", 1}}}, style); CHECK(str == "[\n" "\t\"foo\",\n" From d54f4653ed772cf049680afc2eb866c25a265705 Mon Sep 17 00:00:00 2001 From: Evan Driscoll Date: Sat, 2 Jun 2018 02:09:04 -0500 Subject: [PATCH 09/41] It's now possible to limit the size of strings --- .../detail/output/fancy_serializer.hpp | 58 +++++++++++++++- single_include/nlohmann/json.hpp | 58 +++++++++++++++- test/src/unit-fancy-serialization.cpp | 68 +++++++++++++++++++ 3 files changed, 182 insertions(+), 2 deletions(-) diff --git a/include/nlohmann/detail/output/fancy_serializer.hpp b/include/nlohmann/detail/output/fancy_serializer.hpp index c3fba10fd..361a76ae9 100644 --- a/include/nlohmann/detail/output/fancy_serializer.hpp +++ b/include/nlohmann/detail/output/fancy_serializer.hpp @@ -28,6 +28,8 @@ struct fancy_serializer_style { unsigned int indent_step = 0; char indent_char = ' '; + + unsigned int strings_maximum_length = 0; }; namespace detail @@ -219,7 +221,61 @@ class fancy_serializer case value_t::string: { o->write_character('\"'); - prim_serializer.dump_escaped(*o, *val.m_value.string, ensure_ascii); + if (style.strings_maximum_length == 0) + { + prim_serializer.dump_escaped(*o, *val.m_value.string, ensure_ascii); + } + else + { + std::stringstream ss; + nlohmann::detail::output_adapter o_string(ss); + nlohmann::detail::output_adapter_t oo_string = o_string; + prim_serializer.dump_escaped(*oo_string, *val.m_value.string, ensure_ascii); + + std::string str = ss.str(); + if (str.size() <= style.strings_maximum_length) + { + o->write_characters(str.c_str(), str.size()); + } + else + { + const unsigned start_len = [](unsigned int maxl) + { + if (maxl <= 3) + { + // There is only room for the ellipsis, + // no characters from the string + return 0u; + } + else if (maxl <= 5) + { + // With four allowed characters, we add in the + // first from the string. With five, we add in + // the *last* instead, so still just one at + // the start. + return 1u; + } + else + { + // We subtract three for the ellipsis + // and one for the last character. + return maxl - 4; + } + }(style.strings_maximum_length); + + const unsigned end_len = + style.strings_maximum_length >= 5 ? 1 : 0; + + const unsigned ellipsis_length = + style.strings_maximum_length >= 3 + ? 3 + : style.strings_maximum_length; + + o->write_characters(str.c_str(), start_len); + o->write_characters("...", ellipsis_length); + o->write_characters(str.c_str() + str.size() - end_len, end_len); + } + } o->write_character('\"'); return; } diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index c6fc6a283..979e55671 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -10098,6 +10098,8 @@ struct fancy_serializer_style { unsigned int indent_step = 0; char indent_char = ' '; + + unsigned int strings_maximum_length = 0; }; namespace detail @@ -10289,7 +10291,61 @@ class fancy_serializer case value_t::string: { o->write_character('\"'); - prim_serializer.dump_escaped(*o, *val.m_value.string, ensure_ascii); + if (style.strings_maximum_length == 0) + { + prim_serializer.dump_escaped(*o, *val.m_value.string, ensure_ascii); + } + else + { + std::stringstream ss; + nlohmann::detail::output_adapter o_string(ss); + nlohmann::detail::output_adapter_t oo_string = o_string; + prim_serializer.dump_escaped(*oo_string, *val.m_value.string, ensure_ascii); + + std::string str = ss.str(); + if (str.size() <= style.strings_maximum_length) + { + o->write_characters(str.c_str(), str.size()); + } + else + { + const unsigned start_len = [](unsigned int maxl) + { + if (maxl <= 3) + { + // There is only room for the ellipsis, + // no characters from the string + return 0u; + } + else if (maxl <= 5) + { + // With four allowed characters, we add in the + // first from the string. With five, we add in + // the *last* instead, so still just one at + // the start. + return 1u; + } + else + { + // We subtract three for the ellipsis + // and one for the last character. + return maxl - 4; + } + }(style.strings_maximum_length); + + const unsigned end_len = + style.strings_maximum_length >= 5 ? 1 : 0; + + const unsigned ellipsis_length = + style.strings_maximum_length >= 3 + ? 3 + : style.strings_maximum_length; + + o->write_characters(str.c_str(), start_len); + o->write_characters("...", ellipsis_length); + o->write_characters(str.c_str() + str.size() - end_len, end_len); + } + } o->write_character('\"'); return; } diff --git a/test/src/unit-fancy-serialization.cpp b/test/src/unit-fancy-serialization.cpp index 17eb6ba3d..3a8e79202 100644 --- a/test/src/unit-fancy-serialization.cpp +++ b/test/src/unit-fancy-serialization.cpp @@ -77,6 +77,74 @@ TEST_CASE("serialization") } } + SECTION("strings") + { + SECTION("long strings usually print") + { + auto str = fancy_to_string( + "The quick brown fox jumps over the lazy brown dog"); + CHECK(str == + "\"The quick brown fox jumps over the lazy brown dog\""); + } + + SECTION("long strings can be shortened") + { + fancy_serializer_style style; + style.strings_maximum_length = 10; + + auto str = fancy_to_string( + "The quick brown fox jumps over the lazy brown dog", + style); + CHECK(str == "\"The qu...g\""); + } + + SECTION("requesting extremely short strings limits what is included") + { + const char* const quick = "The quick brown fox jumps over the lazy brown dog"; + + std::pair tests[] = + { + {5, "\"T...g\""}, + {4, "\"T...\""}, + {3, "\"...\""}, + {2, "\"..\""}, + {1, "\".\""}, + }; + + for (auto test : tests) + { + fancy_serializer_style style; + style.strings_maximum_length = test.first; + auto str = fancy_to_string(quick, style); + CHECK(str == test.second); + } + } + + SECTION("But you cannot ask for a length of zero; that means unlimited") + { + fancy_serializer_style style; + style.strings_maximum_length = 0; + + auto str = fancy_to_string( + "The quick brown fox jumps over the lazy brown dog", + style); + CHECK(str == + "\"The quick brown fox jumps over the lazy brown dog\""); + } + + SECTION("\"Limiting\" to something long doesn't do anything") + { + fancy_serializer_style style; + style.strings_maximum_length = 100; + + auto str = fancy_to_string( + "The quick brown fox jumps over the lazy brown dog", + style); + CHECK(str == + "\"The quick brown fox jumps over the lazy brown dog\""); + } + } + SECTION("given width") { fancy_serializer_style style; From 7dbfe2459be9e8aac2d1a678b304891319f3999c Mon Sep 17 00:00:00 2001 From: Evan Driscoll Date: Sat, 2 Jun 2018 20:15:07 -0500 Subject: [PATCH 10/41] Refactor: move string handling to dump_string --- .../detail/output/fancy_serializer.hpp | 120 +++++++++--------- single_include/nlohmann/json.hpp | 120 +++++++++--------- 2 files changed, 126 insertions(+), 114 deletions(-) diff --git a/include/nlohmann/detail/output/fancy_serializer.hpp b/include/nlohmann/detail/output/fancy_serializer.hpp index 361a76ae9..e26e119a5 100644 --- a/include/nlohmann/detail/output/fancy_serializer.hpp +++ b/include/nlohmann/detail/output/fancy_serializer.hpp @@ -220,63 +220,7 @@ class fancy_serializer case value_t::string: { - o->write_character('\"'); - if (style.strings_maximum_length == 0) - { - prim_serializer.dump_escaped(*o, *val.m_value.string, ensure_ascii); - } - else - { - std::stringstream ss; - nlohmann::detail::output_adapter o_string(ss); - nlohmann::detail::output_adapter_t oo_string = o_string; - prim_serializer.dump_escaped(*oo_string, *val.m_value.string, ensure_ascii); - - std::string str = ss.str(); - if (str.size() <= style.strings_maximum_length) - { - o->write_characters(str.c_str(), str.size()); - } - else - { - const unsigned start_len = [](unsigned int maxl) - { - if (maxl <= 3) - { - // There is only room for the ellipsis, - // no characters from the string - return 0u; - } - else if (maxl <= 5) - { - // With four allowed characters, we add in the - // first from the string. With five, we add in - // the *last* instead, so still just one at - // the start. - return 1u; - } - else - { - // We subtract three for the ellipsis - // and one for the last character. - return maxl - 4; - } - }(style.strings_maximum_length); - - const unsigned end_len = - style.strings_maximum_length >= 5 ? 1 : 0; - - const unsigned ellipsis_length = - style.strings_maximum_length >= 3 - ? 3 - : style.strings_maximum_length; - - o->write_characters(str.c_str(), start_len); - o->write_characters("...", ellipsis_length); - o->write_characters(str.c_str() + str.size() - end_len, end_len); - } - } - o->write_character('\"'); + dump_string(*val.m_value.string, ensure_ascii); return; } @@ -325,6 +269,68 @@ class fancy_serializer } } + private: + void dump_string(string_t const& str, bool ensure_ascii) + { + o->write_character('\"'); + if (style.strings_maximum_length == 0) + { + prim_serializer.dump_escaped(*o, str, ensure_ascii); + } + else + { + std::stringstream ss; + nlohmann::detail::output_adapter o_string(ss); + nlohmann::detail::output_adapter_t oo_string = o_string; + prim_serializer.dump_escaped(*oo_string, str, ensure_ascii); + + std::string full_str = ss.str(); + if (full_str.size() <= style.strings_maximum_length) + { + o->write_characters(full_str.c_str(), full_str.size()); + } + else + { + const unsigned start_len = [](unsigned int maxl) + { + if (maxl <= 3) + { + // There is only room for the ellipsis, + // no characters from the string + return 0u; + } + else if (maxl <= 5) + { + // With four allowed characters, we add in the + // first from the string. With five, we add in + // the *last* instead, so still just one at + // the start. + return 1u; + } + else + { + // We subtract three for the ellipsis + // and one for the last character. + return maxl - 4; + } + }(style.strings_maximum_length); + + const unsigned end_len = + style.strings_maximum_length >= 5 ? 1 : 0; + + const unsigned ellipsis_length = + style.strings_maximum_length >= 3 + ? 3 + : style.strings_maximum_length; + + o->write_characters(full_str.c_str(), start_len); + o->write_characters("...", ellipsis_length); + o->write_characters(full_str.c_str() + str.size() - end_len, end_len); + } + } + o->write_character('\"'); + } + private: /// the output of the fancy_serializer output_adapter_t o = nullptr; diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 979e55671..e693cfc12 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -10290,63 +10290,7 @@ class fancy_serializer case value_t::string: { - o->write_character('\"'); - if (style.strings_maximum_length == 0) - { - prim_serializer.dump_escaped(*o, *val.m_value.string, ensure_ascii); - } - else - { - std::stringstream ss; - nlohmann::detail::output_adapter o_string(ss); - nlohmann::detail::output_adapter_t oo_string = o_string; - prim_serializer.dump_escaped(*oo_string, *val.m_value.string, ensure_ascii); - - std::string str = ss.str(); - if (str.size() <= style.strings_maximum_length) - { - o->write_characters(str.c_str(), str.size()); - } - else - { - const unsigned start_len = [](unsigned int maxl) - { - if (maxl <= 3) - { - // There is only room for the ellipsis, - // no characters from the string - return 0u; - } - else if (maxl <= 5) - { - // With four allowed characters, we add in the - // first from the string. With five, we add in - // the *last* instead, so still just one at - // the start. - return 1u; - } - else - { - // We subtract three for the ellipsis - // and one for the last character. - return maxl - 4; - } - }(style.strings_maximum_length); - - const unsigned end_len = - style.strings_maximum_length >= 5 ? 1 : 0; - - const unsigned ellipsis_length = - style.strings_maximum_length >= 3 - ? 3 - : style.strings_maximum_length; - - o->write_characters(str.c_str(), start_len); - o->write_characters("...", ellipsis_length); - o->write_characters(str.c_str() + str.size() - end_len, end_len); - } - } - o->write_character('\"'); + dump_string(*val.m_value.string, ensure_ascii); return; } @@ -10395,6 +10339,68 @@ class fancy_serializer } } + private: + void dump_string(string_t const& str, bool ensure_ascii) + { + o->write_character('\"'); + if (style.strings_maximum_length == 0) + { + prim_serializer.dump_escaped(*o, str, ensure_ascii); + } + else + { + std::stringstream ss; + nlohmann::detail::output_adapter o_string(ss); + nlohmann::detail::output_adapter_t oo_string = o_string; + prim_serializer.dump_escaped(*oo_string, str, ensure_ascii); + + std::string full_str = ss.str(); + if (full_str.size() <= style.strings_maximum_length) + { + o->write_characters(full_str.c_str(), full_str.size()); + } + else + { + const unsigned start_len = [](unsigned int maxl) + { + if (maxl <= 3) + { + // There is only room for the ellipsis, + // no characters from the string + return 0u; + } + else if (maxl <= 5) + { + // With four allowed characters, we add in the + // first from the string. With five, we add in + // the *last* instead, so still just one at + // the start. + return 1u; + } + else + { + // We subtract three for the ellipsis + // and one for the last character. + return maxl - 4; + } + }(style.strings_maximum_length); + + const unsigned end_len = + style.strings_maximum_length >= 5 ? 1 : 0; + + const unsigned ellipsis_length = + style.strings_maximum_length >= 3 + ? 3 + : style.strings_maximum_length; + + o->write_characters(full_str.c_str(), start_len); + o->write_characters("...", ellipsis_length); + o->write_characters(full_str.c_str() + str.size() - end_len, end_len); + } + } + o->write_character('\"'); + } + private: /// the output of the fancy_serializer output_adapter_t o = nullptr; From 0500d41fbb35f32196fc6819fcdc2169ba0ab824 Mon Sep 17 00:00:00 2001 From: Evan Driscoll Date: Sat, 2 Jun 2018 20:18:55 -0500 Subject: [PATCH 11/41] TODO note --- test/src/unit-fancy-serialization.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/src/unit-fancy-serialization.cpp b/test/src/unit-fancy-serialization.cpp index 3a8e79202..71eab34ec 100644 --- a/test/src/unit-fancy-serialization.cpp +++ b/test/src/unit-fancy-serialization.cpp @@ -143,6 +143,9 @@ TEST_CASE("serialization") CHECK(str == "\"The quick brown fox jumps over the lazy brown dog\""); } + + // TODO: Handle escape sequences. Figure out what we want the + // behavior to be, first. :-) } SECTION("given width") From f180227b6d4dc8466a2e96c0cf29f52a9ae80aea Mon Sep 17 00:00:00 2001 From: Evan Driscoll Date: Sat, 2 Jun 2018 20:30:12 -0500 Subject: [PATCH 12/41] Fancy serializer: Split object and array handling into functions --- .../detail/output/fancy_serializer.hpp | 252 +++++++++--------- 1 file changed, 130 insertions(+), 122 deletions(-) diff --git a/include/nlohmann/detail/output/fancy_serializer.hpp b/include/nlohmann/detail/output/fancy_serializer.hpp index e26e119a5..46a88a1dc 100644 --- a/include/nlohmann/detail/output/fancy_serializer.hpp +++ b/include/nlohmann/detail/output/fancy_serializer.hpp @@ -88,133 +88,13 @@ class fancy_serializer { case value_t::object: { - if (val.m_value.object->empty()) - { - o->write_characters("{}", 2); - return; - } - - if (style.indent_step > 0) - { - o->write_characters("{\n", 2); - - // variable to hold indentation for recursive calls - const auto new_indent = current_indent + style.indent_step; - if (JSON_UNLIKELY(indent_string.size() < new_indent)) - { - indent_string.resize(indent_string.size() * 2, ' '); - } - - // first n-1 elements - auto i = val.m_value.object->cbegin(); - for (std::size_t cnt = 0; cnt < val.m_value.object->size() - 1; ++cnt, ++i) - { - o->write_characters(indent_string.c_str(), new_indent); - o->write_character('\"'); - prim_serializer.dump_escaped(*o, i->first, ensure_ascii); - o->write_characters("\": ", 3); - dump(i->second, ensure_ascii, new_indent); - o->write_characters(",\n", 2); - } - - // last element - assert(i != val.m_value.object->cend()); - assert(std::next(i) == val.m_value.object->cend()); - o->write_characters(indent_string.c_str(), new_indent); - o->write_character('\"'); - prim_serializer.dump_escaped(*o, i->first, ensure_ascii); - o->write_characters("\": ", 3); - dump(i->second, ensure_ascii, new_indent); - - o->write_character('\n'); - o->write_characters(indent_string.c_str(), current_indent); - o->write_character('}'); - } - else - { - o->write_character('{'); - - // first n-1 elements - auto i = val.m_value.object->cbegin(); - for (std::size_t cnt = 0; cnt < val.m_value.object->size() - 1; ++cnt, ++i) - { - o->write_character('\"'); - prim_serializer.dump_escaped(*o, i->first, ensure_ascii); - o->write_characters("\":", 2); - dump(i->second, ensure_ascii, current_indent); - o->write_character(','); - } - - // last element - assert(i != val.m_value.object->cend()); - assert(std::next(i) == val.m_value.object->cend()); - o->write_character('\"'); - prim_serializer.dump_escaped(*o, i->first, ensure_ascii); - o->write_characters("\":", 2); - dump(i->second, ensure_ascii, current_indent); - - o->write_character('}'); - } - + dump_object(val, ensure_ascii, current_indent); return; } case value_t::array: { - if (val.m_value.array->empty()) - { - o->write_characters("[]", 2); - return; - } - - if (style.indent_step > 0) - { - o->write_characters("[\n", 2); - - // variable to hold indentation for recursive calls - const auto new_indent = current_indent + style.indent_step; - if (JSON_UNLIKELY(indent_string.size() < new_indent)) - { - indent_string.resize(indent_string.size() * 2, ' '); - } - - // first n-1 elements - for (auto i = val.m_value.array->cbegin(); - i != val.m_value.array->cend() - 1; ++i) - { - o->write_characters(indent_string.c_str(), new_indent); - dump(*i, ensure_ascii, new_indent); - o->write_characters(",\n", 2); - } - - // last element - assert(not val.m_value.array->empty()); - o->write_characters(indent_string.c_str(), new_indent); - dump(val.m_value.array->back(), ensure_ascii, new_indent); - - o->write_character('\n'); - o->write_characters(indent_string.c_str(), current_indent); - o->write_character(']'); - } - else - { - o->write_character('['); - - // first n-1 elements - for (auto i = val.m_value.array->cbegin(); - i != val.m_value.array->cend() - 1; ++i) - { - dump(*i, ensure_ascii, current_indent); - o->write_character(','); - } - - // last element - assert(not val.m_value.array->empty()); - dump(val.m_value.array->back(), ensure_ascii, current_indent); - - o->write_character(']'); - } - + dump_array(val, ensure_ascii, current_indent); return; } @@ -270,6 +150,134 @@ class fancy_serializer } private: + void dump_object(const BasicJsonType & val, bool ensure_ascii, unsigned int current_indent) + { + if (val.m_value.object->empty()) + { + o->write_characters("{}", 2); + return; + } + + if (style.indent_step > 0) + { + o->write_characters("{\n", 2); + + // variable to hold indentation for recursive calls + const auto new_indent = current_indent + style.indent_step; + if (JSON_UNLIKELY(indent_string.size() < new_indent)) + { + indent_string.resize(indent_string.size() * 2, ' '); + } + + // first n-1 elements + auto i = val.m_value.object->cbegin(); + for (std::size_t cnt = 0; cnt < val.m_value.object->size() - 1; ++cnt, ++i) + { + o->write_characters(indent_string.c_str(), new_indent); + o->write_character('\"'); + prim_serializer.dump_escaped(*o, i->first, ensure_ascii); + o->write_characters("\": ", 3); + dump(i->second, ensure_ascii, new_indent); + o->write_characters(",\n", 2); + } + + // last element + assert(i != val.m_value.object->cend()); + assert(std::next(i) == val.m_value.object->cend()); + o->write_characters(indent_string.c_str(), new_indent); + o->write_character('\"'); + prim_serializer.dump_escaped(*o, i->first, ensure_ascii); + o->write_characters("\": ", 3); + dump(i->second, ensure_ascii, new_indent); + + o->write_character('\n'); + o->write_characters(indent_string.c_str(), current_indent); + o->write_character('}'); + } + else + { + o->write_character('{'); + + // first n-1 elements + auto i = val.m_value.object->cbegin(); + for (std::size_t cnt = 0; cnt < val.m_value.object->size() - 1; ++cnt, ++i) + { + o->write_character('\"'); + prim_serializer.dump_escaped(*o, i->first, ensure_ascii); + o->write_characters("\":", 2); + dump(i->second, ensure_ascii, current_indent); + o->write_character(','); + } + + // last element + assert(i != val.m_value.object->cend()); + assert(std::next(i) == val.m_value.object->cend()); + o->write_character('\"'); + prim_serializer.dump_escaped(*o, i->first, ensure_ascii); + o->write_characters("\":", 2); + dump(i->second, ensure_ascii, current_indent); + + o->write_character('}'); + } + } + + void dump_array(const BasicJsonType & val, bool ensure_ascii, unsigned int current_indent) + { + if (val.m_value.array->empty()) + { + o->write_characters("[]", 2); + return; + } + + if (style.indent_step > 0) + { + o->write_characters("[\n", 2); + + // variable to hold indentation for recursive calls + const auto new_indent = current_indent + style.indent_step; + if (JSON_UNLIKELY(indent_string.size() < new_indent)) + { + indent_string.resize(indent_string.size() * 2, ' '); + } + + // first n-1 elements + for (auto i = val.m_value.array->cbegin(); + i != val.m_value.array->cend() - 1; ++i) + { + o->write_characters(indent_string.c_str(), new_indent); + dump(*i, ensure_ascii, new_indent); + o->write_characters(",\n", 2); + } + + // last element + assert(not val.m_value.array->empty()); + o->write_characters(indent_string.c_str(), new_indent); + dump(val.m_value.array->back(), ensure_ascii, new_indent); + + o->write_character('\n'); + o->write_characters(indent_string.c_str(), current_indent); + o->write_character(']'); + } + else + { + o->write_character('['); + + // first n-1 elements + for (auto i = val.m_value.array->cbegin(); + i != val.m_value.array->cend() - 1; ++i) + { + dump(*i, ensure_ascii, current_indent); + o->write_character(','); + } + + // last element + assert(not val.m_value.array->empty()); + dump(val.m_value.array->back(), ensure_ascii, current_indent); + + o->write_character(']'); + } + } + void dump_string(string_t const& str, bool ensure_ascii) { o->write_character('\"'); From dafee1343c5e72981e1348fa5bf93db8defd9dda Mon Sep 17 00:00:00 2001 From: Evan Driscoll Date: Sat, 2 Jun 2018 20:50:12 -0500 Subject: [PATCH 13/41] Refactor: dump tracks recursive depth, not indent size This makes the depth independent of the indent_step and independent of whether it's indenting at all --- .../detail/output/fancy_serializer.hpp | 45 +-- single_include/nlohmann/json.hpp | 259 +++++++++--------- 2 files changed, 157 insertions(+), 147 deletions(-) diff --git a/include/nlohmann/detail/output/fancy_serializer.hpp b/include/nlohmann/detail/output/fancy_serializer.hpp index 46a88a1dc..b01cfab51 100644 --- a/include/nlohmann/detail/output/fancy_serializer.hpp +++ b/include/nlohmann/detail/output/fancy_serializer.hpp @@ -77,24 +77,23 @@ class fancy_serializer @param[in] val value to serialize @param[in] pretty_print whether the output shall be pretty-printed - @param[in] indent_step the indent level - @param[in] current_indent the current indent level (only used internally) + @param[in] depth the current recursive depth */ void dump(const BasicJsonType& val, const bool ensure_ascii, - const unsigned int current_indent = 0) + const unsigned int depth = 0) { switch (val.m_type) { case value_t::object: { - dump_object(val, ensure_ascii, current_indent); + dump_object(val, ensure_ascii, depth); return; } case value_t::array: { - dump_array(val, ensure_ascii, current_indent); + dump_array(val, ensure_ascii, depth); return; } @@ -150,7 +149,7 @@ class fancy_serializer } private: - void dump_object(const BasicJsonType & val, bool ensure_ascii, unsigned int current_indent) + void dump_object(const BasicJsonType& val, bool ensure_ascii, unsigned int depth) { if (val.m_value.object->empty()) { @@ -163,7 +162,8 @@ class fancy_serializer o->write_characters("{\n", 2); // variable to hold indentation for recursive calls - const auto new_indent = current_indent + style.indent_step; + const auto old_indent = depth * style.indent_step; + const auto new_indent = (depth + 1) * style.indent_step; if (JSON_UNLIKELY(indent_string.size() < new_indent)) { indent_string.resize(indent_string.size() * 2, ' '); @@ -177,7 +177,7 @@ class fancy_serializer o->write_character('\"'); prim_serializer.dump_escaped(*o, i->first, ensure_ascii); o->write_characters("\": ", 3); - dump(i->second, ensure_ascii, new_indent); + dump(i->second, ensure_ascii, depth + 1); o->write_characters(",\n", 2); } @@ -188,10 +188,10 @@ class fancy_serializer o->write_character('\"'); prim_serializer.dump_escaped(*o, i->first, ensure_ascii); o->write_characters("\": ", 3); - dump(i->second, ensure_ascii, new_indent); + dump(i->second, ensure_ascii, depth + 1); o->write_character('\n'); - o->write_characters(indent_string.c_str(), current_indent); + o->write_characters(indent_string.c_str(), old_indent); o->write_character('}'); } else @@ -205,7 +205,7 @@ class fancy_serializer o->write_character('\"'); prim_serializer.dump_escaped(*o, i->first, ensure_ascii); o->write_characters("\":", 2); - dump(i->second, ensure_ascii, current_indent); + dump(i->second, ensure_ascii, depth + 1); o->write_character(','); } @@ -215,13 +215,13 @@ class fancy_serializer o->write_character('\"'); prim_serializer.dump_escaped(*o, i->first, ensure_ascii); o->write_characters("\":", 2); - dump(i->second, ensure_ascii, current_indent); + dump(i->second, ensure_ascii, depth + 1); o->write_character('}'); } } - void dump_array(const BasicJsonType & val, bool ensure_ascii, unsigned int current_indent) + void dump_array(const BasicJsonType& val, bool ensure_ascii, unsigned int depth) { if (val.m_value.array->empty()) { @@ -234,7 +234,8 @@ class fancy_serializer o->write_characters("[\n", 2); // variable to hold indentation for recursive calls - const auto new_indent = current_indent + style.indent_step; + const auto old_indent = depth * style.indent_step; + const auto new_indent = (depth + 1) * style.indent_step; if (JSON_UNLIKELY(indent_string.size() < new_indent)) { indent_string.resize(indent_string.size() * 2, ' '); @@ -242,20 +243,20 @@ class fancy_serializer // first n-1 elements for (auto i = val.m_value.array->cbegin(); - i != val.m_value.array->cend() - 1; ++i) + i != val.m_value.array->cend() - 1; ++i) { o->write_characters(indent_string.c_str(), new_indent); - dump(*i, ensure_ascii, new_indent); + dump(*i, ensure_ascii, depth + 1); o->write_characters(",\n", 2); } // last element assert(not val.m_value.array->empty()); o->write_characters(indent_string.c_str(), new_indent); - dump(val.m_value.array->back(), ensure_ascii, new_indent); + dump(val.m_value.array->back(), ensure_ascii, depth + 1); o->write_character('\n'); - o->write_characters(indent_string.c_str(), current_indent); + o->write_characters(indent_string.c_str(), old_indent); o->write_character(']'); } else @@ -264,20 +265,20 @@ class fancy_serializer // first n-1 elements for (auto i = val.m_value.array->cbegin(); - i != val.m_value.array->cend() - 1; ++i) + i != val.m_value.array->cend() - 1; ++i) { - dump(*i, ensure_ascii, current_indent); + dump(*i, ensure_ascii, depth + 1); o->write_character(','); } // last element assert(not val.m_value.array->empty()); - dump(val.m_value.array->back(), ensure_ascii, current_indent); + dump(val.m_value.array->back(), ensure_ascii, depth + 1); o->write_character(']'); } } - + void dump_string(string_t const& str, bool ensure_ascii) { o->write_character('\"'); diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index e693cfc12..82589b666 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -10147,144 +10147,23 @@ class fancy_serializer @param[in] val value to serialize @param[in] pretty_print whether the output shall be pretty-printed - @param[in] indent_step the indent level - @param[in] current_indent the current indent level (only used internally) + @param[in] depth the current recursive depth */ void dump(const BasicJsonType& val, const bool ensure_ascii, - const unsigned int current_indent = 0) + const unsigned int depth = 0) { switch (val.m_type) { case value_t::object: { - if (val.m_value.object->empty()) - { - o->write_characters("{}", 2); - return; - } - - if (style.indent_step > 0) - { - o->write_characters("{\n", 2); - - // variable to hold indentation for recursive calls - const auto new_indent = current_indent + style.indent_step; - if (JSON_UNLIKELY(indent_string.size() < new_indent)) - { - indent_string.resize(indent_string.size() * 2, ' '); - } - - // first n-1 elements - auto i = val.m_value.object->cbegin(); - for (std::size_t cnt = 0; cnt < val.m_value.object->size() - 1; ++cnt, ++i) - { - o->write_characters(indent_string.c_str(), new_indent); - o->write_character('\"'); - prim_serializer.dump_escaped(*o, i->first, ensure_ascii); - o->write_characters("\": ", 3); - dump(i->second, ensure_ascii, new_indent); - o->write_characters(",\n", 2); - } - - // last element - assert(i != val.m_value.object->cend()); - assert(std::next(i) == val.m_value.object->cend()); - o->write_characters(indent_string.c_str(), new_indent); - o->write_character('\"'); - prim_serializer.dump_escaped(*o, i->first, ensure_ascii); - o->write_characters("\": ", 3); - dump(i->second, ensure_ascii, new_indent); - - o->write_character('\n'); - o->write_characters(indent_string.c_str(), current_indent); - o->write_character('}'); - } - else - { - o->write_character('{'); - - // first n-1 elements - auto i = val.m_value.object->cbegin(); - for (std::size_t cnt = 0; cnt < val.m_value.object->size() - 1; ++cnt, ++i) - { - o->write_character('\"'); - prim_serializer.dump_escaped(*o, i->first, ensure_ascii); - o->write_characters("\":", 2); - dump(i->second, ensure_ascii, current_indent); - o->write_character(','); - } - - // last element - assert(i != val.m_value.object->cend()); - assert(std::next(i) == val.m_value.object->cend()); - o->write_character('\"'); - prim_serializer.dump_escaped(*o, i->first, ensure_ascii); - o->write_characters("\":", 2); - dump(i->second, ensure_ascii, current_indent); - - o->write_character('}'); - } - + dump_object(val, ensure_ascii, depth); return; } case value_t::array: { - if (val.m_value.array->empty()) - { - o->write_characters("[]", 2); - return; - } - - if (style.indent_step > 0) - { - o->write_characters("[\n", 2); - - // variable to hold indentation for recursive calls - const auto new_indent = current_indent + style.indent_step; - if (JSON_UNLIKELY(indent_string.size() < new_indent)) - { - indent_string.resize(indent_string.size() * 2, ' '); - } - - // first n-1 elements - for (auto i = val.m_value.array->cbegin(); - i != val.m_value.array->cend() - 1; ++i) - { - o->write_characters(indent_string.c_str(), new_indent); - dump(*i, ensure_ascii, new_indent); - o->write_characters(",\n", 2); - } - - // last element - assert(not val.m_value.array->empty()); - o->write_characters(indent_string.c_str(), new_indent); - dump(val.m_value.array->back(), ensure_ascii, new_indent); - - o->write_character('\n'); - o->write_characters(indent_string.c_str(), current_indent); - o->write_character(']'); - } - else - { - o->write_character('['); - - // first n-1 elements - for (auto i = val.m_value.array->cbegin(); - i != val.m_value.array->cend() - 1; ++i) - { - dump(*i, ensure_ascii, current_indent); - o->write_character(','); - } - - // last element - assert(not val.m_value.array->empty()); - dump(val.m_value.array->back(), ensure_ascii, current_indent); - - o->write_character(']'); - } - + dump_array(val, ensure_ascii, depth); return; } @@ -10340,6 +10219,136 @@ class fancy_serializer } private: + void dump_object(const BasicJsonType& val, bool ensure_ascii, unsigned int depth) + { + if (val.m_value.object->empty()) + { + o->write_characters("{}", 2); + return; + } + + if (style.indent_step > 0) + { + o->write_characters("{\n", 2); + + // variable to hold indentation for recursive calls + const auto old_indent = depth * style.indent_step; + const auto new_indent = (depth + 1) * style.indent_step; + if (JSON_UNLIKELY(indent_string.size() < new_indent)) + { + indent_string.resize(indent_string.size() * 2, ' '); + } + + // first n-1 elements + auto i = val.m_value.object->cbegin(); + for (std::size_t cnt = 0; cnt < val.m_value.object->size() - 1; ++cnt, ++i) + { + o->write_characters(indent_string.c_str(), new_indent); + o->write_character('\"'); + prim_serializer.dump_escaped(*o, i->first, ensure_ascii); + o->write_characters("\": ", 3); + dump(i->second, ensure_ascii, depth + 1); + o->write_characters(",\n", 2); + } + + // last element + assert(i != val.m_value.object->cend()); + assert(std::next(i) == val.m_value.object->cend()); + o->write_characters(indent_string.c_str(), new_indent); + o->write_character('\"'); + prim_serializer.dump_escaped(*o, i->first, ensure_ascii); + o->write_characters("\": ", 3); + dump(i->second, ensure_ascii, depth + 1); + + o->write_character('\n'); + o->write_characters(indent_string.c_str(), old_indent); + o->write_character('}'); + } + else + { + o->write_character('{'); + + // first n-1 elements + auto i = val.m_value.object->cbegin(); + for (std::size_t cnt = 0; cnt < val.m_value.object->size() - 1; ++cnt, ++i) + { + o->write_character('\"'); + prim_serializer.dump_escaped(*o, i->first, ensure_ascii); + o->write_characters("\":", 2); + dump(i->second, ensure_ascii, depth + 1); + o->write_character(','); + } + + // last element + assert(i != val.m_value.object->cend()); + assert(std::next(i) == val.m_value.object->cend()); + o->write_character('\"'); + prim_serializer.dump_escaped(*o, i->first, ensure_ascii); + o->write_characters("\":", 2); + dump(i->second, ensure_ascii, depth + 1); + + o->write_character('}'); + } + } + + void dump_array(const BasicJsonType& val, bool ensure_ascii, unsigned int depth) + { + if (val.m_value.array->empty()) + { + o->write_characters("[]", 2); + return; + } + + if (style.indent_step > 0) + { + o->write_characters("[\n", 2); + + // variable to hold indentation for recursive calls + const auto old_indent = depth * style.indent_step; + const auto new_indent = (depth + 1) * style.indent_step; + if (JSON_UNLIKELY(indent_string.size() < new_indent)) + { + indent_string.resize(indent_string.size() * 2, ' '); + } + + // first n-1 elements + for (auto i = val.m_value.array->cbegin(); + i != val.m_value.array->cend() - 1; ++i) + { + o->write_characters(indent_string.c_str(), new_indent); + dump(*i, ensure_ascii, depth + 1); + o->write_characters(",\n", 2); + } + + // last element + assert(not val.m_value.array->empty()); + o->write_characters(indent_string.c_str(), new_indent); + dump(val.m_value.array->back(), ensure_ascii, depth + 1); + + o->write_character('\n'); + o->write_characters(indent_string.c_str(), old_indent); + o->write_character(']'); + } + else + { + o->write_character('['); + + // first n-1 elements + for (auto i = val.m_value.array->cbegin(); + i != val.m_value.array->cend() - 1; ++i) + { + dump(*i, ensure_ascii, depth + 1); + o->write_character(','); + } + + // last element + assert(not val.m_value.array->empty()); + dump(val.m_value.array->back(), ensure_ascii, depth + 1); + + o->write_character(']'); + } + } + void dump_string(string_t const& str, bool ensure_ascii) { o->write_character('\"'); From cc4206a718c7aea53c314182e45952234aaa14be Mon Sep 17 00:00:00 2001 From: Evan Driscoll Date: Sat, 2 Jun 2018 20:51:53 -0500 Subject: [PATCH 14/41] West const to match existing code --- include/nlohmann/detail/output/fancy_serializer.hpp | 2 +- single_include/nlohmann/json.hpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/include/nlohmann/detail/output/fancy_serializer.hpp b/include/nlohmann/detail/output/fancy_serializer.hpp index b01cfab51..190bcbc63 100644 --- a/include/nlohmann/detail/output/fancy_serializer.hpp +++ b/include/nlohmann/detail/output/fancy_serializer.hpp @@ -279,7 +279,7 @@ class fancy_serializer } } - void dump_string(string_t const& str, bool ensure_ascii) + void dump_string(const string_t& str, bool ensure_ascii) { o->write_character('\"'); if (style.strings_maximum_length == 0) diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 82589b666..8782872ea 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -10349,7 +10349,7 @@ class fancy_serializer } } - void dump_string(string_t const& str, bool ensure_ascii) + void dump_string(const string_t& str, bool ensure_ascii) { o->write_character('\"'); if (style.strings_maximum_length == 0) From 9cdf54b8867f88feac4cc4035d0f9d64aadc6781 Mon Sep 17 00:00:00 2001 From: Evan Driscoll Date: Sat, 2 Jun 2018 21:01:22 -0500 Subject: [PATCH 15/41] Refactor: dump_array doesn't split cases for indent --- .../detail/output/fancy_serializer.hpp | 62 +++++++------------ single_include/nlohmann/json.hpp | 62 +++++++------------ 2 files changed, 42 insertions(+), 82 deletions(-) diff --git a/include/nlohmann/detail/output/fancy_serializer.hpp b/include/nlohmann/detail/output/fancy_serializer.hpp index 190bcbc63..30b272e02 100644 --- a/include/nlohmann/detail/output/fancy_serializer.hpp +++ b/include/nlohmann/detail/output/fancy_serializer.hpp @@ -229,54 +229,34 @@ class fancy_serializer return; } - if (style.indent_step > 0) + // variable to hold indentation for recursive calls + const auto old_indent = depth * style.indent_step; + const auto new_indent = (depth + 1) * style.indent_step; + if (JSON_UNLIKELY(indent_string.size() < new_indent)) { - o->write_characters("[\n", 2); + indent_string.resize(indent_string.size() * 2, ' '); + } + const int newline_len = (style.indent_step > 0); - // variable to hold indentation for recursive calls - const auto old_indent = depth * style.indent_step; - const auto new_indent = (depth + 1) * style.indent_step; - if (JSON_UNLIKELY(indent_string.size() < new_indent)) - { - indent_string.resize(indent_string.size() * 2, ' '); - } + o->write_characters("[\n", 1 + newline_len); - // first n-1 elements - for (auto i = val.m_value.array->cbegin(); - i != val.m_value.array->cend() - 1; ++i) - { - o->write_characters(indent_string.c_str(), new_indent); - dump(*i, ensure_ascii, depth + 1); - o->write_characters(",\n", 2); - } - - // last element - assert(not val.m_value.array->empty()); + // first n-1 elements + for (auto i = val.m_value.array->cbegin(); + i != val.m_value.array->cend() - 1; ++i) + { o->write_characters(indent_string.c_str(), new_indent); - dump(val.m_value.array->back(), ensure_ascii, depth + 1); - - o->write_character('\n'); - o->write_characters(indent_string.c_str(), old_indent); - o->write_character(']'); + dump(*i, ensure_ascii, depth + 1); + o->write_characters(",\n", 1 + newline_len); } - else - { - o->write_character('['); - // first n-1 elements - for (auto i = val.m_value.array->cbegin(); - i != val.m_value.array->cend() - 1; ++i) - { - dump(*i, ensure_ascii, depth + 1); - o->write_character(','); - } + // last element + assert(not val.m_value.array->empty()); + o->write_characters(indent_string.c_str(), new_indent); + dump(val.m_value.array->back(), ensure_ascii, depth + 1); - // last element - assert(not val.m_value.array->empty()); - dump(val.m_value.array->back(), ensure_ascii, depth + 1); - - o->write_character(']'); - } + o->write_characters("\n", newline_len); + o->write_characters(indent_string.c_str(), old_indent); + o->write_character(']'); } void dump_string(const string_t& str, bool ensure_ascii) diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 8782872ea..45601d51d 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -10299,54 +10299,34 @@ class fancy_serializer return; } - if (style.indent_step > 0) + // variable to hold indentation for recursive calls + const auto old_indent = depth * style.indent_step; + const auto new_indent = (depth + 1) * style.indent_step; + if (JSON_UNLIKELY(indent_string.size() < new_indent)) { - o->write_characters("[\n", 2); + indent_string.resize(indent_string.size() * 2, ' '); + } + const int newline_len = (style.indent_step > 0); - // variable to hold indentation for recursive calls - const auto old_indent = depth * style.indent_step; - const auto new_indent = (depth + 1) * style.indent_step; - if (JSON_UNLIKELY(indent_string.size() < new_indent)) - { - indent_string.resize(indent_string.size() * 2, ' '); - } + o->write_characters("[\n", 1 + newline_len); - // first n-1 elements - for (auto i = val.m_value.array->cbegin(); - i != val.m_value.array->cend() - 1; ++i) - { - o->write_characters(indent_string.c_str(), new_indent); - dump(*i, ensure_ascii, depth + 1); - o->write_characters(",\n", 2); - } - - // last element - assert(not val.m_value.array->empty()); + // first n-1 elements + for (auto i = val.m_value.array->cbegin(); + i != val.m_value.array->cend() - 1; ++i) + { o->write_characters(indent_string.c_str(), new_indent); - dump(val.m_value.array->back(), ensure_ascii, depth + 1); - - o->write_character('\n'); - o->write_characters(indent_string.c_str(), old_indent); - o->write_character(']'); + dump(*i, ensure_ascii, depth + 1); + o->write_characters(",\n", 1 + newline_len); } - else - { - o->write_character('['); - // first n-1 elements - for (auto i = val.m_value.array->cbegin(); - i != val.m_value.array->cend() - 1; ++i) - { - dump(*i, ensure_ascii, depth + 1); - o->write_character(','); - } + // last element + assert(not val.m_value.array->empty()); + o->write_characters(indent_string.c_str(), new_indent); + dump(val.m_value.array->back(), ensure_ascii, depth + 1); - // last element - assert(not val.m_value.array->empty()); - dump(val.m_value.array->back(), ensure_ascii, depth + 1); - - o->write_character(']'); - } + o->write_characters("\n", newline_len); + o->write_characters(indent_string.c_str(), old_indent); + o->write_character(']'); } void dump_string(const string_t& str, bool ensure_ascii) From a983e47619d5b181792e55009913a07b2c2b0ddc Mon Sep 17 00:00:00 2001 From: Evan Driscoll Date: Sat, 2 Jun 2018 21:08:53 -0500 Subject: [PATCH 16/41] Refactor: dump_object no longer splits cases by indent --- .../detail/output/fancy_serializer.hpp | 77 ++++++------------- single_include/nlohmann/json.hpp | 77 ++++++------------- 2 files changed, 50 insertions(+), 104 deletions(-) diff --git a/include/nlohmann/detail/output/fancy_serializer.hpp b/include/nlohmann/detail/output/fancy_serializer.hpp index 30b272e02..1cddcca3c 100644 --- a/include/nlohmann/detail/output/fancy_serializer.hpp +++ b/include/nlohmann/detail/output/fancy_serializer.hpp @@ -157,68 +157,41 @@ class fancy_serializer return; } - if (style.indent_step > 0) + // variable to hold indentation for recursive calls + const auto old_indent = depth * style.indent_step; + const auto new_indent = (depth + 1) * style.indent_step; + if (JSON_UNLIKELY(indent_string.size() < new_indent)) { - o->write_characters("{\n", 2); + indent_string.resize(indent_string.size() * 2, ' '); + } + const int newline_len = (style.indent_step > 0); - // variable to hold indentation for recursive calls - const auto old_indent = depth * style.indent_step; - const auto new_indent = (depth + 1) * style.indent_step; - if (JSON_UNLIKELY(indent_string.size() < new_indent)) - { - indent_string.resize(indent_string.size() * 2, ' '); - } + o->write_characters("{\n", 1 + newline_len); - // first n-1 elements - auto i = val.m_value.object->cbegin(); - for (std::size_t cnt = 0; cnt < val.m_value.object->size() - 1; ++cnt, ++i) - { - o->write_characters(indent_string.c_str(), new_indent); - o->write_character('\"'); - prim_serializer.dump_escaped(*o, i->first, ensure_ascii); - o->write_characters("\": ", 3); - dump(i->second, ensure_ascii, depth + 1); - o->write_characters(",\n", 2); - } - - // last element - assert(i != val.m_value.object->cend()); - assert(std::next(i) == val.m_value.object->cend()); + // first n-1 elements + auto i = val.m_value.object->cbegin(); + for (std::size_t cnt = 0; cnt < val.m_value.object->size() - 1; ++cnt, ++i) + { o->write_characters(indent_string.c_str(), new_indent); o->write_character('\"'); prim_serializer.dump_escaped(*o, i->first, ensure_ascii); - o->write_characters("\": ", 3); + o->write_characters("\": ", 2 + newline_len); dump(i->second, ensure_ascii, depth + 1); - - o->write_character('\n'); - o->write_characters(indent_string.c_str(), old_indent); - o->write_character('}'); + o->write_characters(",\n", 1 + newline_len); } - else - { - o->write_character('{'); - // first n-1 elements - auto i = val.m_value.object->cbegin(); - for (std::size_t cnt = 0; cnt < val.m_value.object->size() - 1; ++cnt, ++i) - { - o->write_character('\"'); - prim_serializer.dump_escaped(*o, i->first, ensure_ascii); - o->write_characters("\":", 2); - dump(i->second, ensure_ascii, depth + 1); - o->write_character(','); - } + // last element + assert(i != val.m_value.object->cend()); + assert(std::next(i) == val.m_value.object->cend()); + o->write_characters(indent_string.c_str(), new_indent); + o->write_character('\"'); + prim_serializer.dump_escaped(*o, i->first, ensure_ascii); + o->write_characters("\": ", 2 + newline_len); + dump(i->second, ensure_ascii, depth + 1); - // last element - assert(i != val.m_value.object->cend()); - assert(std::next(i) == val.m_value.object->cend()); - o->write_character('\"'); - prim_serializer.dump_escaped(*o, i->first, ensure_ascii); - o->write_characters("\":", 2); - dump(i->second, ensure_ascii, depth + 1); - - o->write_character('}'); - } + o->write_characters("\n", newline_len); + o->write_characters(indent_string.c_str(), old_indent); + o->write_character('}'); } void dump_array(const BasicJsonType& val, bool ensure_ascii, unsigned int depth) diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 45601d51d..532e8fa19 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -10227,68 +10227,41 @@ class fancy_serializer return; } - if (style.indent_step > 0) + // variable to hold indentation for recursive calls + const auto old_indent = depth * style.indent_step; + const auto new_indent = (depth + 1) * style.indent_step; + if (JSON_UNLIKELY(indent_string.size() < new_indent)) { - o->write_characters("{\n", 2); + indent_string.resize(indent_string.size() * 2, ' '); + } + const int newline_len = (style.indent_step > 0); - // variable to hold indentation for recursive calls - const auto old_indent = depth * style.indent_step; - const auto new_indent = (depth + 1) * style.indent_step; - if (JSON_UNLIKELY(indent_string.size() < new_indent)) - { - indent_string.resize(indent_string.size() * 2, ' '); - } + o->write_characters("{\n", 1 + newline_len); - // first n-1 elements - auto i = val.m_value.object->cbegin(); - for (std::size_t cnt = 0; cnt < val.m_value.object->size() - 1; ++cnt, ++i) - { - o->write_characters(indent_string.c_str(), new_indent); - o->write_character('\"'); - prim_serializer.dump_escaped(*o, i->first, ensure_ascii); - o->write_characters("\": ", 3); - dump(i->second, ensure_ascii, depth + 1); - o->write_characters(",\n", 2); - } - - // last element - assert(i != val.m_value.object->cend()); - assert(std::next(i) == val.m_value.object->cend()); + // first n-1 elements + auto i = val.m_value.object->cbegin(); + for (std::size_t cnt = 0; cnt < val.m_value.object->size() - 1; ++cnt, ++i) + { o->write_characters(indent_string.c_str(), new_indent); o->write_character('\"'); prim_serializer.dump_escaped(*o, i->first, ensure_ascii); - o->write_characters("\": ", 3); + o->write_characters("\": ", 2 + newline_len); dump(i->second, ensure_ascii, depth + 1); - - o->write_character('\n'); - o->write_characters(indent_string.c_str(), old_indent); - o->write_character('}'); + o->write_characters(",\n", 1 + newline_len); } - else - { - o->write_character('{'); - // first n-1 elements - auto i = val.m_value.object->cbegin(); - for (std::size_t cnt = 0; cnt < val.m_value.object->size() - 1; ++cnt, ++i) - { - o->write_character('\"'); - prim_serializer.dump_escaped(*o, i->first, ensure_ascii); - o->write_characters("\":", 2); - dump(i->second, ensure_ascii, depth + 1); - o->write_character(','); - } + // last element + assert(i != val.m_value.object->cend()); + assert(std::next(i) == val.m_value.object->cend()); + o->write_characters(indent_string.c_str(), new_indent); + o->write_character('\"'); + prim_serializer.dump_escaped(*o, i->first, ensure_ascii); + o->write_characters("\": ", 2 + newline_len); + dump(i->second, ensure_ascii, depth + 1); - // last element - assert(i != val.m_value.object->cend()); - assert(std::next(i) == val.m_value.object->cend()); - o->write_character('\"'); - prim_serializer.dump_escaped(*o, i->first, ensure_ascii); - o->write_characters("\":", 2); - dump(i->second, ensure_ascii, depth + 1); - - o->write_character('}'); - } + o->write_characters("\n", newline_len); + o->write_characters(indent_string.c_str(), old_indent); + o->write_character('}'); } void dump_array(const BasicJsonType& val, bool ensure_ascii, unsigned int depth) From 1c1c78908424c3ec0515ed957a45e5ecfc363632 Mon Sep 17 00:00:00 2001 From: Evan Driscoll Date: Sat, 2 Jun 2018 21:34:55 -0500 Subject: [PATCH 17/41] Serializer edge case bug fix: very deep indents ignored indent_char --- .../detail/output/fancy_serializer.hpp | 4 +-- include/nlohmann/detail/output/serializer.hpp | 4 +-- single_include/nlohmann/json.hpp | 8 ++--- test/src/unit-fancy-serialization.cpp | 35 +++++++++++++++++++ test/src/unit-serialization.cpp | 31 ++++++++++++++++ 5 files changed, 74 insertions(+), 8 deletions(-) diff --git a/include/nlohmann/detail/output/fancy_serializer.hpp b/include/nlohmann/detail/output/fancy_serializer.hpp index 1cddcca3c..62462b085 100644 --- a/include/nlohmann/detail/output/fancy_serializer.hpp +++ b/include/nlohmann/detail/output/fancy_serializer.hpp @@ -162,7 +162,7 @@ class fancy_serializer const auto new_indent = (depth + 1) * style.indent_step; if (JSON_UNLIKELY(indent_string.size() < new_indent)) { - indent_string.resize(indent_string.size() * 2, ' '); + indent_string.resize(indent_string.size() * 2, style.indent_char); } const int newline_len = (style.indent_step > 0); @@ -207,7 +207,7 @@ class fancy_serializer const auto new_indent = (depth + 1) * style.indent_step; if (JSON_UNLIKELY(indent_string.size() < new_indent)) { - indent_string.resize(indent_string.size() * 2, ' '); + indent_string.resize(indent_string.size() * 2, style.indent_char); } const int newline_len = (style.indent_step > 0); diff --git a/include/nlohmann/detail/output/serializer.hpp b/include/nlohmann/detail/output/serializer.hpp index b4997adac..956df5685 100644 --- a/include/nlohmann/detail/output/serializer.hpp +++ b/include/nlohmann/detail/output/serializer.hpp @@ -93,7 +93,7 @@ class serializer const auto new_indent = current_indent + indent_step; if (JSON_UNLIKELY(indent_string.size() < new_indent)) { - indent_string.resize(indent_string.size() * 2, ' '); + indent_string.resize(indent_string.size() * 2, indent_char); } // first n-1 elements @@ -166,7 +166,7 @@ class serializer const auto new_indent = current_indent + indent_step; if (JSON_UNLIKELY(indent_string.size() < new_indent)) { - indent_string.resize(indent_string.size() * 2, ' '); + indent_string.resize(indent_string.size() * 2, indent_char); } // first n-1 elements diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 532e8fa19..a1d285474 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -9876,7 +9876,7 @@ class serializer const auto new_indent = current_indent + indent_step; if (JSON_UNLIKELY(indent_string.size() < new_indent)) { - indent_string.resize(indent_string.size() * 2, ' '); + indent_string.resize(indent_string.size() * 2, indent_char); } // first n-1 elements @@ -9949,7 +9949,7 @@ class serializer const auto new_indent = current_indent + indent_step; if (JSON_UNLIKELY(indent_string.size() < new_indent)) { - indent_string.resize(indent_string.size() * 2, ' '); + indent_string.resize(indent_string.size() * 2, indent_char); } // first n-1 elements @@ -10232,7 +10232,7 @@ class fancy_serializer const auto new_indent = (depth + 1) * style.indent_step; if (JSON_UNLIKELY(indent_string.size() < new_indent)) { - indent_string.resize(indent_string.size() * 2, ' '); + indent_string.resize(indent_string.size() * 2, style.indent_char); } const int newline_len = (style.indent_step > 0); @@ -10277,7 +10277,7 @@ class fancy_serializer const auto new_indent = (depth + 1) * style.indent_step; if (JSON_UNLIKELY(indent_string.size() < new_indent)) { - indent_string.resize(indent_string.size() * 2, ' '); + indent_string.resize(indent_string.size() * 2, style.indent_char); } const int newline_len = (style.indent_step > 0); diff --git a/test/src/unit-fancy-serialization.cpp b/test/src/unit-fancy-serialization.cpp index 71eab34ec..03c628d77 100644 --- a/test/src/unit-fancy-serialization.cpp +++ b/test/src/unit-fancy-serialization.cpp @@ -187,4 +187,39 @@ TEST_CASE("serialization") "]" ); } + + SECTION("indent_char is honored for deep indents in lists") + { + fancy_serializer_style style; + style.indent_step = 300; + style.indent_char = 'X'; + + auto str = fancy_to_string({1, {1}}, style); + + std::string indent(300, 'X'); + CHECK(str == + "[\n" + + indent + "1,\n" + + indent + "[\n" + + indent + indent + "1\n" + + indent + "]\n" + + "]"); + } + + SECTION("indent_char is honored for deep indents in objects") + { + fancy_serializer_style style; + style.indent_step = 300; + style.indent_char = 'X'; + + auto str = fancy_to_string({{"key", {{"key", 1}}}}, style); + + std::string indent(300, 'X'); + CHECK(str == + "{\n" + + indent + "\"key\": {\n" + + indent + indent + "\"key\": 1\n" + + indent + "}\n" + + "}"); + } } diff --git a/test/src/unit-serialization.cpp b/test/src/unit-serialization.cpp index 3abc7fb19..7918c171b 100644 --- a/test/src/unit-serialization.cpp +++ b/test/src/unit-serialization.cpp @@ -63,6 +63,37 @@ TEST_CASE("serialization") } } + SECTION("indent_char is honored for deep indents in lists") + { + std::stringstream ss; + json j = {1, {1}}; + ss << std::setw(300) << std::setfill('X') << j; + + std::string indent(300, 'X'); + CHECK(ss.str() == + "[\n" + + indent + "1,\n" + + indent + "[\n" + + indent + indent + "1\n" + + indent + "]\n" + + "]"); + } + + SECTION("indent_char is honored for deep indents in objects") + { + std::stringstream ss; + json j = {{"key", {{"key", 1}}}}; + ss << std::setw(300) << std::setfill('X') << j; + + std::string indent(300, 'X'); + CHECK(ss.str() == + "{\n" + + indent + "\"key\": {\n" + + indent + indent + "\"key\": 1\n" + + indent + "}\n" + + "}"); + } + SECTION("operator>>") { SECTION("no given width") From cd0c225a506265475029abd6ed4e7508c2aa8b1d Mon Sep 17 00:00:00 2001 From: Evan Driscoll Date: Sat, 2 Jun 2018 22:10:16 -0500 Subject: [PATCH 18/41] Use raw literals for multi-line expected results Prettyfies the test cases a bit, though there is a utiliity function needed now. --- test/src/unit-fancy-serialization.cpp | 57 +++++++++++++++++++++------ 1 file changed, 46 insertions(+), 11 deletions(-) diff --git a/test/src/unit-fancy-serialization.cpp b/test/src/unit-fancy-serialization.cpp index 03c628d77..3f732cd17 100644 --- a/test/src/unit-fancy-serialization.cpp +++ b/test/src/unit-fancy-serialization.cpp @@ -35,6 +35,41 @@ using nlohmann::json; using nlohmann::fancy_dump; using nlohmann::fancy_serializer_style; +// Chops off the first line (if empty, but if it *isn't* empty you're +// probably using this wrong), measures the leading indent on the +// *next* line, then chops that amount off of all subsequent lines. +std::string dedent(const char* str) +{ + std::stringstream out; + std::stringstream ss(str); + std::string line; + bool first = true; + int indent = -1; + + while (getline(ss, line)) + { + if (first && line.empty()) + { + first = false; + continue; + } + if (indent == -1) + { + indent = line.find_first_not_of(' '); + assert(indent != std::string::npos); + } + out << line.c_str() + indent << "\n"; + } + + std::string ans = out.str(); + if (ans[ans.size() - 1] == '\n' and str[strlen(str) - 1] != '\n') + { + ans.resize(ans.size() - 1); + } + + return ans; +} + std::string fancy_to_string(json j, fancy_serializer_style style = fancy_serializer_style()) { std::stringstream ss; @@ -154,17 +189,17 @@ TEST_CASE("serialization") style.indent_step = 4; auto str = fancy_to_string({"foo", 1, 2, 3, false, {{"one", 1}}}, style); CHECK(str == - "[\n" - " \"foo\",\n" - " 1,\n" - " 2,\n" - " 3,\n" - " false,\n" - " {\n" - " \"one\": 1\n" - " }\n" - "]" - ); + dedent(R"( + [ + "foo", + 1, + 2, + 3, + false, + { + "one": 1 + } + ])")); } SECTION("given fill") From 28e7eecf33b87cf8c732cbd8c67b556609a625f6 Mon Sep 17 00:00:00 2001 From: Evan Driscoll Date: Sat, 2 Jun 2018 22:22:24 -0500 Subject: [PATCH 19/41] Fancy serializer can limit recursion depth Elides objects with ... if that limit is exceeded --- .../detail/output/fancy_serializer.hpp | 12 ++++ single_include/nlohmann/json.hpp | 12 ++++ test/src/unit-fancy-serialization.cpp | 60 +++++++++++++++---- 3 files changed, 72 insertions(+), 12 deletions(-) diff --git a/include/nlohmann/detail/output/fancy_serializer.hpp b/include/nlohmann/detail/output/fancy_serializer.hpp index 62462b085..f8a4014ca 100644 --- a/include/nlohmann/detail/output/fancy_serializer.hpp +++ b/include/nlohmann/detail/output/fancy_serializer.hpp @@ -29,6 +29,8 @@ struct fancy_serializer_style unsigned int indent_step = 0; char indent_char = ' '; + unsigned int depth_limit = std::numeric_limits::max(); + unsigned int strings_maximum_length = 0; }; @@ -156,6 +158,11 @@ class fancy_serializer o->write_characters("{}", 2); return; } + else if (depth >= style.depth_limit) + { + o->write_characters("{...}", 5); + return; + } // variable to hold indentation for recursive calls const auto old_indent = depth * style.indent_step; @@ -201,6 +208,11 @@ class fancy_serializer o->write_characters("[]", 2); return; } + else if (depth >= style.depth_limit) + { + o->write_characters("[...]", 5); + return; + } // variable to hold indentation for recursive calls const auto old_indent = depth * style.indent_step; diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index a1d285474..1e1212037 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -10099,6 +10099,8 @@ struct fancy_serializer_style unsigned int indent_step = 0; char indent_char = ' '; + unsigned int depth_limit = std::numeric_limits::max(); + unsigned int strings_maximum_length = 0; }; @@ -10226,6 +10228,11 @@ class fancy_serializer o->write_characters("{}", 2); return; } + else if (depth >= style.depth_limit) + { + o->write_characters("{...}", 5); + return; + } // variable to hold indentation for recursive calls const auto old_indent = depth * style.indent_step; @@ -10271,6 +10278,11 @@ class fancy_serializer o->write_characters("[]", 2); return; } + else if (depth >= style.depth_limit) + { + o->write_characters("[...]", 5); + return; + } // variable to hold indentation for recursive calls const auto old_indent = depth * style.indent_step; diff --git a/test/src/unit-fancy-serialization.cpp b/test/src/unit-fancy-serialization.cpp index 3f732cd17..c50a1365f 100644 --- a/test/src/unit-fancy-serialization.cpp +++ b/test/src/unit-fancy-serialization.cpp @@ -183,23 +183,59 @@ TEST_CASE("serialization") // behavior to be, first. :-) } + SECTION("maximum depth") + { + SECTION("recursing past the maximum depth with a list elides the subobjects") + { + fancy_serializer_style style; + style.depth_limit = 1; + + auto str_flat = fancy_to_string({1, {1}}, style); + CHECK(str_flat == "[1,[...]]"); + + style.indent_step = 4; + auto str_lines = fancy_to_string({1, {1}}, style); + CHECK(str_lines == dedent(R"( + [ + 1, + [...] + ])")); + } + + SECTION("recursing past the maximum depth with an object elides the subobjects") + { + fancy_serializer_style style; + style.depth_limit = 1; + + auto str_flat = fancy_to_string({1, {{"one", 1}}}, style); + CHECK(str_flat == "[1,{...}]"); + + style.indent_step = 4; + auto str_lines = fancy_to_string({1, {{"one", 1}}}, style); + CHECK(str_lines == dedent(R"( + [ + 1, + {...} + ])")); + } + } + SECTION("given width") { fancy_serializer_style style; style.indent_step = 4; auto str = fancy_to_string({"foo", 1, 2, 3, false, {{"one", 1}}}, style); - CHECK(str == - dedent(R"( - [ - "foo", - 1, - 2, - 3, - false, - { - "one": 1 - } - ])")); + CHECK(str == dedent(R"( + [ + "foo", + 1, + 2, + 3, + false, + { + "one": 1 + } + ])")); } SECTION("given fill") From 7fa44314749a7470168a4c0ea26949e9f33dea8c Mon Sep 17 00:00:00 2001 From: Evan Driscoll Date: Sat, 2 Jun 2018 22:44:28 -0500 Subject: [PATCH 20/41] Refactor and prep: introduce new fancy_serializer_stylizer class This will be able to provide multiple styles depending on context --- .../detail/output/fancy_serializer.hpp | 108 ++++++++++++------ single_include/nlohmann/json.hpp | 108 ++++++++++++------ 2 files changed, 146 insertions(+), 70 deletions(-) diff --git a/include/nlohmann/detail/output/fancy_serializer.hpp b/include/nlohmann/detail/output/fancy_serializer.hpp index f8a4014ca..87fd9578e 100644 --- a/include/nlohmann/detail/output/fancy_serializer.hpp +++ b/include/nlohmann/detail/output/fancy_serializer.hpp @@ -12,6 +12,7 @@ #include // numeric_limits #include // string #include // is_same +#include #include #include @@ -34,6 +35,26 @@ struct fancy_serializer_style unsigned int strings_maximum_length = 0; }; +template +class fancy_serializer_stylizer +{ + public: + fancy_serializer_stylizer(fancy_serializer_style const& ds) + : default_style(ds) + {} + + fancy_serializer_stylizer() = delete; + + public: + const fancy_serializer_style& get_default_style() const + { + return default_style; + } + + private: + fancy_serializer_style default_style; +}; + namespace detail { /////////////////// @@ -43,6 +64,7 @@ namespace detail template class fancy_serializer { + using stylizer_t = fancy_serializer_stylizer; using primitive_serializer_t = primitive_serializer; using string_t = typename BasicJsonType::string_t; using number_float_t = typename BasicJsonType::number_float_t; @@ -57,14 +79,21 @@ class fancy_serializer @param[in] ichar indentation character to use */ fancy_serializer(output_adapter_t s, - const fancy_serializer_style& st) - : o(std::move(s)), indent_string(512, st.indent_char), style(st) + const stylizer_t& st) + : o(std::move(s)), stylizer(st), + indent_string(512, st.get_default_style().indent_char) {} // delete because of pointer members fancy_serializer(const fancy_serializer&) = delete; fancy_serializer& operator=(const fancy_serializer&) = delete; + void dump(const BasicJsonType& val, const bool ensure_ascii) + { + dump(val, ensure_ascii, 0, &stylizer.get_default_style()); + } + + private: /*! @brief internal implementation of the serialization function @@ -83,25 +112,26 @@ class fancy_serializer */ void dump(const BasicJsonType& val, const bool ensure_ascii, - const unsigned int depth = 0) + const unsigned int depth, + const fancy_serializer_style* active_style) { switch (val.m_type) { case value_t::object: { - dump_object(val, ensure_ascii, depth); + dump_object(val, ensure_ascii, depth, active_style); return; } case value_t::array: { - dump_array(val, ensure_ascii, depth); + dump_array(val, ensure_ascii, depth, active_style); return; } case value_t::string: { - dump_string(*val.m_value.string, ensure_ascii); + dump_string(*val.m_value.string, ensure_ascii, active_style); return; } @@ -151,27 +181,30 @@ class fancy_serializer } private: - void dump_object(const BasicJsonType& val, bool ensure_ascii, unsigned int depth) + void dump_object(const BasicJsonType& val, + bool ensure_ascii, + unsigned int depth, + const fancy_serializer_style* active_style) { if (val.m_value.object->empty()) { o->write_characters("{}", 2); return; } - else if (depth >= style.depth_limit) + else if (depth >= active_style->depth_limit) { o->write_characters("{...}", 5); return; } // variable to hold indentation for recursive calls - const auto old_indent = depth * style.indent_step; - const auto new_indent = (depth + 1) * style.indent_step; + const auto old_indent = depth * active_style->indent_step; + const auto new_indent = (depth + 1) * active_style->indent_step; if (JSON_UNLIKELY(indent_string.size() < new_indent)) { - indent_string.resize(indent_string.size() * 2, style.indent_char); + indent_string.resize(indent_string.size() * 2, active_style->indent_char); } - const int newline_len = (style.indent_step > 0); + const int newline_len = (active_style->indent_step > 0); o->write_characters("{\n", 1 + newline_len); @@ -183,7 +216,7 @@ class fancy_serializer o->write_character('\"'); prim_serializer.dump_escaped(*o, i->first, ensure_ascii); o->write_characters("\": ", 2 + newline_len); - dump(i->second, ensure_ascii, depth + 1); + dump(i->second, ensure_ascii, depth + 1, active_style); o->write_characters(",\n", 1 + newline_len); } @@ -194,34 +227,37 @@ class fancy_serializer o->write_character('\"'); prim_serializer.dump_escaped(*o, i->first, ensure_ascii); o->write_characters("\": ", 2 + newline_len); - dump(i->second, ensure_ascii, depth + 1); + dump(i->second, ensure_ascii, depth + 1, active_style); o->write_characters("\n", newline_len); o->write_characters(indent_string.c_str(), old_indent); o->write_character('}'); } - void dump_array(const BasicJsonType& val, bool ensure_ascii, unsigned int depth) + void dump_array(const BasicJsonType& val, + bool ensure_ascii, + unsigned int depth, + const fancy_serializer_style* active_style) { if (val.m_value.array->empty()) { o->write_characters("[]", 2); return; } - else if (depth >= style.depth_limit) + else if (depth >= active_style->depth_limit) { o->write_characters("[...]", 5); return; } // variable to hold indentation for recursive calls - const auto old_indent = depth * style.indent_step; - const auto new_indent = (depth + 1) * style.indent_step; + const auto old_indent = depth * active_style->indent_step; + const auto new_indent = (depth + 1) * active_style->indent_step; if (JSON_UNLIKELY(indent_string.size() < new_indent)) { - indent_string.resize(indent_string.size() * 2, style.indent_char); + indent_string.resize(indent_string.size() * 2, active_style->indent_char); } - const int newline_len = (style.indent_step > 0); + const int newline_len = (active_style->indent_step > 0); o->write_characters("[\n", 1 + newline_len); @@ -230,24 +266,25 @@ class fancy_serializer i != val.m_value.array->cend() - 1; ++i) { o->write_characters(indent_string.c_str(), new_indent); - dump(*i, ensure_ascii, depth + 1); + dump(*i, ensure_ascii, depth + 1, active_style); o->write_characters(",\n", 1 + newline_len); } // last element assert(not val.m_value.array->empty()); o->write_characters(indent_string.c_str(), new_indent); - dump(val.m_value.array->back(), ensure_ascii, depth + 1); + dump(val.m_value.array->back(), ensure_ascii, depth + 1, active_style); o->write_characters("\n", newline_len); o->write_characters(indent_string.c_str(), old_indent); o->write_character(']'); } - void dump_string(const string_t& str, bool ensure_ascii) + void dump_string(const string_t& str, bool ensure_ascii, + const fancy_serializer_style* active_style) { o->write_character('\"'); - if (style.strings_maximum_length == 0) + if (active_style->strings_maximum_length == 0) { prim_serializer.dump_escaped(*o, str, ensure_ascii); } @@ -259,7 +296,7 @@ class fancy_serializer prim_serializer.dump_escaped(*oo_string, str, ensure_ascii); std::string full_str = ss.str(); - if (full_str.size() <= style.strings_maximum_length) + if (full_str.size() <= active_style->strings_maximum_length) { o->write_characters(full_str.c_str(), full_str.size()); } @@ -287,15 +324,15 @@ class fancy_serializer // and one for the last character. return maxl - 4; } - }(style.strings_maximum_length); + }(active_style->strings_maximum_length); const unsigned end_len = - style.strings_maximum_length >= 5 ? 1 : 0; + active_style->strings_maximum_length >= 5 ? 1 : 0; const unsigned ellipsis_length = - style.strings_maximum_length >= 3 + active_style->strings_maximum_length >= 3 ? 3 - : style.strings_maximum_length; + : active_style->strings_maximum_length; o->write_characters(full_str.c_str(), start_len); o->write_characters("...", ellipsis_length); @@ -309,15 +346,15 @@ class fancy_serializer /// the output of the fancy_serializer output_adapter_t o = nullptr; - /// the indentation string - string_t indent_string; - /// Used for serializing "base" objects. Strings are sort of /// counted in this, but not completely. primitive_serializer_t prim_serializer; + /// the indentation string + string_t indent_string; + /// Output style - fancy_serializer_style style; + const stylizer_t stylizer; }; } @@ -325,9 +362,10 @@ template std::ostream& fancy_dump(std::ostream& o, const BasicJsonType& j, fancy_serializer_style style) { + fancy_serializer_stylizer stylizer(style); // do the actual serialization - detail::fancy_serializer s(detail::output_adapter(o), style); - s.dump(j, false, 0u); + detail::fancy_serializer s(detail::output_adapter(o), stylizer); + s.dump(j, false); return o; } diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 1e1212037..e05568136 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -10075,6 +10075,7 @@ class serializer #include // numeric_limits #include // string #include // is_same +#include // #include @@ -10104,6 +10105,26 @@ struct fancy_serializer_style unsigned int strings_maximum_length = 0; }; +template +class fancy_serializer_stylizer +{ + public: + fancy_serializer_stylizer(fancy_serializer_style const& ds) + : default_style(ds) + {} + + fancy_serializer_stylizer() = delete; + + public: + const fancy_serializer_style& get_default_style() const + { + return default_style; + } + + private: + fancy_serializer_style default_style; +}; + namespace detail { /////////////////// @@ -10113,6 +10134,7 @@ namespace detail template class fancy_serializer { + using stylizer_t = fancy_serializer_stylizer; using primitive_serializer_t = primitive_serializer; using string_t = typename BasicJsonType::string_t; using number_float_t = typename BasicJsonType::number_float_t; @@ -10127,14 +10149,21 @@ class fancy_serializer @param[in] ichar indentation character to use */ fancy_serializer(output_adapter_t s, - const fancy_serializer_style& st) - : o(std::move(s)), indent_string(512, st.indent_char), style(st) + const stylizer_t& st) + : o(std::move(s)), stylizer(st), + indent_string(512, st.get_default_style().indent_char) {} // delete because of pointer members fancy_serializer(const fancy_serializer&) = delete; fancy_serializer& operator=(const fancy_serializer&) = delete; + void dump(const BasicJsonType& val, const bool ensure_ascii) + { + dump(val, ensure_ascii, 0, &stylizer.get_default_style()); + } + + private: /*! @brief internal implementation of the serialization function @@ -10153,25 +10182,26 @@ class fancy_serializer */ void dump(const BasicJsonType& val, const bool ensure_ascii, - const unsigned int depth = 0) + const unsigned int depth, + const fancy_serializer_style* active_style) { switch (val.m_type) { case value_t::object: { - dump_object(val, ensure_ascii, depth); + dump_object(val, ensure_ascii, depth, active_style); return; } case value_t::array: { - dump_array(val, ensure_ascii, depth); + dump_array(val, ensure_ascii, depth, active_style); return; } case value_t::string: { - dump_string(*val.m_value.string, ensure_ascii); + dump_string(*val.m_value.string, ensure_ascii, active_style); return; } @@ -10221,27 +10251,30 @@ class fancy_serializer } private: - void dump_object(const BasicJsonType& val, bool ensure_ascii, unsigned int depth) + void dump_object(const BasicJsonType& val, + bool ensure_ascii, + unsigned int depth, + const fancy_serializer_style* active_style) { if (val.m_value.object->empty()) { o->write_characters("{}", 2); return; } - else if (depth >= style.depth_limit) + else if (depth >= active_style->depth_limit) { o->write_characters("{...}", 5); return; } // variable to hold indentation for recursive calls - const auto old_indent = depth * style.indent_step; - const auto new_indent = (depth + 1) * style.indent_step; + const auto old_indent = depth * active_style->indent_step; + const auto new_indent = (depth + 1) * active_style->indent_step; if (JSON_UNLIKELY(indent_string.size() < new_indent)) { - indent_string.resize(indent_string.size() * 2, style.indent_char); + indent_string.resize(indent_string.size() * 2, active_style->indent_char); } - const int newline_len = (style.indent_step > 0); + const int newline_len = (active_style->indent_step > 0); o->write_characters("{\n", 1 + newline_len); @@ -10253,7 +10286,7 @@ class fancy_serializer o->write_character('\"'); prim_serializer.dump_escaped(*o, i->first, ensure_ascii); o->write_characters("\": ", 2 + newline_len); - dump(i->second, ensure_ascii, depth + 1); + dump(i->second, ensure_ascii, depth + 1, active_style); o->write_characters(",\n", 1 + newline_len); } @@ -10264,34 +10297,37 @@ class fancy_serializer o->write_character('\"'); prim_serializer.dump_escaped(*o, i->first, ensure_ascii); o->write_characters("\": ", 2 + newline_len); - dump(i->second, ensure_ascii, depth + 1); + dump(i->second, ensure_ascii, depth + 1, active_style); o->write_characters("\n", newline_len); o->write_characters(indent_string.c_str(), old_indent); o->write_character('}'); } - void dump_array(const BasicJsonType& val, bool ensure_ascii, unsigned int depth) + void dump_array(const BasicJsonType& val, + bool ensure_ascii, + unsigned int depth, + const fancy_serializer_style* active_style) { if (val.m_value.array->empty()) { o->write_characters("[]", 2); return; } - else if (depth >= style.depth_limit) + else if (depth >= active_style->depth_limit) { o->write_characters("[...]", 5); return; } // variable to hold indentation for recursive calls - const auto old_indent = depth * style.indent_step; - const auto new_indent = (depth + 1) * style.indent_step; + const auto old_indent = depth * active_style->indent_step; + const auto new_indent = (depth + 1) * active_style->indent_step; if (JSON_UNLIKELY(indent_string.size() < new_indent)) { - indent_string.resize(indent_string.size() * 2, style.indent_char); + indent_string.resize(indent_string.size() * 2, active_style->indent_char); } - const int newline_len = (style.indent_step > 0); + const int newline_len = (active_style->indent_step > 0); o->write_characters("[\n", 1 + newline_len); @@ -10300,24 +10336,25 @@ class fancy_serializer i != val.m_value.array->cend() - 1; ++i) { o->write_characters(indent_string.c_str(), new_indent); - dump(*i, ensure_ascii, depth + 1); + dump(*i, ensure_ascii, depth + 1, active_style); o->write_characters(",\n", 1 + newline_len); } // last element assert(not val.m_value.array->empty()); o->write_characters(indent_string.c_str(), new_indent); - dump(val.m_value.array->back(), ensure_ascii, depth + 1); + dump(val.m_value.array->back(), ensure_ascii, depth + 1, active_style); o->write_characters("\n", newline_len); o->write_characters(indent_string.c_str(), old_indent); o->write_character(']'); } - void dump_string(const string_t& str, bool ensure_ascii) + void dump_string(const string_t& str, bool ensure_ascii, + const fancy_serializer_style* active_style) { o->write_character('\"'); - if (style.strings_maximum_length == 0) + if (active_style->strings_maximum_length == 0) { prim_serializer.dump_escaped(*o, str, ensure_ascii); } @@ -10329,7 +10366,7 @@ class fancy_serializer prim_serializer.dump_escaped(*oo_string, str, ensure_ascii); std::string full_str = ss.str(); - if (full_str.size() <= style.strings_maximum_length) + if (full_str.size() <= active_style->strings_maximum_length) { o->write_characters(full_str.c_str(), full_str.size()); } @@ -10357,15 +10394,15 @@ class fancy_serializer // and one for the last character. return maxl - 4; } - }(style.strings_maximum_length); + }(active_style->strings_maximum_length); const unsigned end_len = - style.strings_maximum_length >= 5 ? 1 : 0; + active_style->strings_maximum_length >= 5 ? 1 : 0; const unsigned ellipsis_length = - style.strings_maximum_length >= 3 + active_style->strings_maximum_length >= 3 ? 3 - : style.strings_maximum_length; + : active_style->strings_maximum_length; o->write_characters(full_str.c_str(), start_len); o->write_characters("...", ellipsis_length); @@ -10379,15 +10416,15 @@ class fancy_serializer /// the output of the fancy_serializer output_adapter_t o = nullptr; - /// the indentation string - string_t indent_string; - /// Used for serializing "base" objects. Strings are sort of /// counted in this, but not completely. primitive_serializer_t prim_serializer; + /// the indentation string + string_t indent_string; + /// Output style - fancy_serializer_style style; + const stylizer_t stylizer; }; } @@ -10395,9 +10432,10 @@ template std::ostream& fancy_dump(std::ostream& o, const BasicJsonType& j, fancy_serializer_style style) { + fancy_serializer_stylizer stylizer(style); // do the actual serialization - detail::fancy_serializer s(detail::output_adapter(o), style); - s.dump(j, false, 0u); + detail::fancy_serializer s(detail::output_adapter(o), stylizer); + s.dump(j, false); return o; } From 186c7df25ae37a279b59d68356c1b8c56220bb70 Mon Sep 17 00:00:00 2001 From: Evan Driscoll Date: Sat, 2 Jun 2018 23:10:28 -0500 Subject: [PATCH 21/41] Can style subobjects of a object differently, by key --- .../detail/output/fancy_serializer.hpp | 45 ++++++++++++++---- include/nlohmann/json.hpp | 2 + single_include/nlohmann/json.hpp | 47 +++++++++++++++---- test/src/unit-fancy-serialization.cpp | 38 +++++++++++++++ 4 files changed, 116 insertions(+), 16 deletions(-) diff --git a/include/nlohmann/detail/output/fancy_serializer.hpp b/include/nlohmann/detail/output/fancy_serializer.hpp index 87fd9578e..2d783b77c 100644 --- a/include/nlohmann/detail/output/fancy_serializer.hpp +++ b/include/nlohmann/detail/output/fancy_serializer.hpp @@ -36,14 +36,16 @@ struct fancy_serializer_style }; template -class fancy_serializer_stylizer +class basic_fancy_serializer_stylizer { public: - fancy_serializer_stylizer(fancy_serializer_style const& ds) + using string_t = typename BasicJsonType::string_t; + + basic_fancy_serializer_stylizer(fancy_serializer_style const& ds) : default_style(ds) {} - fancy_serializer_stylizer() = delete; + basic_fancy_serializer_stylizer() = default; public: const fancy_serializer_style& get_default_style() const @@ -51,8 +53,26 @@ class fancy_serializer_stylizer return default_style; } + fancy_serializer_style& get_default_style() + { + return default_style; + } + + const fancy_serializer_style& get_style(const string_t& j) const + { + auto iter = key_styles.find(j); + return iter == key_styles.end() ? default_style : iter->second; + } + + fancy_serializer_style& get_or_insert_style(const string_t& j) + { + return key_styles[j]; + } + private: fancy_serializer_style default_style; + + std::map key_styles; }; namespace detail @@ -64,7 +84,7 @@ namespace detail template class fancy_serializer { - using stylizer_t = fancy_serializer_stylizer; + using stylizer_t = basic_fancy_serializer_stylizer; using primitive_serializer_t = primitive_serializer; using string_t = typename BasicJsonType::string_t; using number_float_t = typename BasicJsonType::number_float_t; @@ -216,7 +236,8 @@ class fancy_serializer o->write_character('\"'); prim_serializer.dump_escaped(*o, i->first, ensure_ascii); o->write_characters("\": ", 2 + newline_len); - dump(i->second, ensure_ascii, depth + 1, active_style); + auto new_style = &stylizer.get_style(i->first); + dump(i->second, ensure_ascii, depth + 1, new_style); o->write_characters(",\n", 1 + newline_len); } @@ -227,7 +248,8 @@ class fancy_serializer o->write_character('\"'); prim_serializer.dump_escaped(*o, i->first, ensure_ascii); o->write_characters("\": ", 2 + newline_len); - dump(i->second, ensure_ascii, depth + 1, active_style); + auto new_style = &stylizer.get_style(i->first); + dump(i->second, ensure_ascii, depth + 1, new_style); o->write_characters("\n", newline_len); o->write_characters(indent_string.c_str(), old_indent); @@ -360,13 +382,20 @@ class fancy_serializer template std::ostream& fancy_dump(std::ostream& o, const BasicJsonType& j, - fancy_serializer_style style) + basic_fancy_serializer_stylizer const& stylizer) { - fancy_serializer_stylizer stylizer(style); // do the actual serialization detail::fancy_serializer s(detail::output_adapter(o), stylizer); s.dump(j, false); return o; } +template +std::ostream& fancy_dump(std::ostream& o, const BasicJsonType& j, + fancy_serializer_style style) +{ + basic_fancy_serializer_stylizer stylizer(style); + return fancy_dump(o, j, stylizer); +} + } diff --git a/include/nlohmann/json.hpp b/include/nlohmann/json.hpp index b2836b6ca..16984d5f7 100644 --- a/include/nlohmann/json.hpp +++ b/include/nlohmann/json.hpp @@ -7624,6 +7624,8 @@ class basic_json /// @} }; + +using fancy_serializer_stylizer = basic_fancy_serializer_stylizer; } // namespace nlohmann /////////////////////// diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index e05568136..0009c3715 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -10106,14 +10106,16 @@ struct fancy_serializer_style }; template -class fancy_serializer_stylizer +class basic_fancy_serializer_stylizer { public: - fancy_serializer_stylizer(fancy_serializer_style const& ds) + using string_t = typename BasicJsonType::string_t; + + basic_fancy_serializer_stylizer(fancy_serializer_style const& ds) : default_style(ds) {} - fancy_serializer_stylizer() = delete; + basic_fancy_serializer_stylizer() = default; public: const fancy_serializer_style& get_default_style() const @@ -10121,8 +10123,26 @@ class fancy_serializer_stylizer return default_style; } + fancy_serializer_style& get_default_style() + { + return default_style; + } + + const fancy_serializer_style& get_style(const string_t& j) const + { + auto iter = key_styles.find(j); + return iter == key_styles.end() ? default_style : iter->second; + } + + fancy_serializer_style& get_or_insert_style(const string_t& j) + { + return key_styles[j]; + } + private: fancy_serializer_style default_style; + + std::map key_styles; }; namespace detail @@ -10134,7 +10154,7 @@ namespace detail template class fancy_serializer { - using stylizer_t = fancy_serializer_stylizer; + using stylizer_t = basic_fancy_serializer_stylizer; using primitive_serializer_t = primitive_serializer; using string_t = typename BasicJsonType::string_t; using number_float_t = typename BasicJsonType::number_float_t; @@ -10286,7 +10306,8 @@ class fancy_serializer o->write_character('\"'); prim_serializer.dump_escaped(*o, i->first, ensure_ascii); o->write_characters("\": ", 2 + newline_len); - dump(i->second, ensure_ascii, depth + 1, active_style); + auto new_style = &stylizer.get_style(i->first); + dump(i->second, ensure_ascii, depth + 1, new_style); o->write_characters(",\n", 1 + newline_len); } @@ -10297,7 +10318,8 @@ class fancy_serializer o->write_character('\"'); prim_serializer.dump_escaped(*o, i->first, ensure_ascii); o->write_characters("\": ", 2 + newline_len); - dump(i->second, ensure_ascii, depth + 1, active_style); + auto new_style = &stylizer.get_style(i->first); + dump(i->second, ensure_ascii, depth + 1, new_style); o->write_characters("\n", newline_len); o->write_characters(indent_string.c_str(), old_indent); @@ -10430,15 +10452,22 @@ class fancy_serializer template std::ostream& fancy_dump(std::ostream& o, const BasicJsonType& j, - fancy_serializer_style style) + basic_fancy_serializer_stylizer const& stylizer) { - fancy_serializer_stylizer stylizer(style); // do the actual serialization detail::fancy_serializer s(detail::output_adapter(o), stylizer); s.dump(j, false); return o; } +template +std::ostream& fancy_dump(std::ostream& o, const BasicJsonType& j, + fancy_serializer_style style) +{ + basic_fancy_serializer_stylizer stylizer(style); + return fancy_dump(o, j, stylizer); +} + } // #include @@ -18811,6 +18840,8 @@ class basic_json /// @} }; + +using fancy_serializer_stylizer = basic_fancy_serializer_stylizer; } // namespace nlohmann /////////////////////// diff --git a/test/src/unit-fancy-serialization.cpp b/test/src/unit-fancy-serialization.cpp index c50a1365f..8c82a32c4 100644 --- a/test/src/unit-fancy-serialization.cpp +++ b/test/src/unit-fancy-serialization.cpp @@ -34,6 +34,7 @@ SOFTWARE. using nlohmann::json; using nlohmann::fancy_dump; using nlohmann::fancy_serializer_style; +using nlohmann::fancy_serializer_stylizer; // Chops off the first line (if empty, but if it *isn't* empty you're // probably using this wrong), measures the leading indent on the @@ -77,6 +78,13 @@ std::string fancy_to_string(json j, fancy_serializer_style style = fancy_seriali return ss.str(); } +std::string fancy_to_string(json j, fancy_serializer_stylizer stylizer) +{ + std::stringstream ss; + fancy_dump(ss, j, stylizer); + return ss.str(); +} + TEST_CASE("serialization") { SECTION("primitives") @@ -220,6 +228,36 @@ TEST_CASE("serialization") } } + SECTION("changing styles") + { + SECTION("can style objects of a key differently") + { + fancy_serializer_stylizer stylizer; + stylizer.get_default_style().indent_step = 4; + stylizer.get_or_insert_style("one line").indent_step = 0; + + auto str = fancy_to_string( + { + { + "one line", {1, 2} + }, + { + "two lines", {1, 2} + } + }, + stylizer); + + CHECK(str == dedent(R"( + { + "one line": [1,2], + "two lines": [ + 1, + 2 + ] + })")); + } + } + SECTION("given width") { fancy_serializer_style style; From ef679f89884b16e1401aa47a1a5e6add5c6b67f4 Mon Sep 17 00:00:00 2001 From: Evan Driscoll Date: Sat, 2 Jun 2018 23:17:42 -0500 Subject: [PATCH 22/41] Fancy serializer: styles continue to propagate if not overridden This is half a bug fix half just continuing work from befor --- .../detail/output/fancy_serializer.hpp | 10 ++++++---- single_include/nlohmann/json.hpp | 10 ++++++---- test/src/unit-fancy-serialization.cpp | 20 +++++++++++++++++++ 3 files changed, 32 insertions(+), 8 deletions(-) diff --git a/include/nlohmann/detail/output/fancy_serializer.hpp b/include/nlohmann/detail/output/fancy_serializer.hpp index 2d783b77c..2e4bba8b2 100644 --- a/include/nlohmann/detail/output/fancy_serializer.hpp +++ b/include/nlohmann/detail/output/fancy_serializer.hpp @@ -58,10 +58,12 @@ class basic_fancy_serializer_stylizer return default_style; } - const fancy_serializer_style& get_style(const string_t& j) const + const fancy_serializer_style* get_new_style_or_active( + const string_t& j, + const fancy_serializer_style* active_style) const { auto iter = key_styles.find(j); - return iter == key_styles.end() ? default_style : iter->second; + return iter == key_styles.end() ? active_style : &iter->second; } fancy_serializer_style& get_or_insert_style(const string_t& j) @@ -236,7 +238,7 @@ class fancy_serializer o->write_character('\"'); prim_serializer.dump_escaped(*o, i->first, ensure_ascii); o->write_characters("\": ", 2 + newline_len); - auto new_style = &stylizer.get_style(i->first); + auto new_style = stylizer.get_new_style_or_active(i->first, active_style); dump(i->second, ensure_ascii, depth + 1, new_style); o->write_characters(",\n", 1 + newline_len); } @@ -248,7 +250,7 @@ class fancy_serializer o->write_character('\"'); prim_serializer.dump_escaped(*o, i->first, ensure_ascii); o->write_characters("\": ", 2 + newline_len); - auto new_style = &stylizer.get_style(i->first); + auto new_style = stylizer.get_new_style_or_active(i->first, active_style); dump(i->second, ensure_ascii, depth + 1, new_style); o->write_characters("\n", newline_len); diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 0009c3715..87fa8de2e 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -10128,10 +10128,12 @@ class basic_fancy_serializer_stylizer return default_style; } - const fancy_serializer_style& get_style(const string_t& j) const + const fancy_serializer_style* get_new_style_or_active( + const string_t& j, + const fancy_serializer_style* active_style) const { auto iter = key_styles.find(j); - return iter == key_styles.end() ? default_style : iter->second; + return iter == key_styles.end() ? active_style : &iter->second; } fancy_serializer_style& get_or_insert_style(const string_t& j) @@ -10306,7 +10308,7 @@ class fancy_serializer o->write_character('\"'); prim_serializer.dump_escaped(*o, i->first, ensure_ascii); o->write_characters("\": ", 2 + newline_len); - auto new_style = &stylizer.get_style(i->first); + auto new_style = stylizer.get_new_style_or_active(i->first, active_style); dump(i->second, ensure_ascii, depth + 1, new_style); o->write_characters(",\n", 1 + newline_len); } @@ -10318,7 +10320,7 @@ class fancy_serializer o->write_character('\"'); prim_serializer.dump_escaped(*o, i->first, ensure_ascii); o->write_characters("\": ", 2 + newline_len); - auto new_style = &stylizer.get_style(i->first); + auto new_style = stylizer.get_new_style_or_active(i->first, active_style); dump(i->second, ensure_ascii, depth + 1, new_style); o->write_characters("\n", newline_len); diff --git a/test/src/unit-fancy-serialization.cpp b/test/src/unit-fancy-serialization.cpp index 8c82a32c4..26af09939 100644 --- a/test/src/unit-fancy-serialization.cpp +++ b/test/src/unit-fancy-serialization.cpp @@ -256,6 +256,26 @@ TEST_CASE("serialization") ] })")); } + + SECTION("changes propagate (unless overridden)") + { + fancy_serializer_stylizer stylizer; + stylizer.get_default_style().indent_step = 4; + stylizer.get_or_insert_style("one line").indent_step = 0; + + auto str = fancy_to_string( + { + { + "one line", {{"still one line", {1, 2}}} + }, + }, + stylizer); + + CHECK(str == dedent(R"( + { + "one line": {"still one line":[1,2]} + })")); + } } SECTION("given width") From 5e10e97296f4f6e32a2d1de2d8ae08565b541325 Mon Sep 17 00:00:00 2001 From: Evan Driscoll Date: Sat, 2 Jun 2018 23:35:38 -0500 Subject: [PATCH 23/41] Refactor: dump_object_key_value This isn't as clean as I wanted -- there's a lot more context than I realized -- but I think it's still an improvement and will become even moreso later --- .../detail/output/fancy_serializer.hpp | 30 +++++++++++-------- single_include/nlohmann/json.hpp | 30 +++++++++++-------- 2 files changed, 36 insertions(+), 24 deletions(-) diff --git a/include/nlohmann/detail/output/fancy_serializer.hpp b/include/nlohmann/detail/output/fancy_serializer.hpp index 2e4bba8b2..6980b29d8 100644 --- a/include/nlohmann/detail/output/fancy_serializer.hpp +++ b/include/nlohmann/detail/output/fancy_serializer.hpp @@ -203,6 +203,22 @@ class fancy_serializer } private: + template + void dump_object_key_value( + Iterator i, bool ensure_ascii, unsigned int depth, + const fancy_serializer_style* active_style) + { + const auto new_indent = (depth + 1) * active_style->indent_step; + const int newline_len = (active_style->indent_step > 0); + + o->write_characters(indent_string.c_str(), new_indent); + o->write_character('\"'); + prim_serializer.dump_escaped(*o, i->first, ensure_ascii); + o->write_characters("\": ", 2 + newline_len); + auto new_style = stylizer.get_new_style_or_active(i->first, active_style); + dump(i->second, ensure_ascii, depth + 1, new_style); + } + void dump_object(const BasicJsonType& val, bool ensure_ascii, unsigned int depth, @@ -234,24 +250,14 @@ class fancy_serializer auto i = val.m_value.object->cbegin(); for (std::size_t cnt = 0; cnt < val.m_value.object->size() - 1; ++cnt, ++i) { - o->write_characters(indent_string.c_str(), new_indent); - o->write_character('\"'); - prim_serializer.dump_escaped(*o, i->first, ensure_ascii); - o->write_characters("\": ", 2 + newline_len); - auto new_style = stylizer.get_new_style_or_active(i->first, active_style); - dump(i->second, ensure_ascii, depth + 1, new_style); + dump_object_key_value(i, ensure_ascii, depth, active_style); o->write_characters(",\n", 1 + newline_len); } // last element assert(i != val.m_value.object->cend()); assert(std::next(i) == val.m_value.object->cend()); - o->write_characters(indent_string.c_str(), new_indent); - o->write_character('\"'); - prim_serializer.dump_escaped(*o, i->first, ensure_ascii); - o->write_characters("\": ", 2 + newline_len); - auto new_style = stylizer.get_new_style_or_active(i->first, active_style); - dump(i->second, ensure_ascii, depth + 1, new_style); + dump_object_key_value(i, ensure_ascii, depth, active_style); o->write_characters("\n", newline_len); o->write_characters(indent_string.c_str(), old_indent); diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 87fa8de2e..5ca8601d3 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -10273,6 +10273,22 @@ class fancy_serializer } private: + template + void dump_object_key_value( + Iterator i, bool ensure_ascii, unsigned int depth, + const fancy_serializer_style* active_style) + { + const auto new_indent = (depth + 1) * active_style->indent_step; + const int newline_len = (active_style->indent_step > 0); + + o->write_characters(indent_string.c_str(), new_indent); + o->write_character('\"'); + prim_serializer.dump_escaped(*o, i->first, ensure_ascii); + o->write_characters("\": ", 2 + newline_len); + auto new_style = stylizer.get_new_style_or_active(i->first, active_style); + dump(i->second, ensure_ascii, depth + 1, new_style); + } + void dump_object(const BasicJsonType& val, bool ensure_ascii, unsigned int depth, @@ -10304,24 +10320,14 @@ class fancy_serializer auto i = val.m_value.object->cbegin(); for (std::size_t cnt = 0; cnt < val.m_value.object->size() - 1; ++cnt, ++i) { - o->write_characters(indent_string.c_str(), new_indent); - o->write_character('\"'); - prim_serializer.dump_escaped(*o, i->first, ensure_ascii); - o->write_characters("\": ", 2 + newline_len); - auto new_style = stylizer.get_new_style_or_active(i->first, active_style); - dump(i->second, ensure_ascii, depth + 1, new_style); + dump_object_key_value(i, ensure_ascii, depth, active_style); o->write_characters(",\n", 1 + newline_len); } // last element assert(i != val.m_value.object->cend()); assert(std::next(i) == val.m_value.object->cend()); - o->write_characters(indent_string.c_str(), new_indent); - o->write_character('\"'); - prim_serializer.dump_escaped(*o, i->first, ensure_ascii); - o->write_characters("\": ", 2 + newline_len); - auto new_style = stylizer.get_new_style_or_active(i->first, active_style); - dump(i->second, ensure_ascii, depth + 1, new_style); + dump_object_key_value(i, ensure_ascii, depth, active_style); o->write_characters("\n", newline_len); o->write_characters(indent_string.c_str(), old_indent); From af8dd92a0cb16ce1d760db32873159d1693fb6b6 Mon Sep 17 00:00:00 2001 From: Evan Driscoll Date: Sat, 2 Jun 2018 23:48:26 -0500 Subject: [PATCH 24/41] Refactor: introduce explicit multiline control Instead of depending on indent_step --- include/nlohmann/detail/output/fancy_serializer.hpp | 6 ++++-- single_include/nlohmann/json.hpp | 6 ++++-- test/src/unit-fancy-serialization.cpp | 9 +++++++++ 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/include/nlohmann/detail/output/fancy_serializer.hpp b/include/nlohmann/detail/output/fancy_serializer.hpp index 6980b29d8..315cba31f 100644 --- a/include/nlohmann/detail/output/fancy_serializer.hpp +++ b/include/nlohmann/detail/output/fancy_serializer.hpp @@ -33,6 +33,8 @@ struct fancy_serializer_style unsigned int depth_limit = std::numeric_limits::max(); unsigned int strings_maximum_length = 0; + + bool multiline = false; }; template @@ -242,7 +244,7 @@ class fancy_serializer { indent_string.resize(indent_string.size() * 2, active_style->indent_char); } - const int newline_len = (active_style->indent_step > 0); + const int newline_len = (active_style->multiline ? 1 : 0); o->write_characters("{\n", 1 + newline_len); @@ -287,7 +289,7 @@ class fancy_serializer { indent_string.resize(indent_string.size() * 2, active_style->indent_char); } - const int newline_len = (active_style->indent_step > 0); + const int newline_len = (active_style->multiline ? 1 : 0); o->write_characters("[\n", 1 + newline_len); diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 5ca8601d3..6edec302f 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -10103,6 +10103,8 @@ struct fancy_serializer_style unsigned int depth_limit = std::numeric_limits::max(); unsigned int strings_maximum_length = 0; + + bool multiline = false; }; template @@ -10312,7 +10314,7 @@ class fancy_serializer { indent_string.resize(indent_string.size() * 2, active_style->indent_char); } - const int newline_len = (active_style->indent_step > 0); + const int newline_len = (active_style->multiline ? 1 : 0); o->write_characters("{\n", 1 + newline_len); @@ -10357,7 +10359,7 @@ class fancy_serializer { indent_string.resize(indent_string.size() * 2, active_style->indent_char); } - const int newline_len = (active_style->indent_step > 0); + const int newline_len = (active_style->multiline ? 1 : 0); o->write_characters("[\n", 1 + newline_len); diff --git a/test/src/unit-fancy-serialization.cpp b/test/src/unit-fancy-serialization.cpp index 26af09939..6f00a9b21 100644 --- a/test/src/unit-fancy-serialization.cpp +++ b/test/src/unit-fancy-serialization.cpp @@ -202,6 +202,7 @@ TEST_CASE("serialization") CHECK(str_flat == "[1,[...]]"); style.indent_step = 4; + style.multiline = true; auto str_lines = fancy_to_string({1, {1}}, style); CHECK(str_lines == dedent(R"( [ @@ -219,6 +220,7 @@ TEST_CASE("serialization") CHECK(str_flat == "[1,{...}]"); style.indent_step = 4; + style.multiline = true; auto str_lines = fancy_to_string({1, {{"one", 1}}}, style); CHECK(str_lines == dedent(R"( [ @@ -234,7 +236,9 @@ TEST_CASE("serialization") { fancy_serializer_stylizer stylizer; stylizer.get_default_style().indent_step = 4; + stylizer.get_default_style().multiline = true; stylizer.get_or_insert_style("one line").indent_step = 0; + stylizer.get_or_insert_style("one line").multiline = false; auto str = fancy_to_string( { @@ -261,6 +265,7 @@ TEST_CASE("serialization") { fancy_serializer_stylizer stylizer; stylizer.get_default_style().indent_step = 4; + stylizer.get_default_style().multiline = 4; stylizer.get_or_insert_style("one line").indent_step = 0; auto str = fancy_to_string( @@ -282,6 +287,7 @@ TEST_CASE("serialization") { fancy_serializer_style style; style.indent_step = 4; + style.multiline = 4; auto str = fancy_to_string({"foo", 1, 2, 3, false, {{"one", 1}}}, style); CHECK(str == dedent(R"( [ @@ -301,6 +307,7 @@ TEST_CASE("serialization") fancy_serializer_style style; style.indent_step = 1; style.indent_char = '\t'; + style.multiline = true; auto str = fancy_to_string({"foo", 1, 2, 3, false, {{"one", 1}}}, style); CHECK(str == @@ -322,6 +329,7 @@ TEST_CASE("serialization") fancy_serializer_style style; style.indent_step = 300; style.indent_char = 'X'; + style.multiline = true; auto str = fancy_to_string({1, {1}}, style); @@ -340,6 +348,7 @@ TEST_CASE("serialization") fancy_serializer_style style; style.indent_step = 300; style.indent_char = 'X'; + style.multiline = true; auto str = fancy_to_string({{"key", {{"key", 1}}}}, style); From ec756e47adaea650244e6e35596f8568f0fe8c4d Mon Sep 17 00:00:00 2001 From: Evan Driscoll Date: Sat, 2 Jun 2018 23:52:30 -0500 Subject: [PATCH 25/41] Refactor: don't indent when multiline is false --- include/nlohmann/detail/output/fancy_serializer.hpp | 12 ++++++------ single_include/nlohmann/json.hpp | 12 ++++++------ test/src/unit-fancy-serialization.cpp | 10 ++-------- 3 files changed, 14 insertions(+), 20 deletions(-) diff --git a/include/nlohmann/detail/output/fancy_serializer.hpp b/include/nlohmann/detail/output/fancy_serializer.hpp index 315cba31f..580ecb571 100644 --- a/include/nlohmann/detail/output/fancy_serializer.hpp +++ b/include/nlohmann/detail/output/fancy_serializer.hpp @@ -27,7 +27,7 @@ namespace nlohmann struct fancy_serializer_style { - unsigned int indent_step = 0; + unsigned int indent_step = 4; char indent_char = ' '; unsigned int depth_limit = std::numeric_limits::max(); @@ -210,7 +210,7 @@ class fancy_serializer Iterator i, bool ensure_ascii, unsigned int depth, const fancy_serializer_style* active_style) { - const auto new_indent = (depth + 1) * active_style->indent_step; + const auto new_indent = (depth + 1) * active_style->indent_step * active_style->multiline; const int newline_len = (active_style->indent_step > 0); o->write_characters(indent_string.c_str(), new_indent); @@ -238,8 +238,8 @@ class fancy_serializer } // variable to hold indentation for recursive calls - const auto old_indent = depth * active_style->indent_step; - const auto new_indent = (depth + 1) * active_style->indent_step; + const auto old_indent = depth * active_style->indent_step * active_style->multiline; + const auto new_indent = (depth + 1) * active_style->indent_step * active_style->multiline; if (JSON_UNLIKELY(indent_string.size() < new_indent)) { indent_string.resize(indent_string.size() * 2, active_style->indent_char); @@ -283,8 +283,8 @@ class fancy_serializer } // variable to hold indentation for recursive calls - const auto old_indent = depth * active_style->indent_step; - const auto new_indent = (depth + 1) * active_style->indent_step; + const auto old_indent = depth * active_style->indent_step * active_style->multiline;; + const auto new_indent = (depth + 1) * active_style->indent_step * active_style->multiline;; if (JSON_UNLIKELY(indent_string.size() < new_indent)) { indent_string.resize(indent_string.size() * 2, active_style->indent_char); diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 6edec302f..fa5b7f251 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -10097,7 +10097,7 @@ namespace nlohmann struct fancy_serializer_style { - unsigned int indent_step = 0; + unsigned int indent_step = 4; char indent_char = ' '; unsigned int depth_limit = std::numeric_limits::max(); @@ -10280,7 +10280,7 @@ class fancy_serializer Iterator i, bool ensure_ascii, unsigned int depth, const fancy_serializer_style* active_style) { - const auto new_indent = (depth + 1) * active_style->indent_step; + const auto new_indent = (depth + 1) * active_style->indent_step * active_style->multiline; const int newline_len = (active_style->indent_step > 0); o->write_characters(indent_string.c_str(), new_indent); @@ -10308,8 +10308,8 @@ class fancy_serializer } // variable to hold indentation for recursive calls - const auto old_indent = depth * active_style->indent_step; - const auto new_indent = (depth + 1) * active_style->indent_step; + const auto old_indent = depth * active_style->indent_step * active_style->multiline; + const auto new_indent = (depth + 1) * active_style->indent_step * active_style->multiline; if (JSON_UNLIKELY(indent_string.size() < new_indent)) { indent_string.resize(indent_string.size() * 2, active_style->indent_char); @@ -10353,8 +10353,8 @@ class fancy_serializer } // variable to hold indentation for recursive calls - const auto old_indent = depth * active_style->indent_step; - const auto new_indent = (depth + 1) * active_style->indent_step; + const auto old_indent = depth * active_style->indent_step * active_style->multiline;; + const auto new_indent = (depth + 1) * active_style->indent_step * active_style->multiline;; if (JSON_UNLIKELY(indent_string.size() < new_indent)) { indent_string.resize(indent_string.size() * 2, active_style->indent_char); diff --git a/test/src/unit-fancy-serialization.cpp b/test/src/unit-fancy-serialization.cpp index 6f00a9b21..edb371593 100644 --- a/test/src/unit-fancy-serialization.cpp +++ b/test/src/unit-fancy-serialization.cpp @@ -201,7 +201,6 @@ TEST_CASE("serialization") auto str_flat = fancy_to_string({1, {1}}, style); CHECK(str_flat == "[1,[...]]"); - style.indent_step = 4; style.multiline = true; auto str_lines = fancy_to_string({1, {1}}, style); CHECK(str_lines == dedent(R"( @@ -219,7 +218,6 @@ TEST_CASE("serialization") auto str_flat = fancy_to_string({1, {{"one", 1}}}, style); CHECK(str_flat == "[1,{...}]"); - style.indent_step = 4; style.multiline = true; auto str_lines = fancy_to_string({1, {{"one", 1}}}, style); CHECK(str_lines == dedent(R"( @@ -235,9 +233,7 @@ TEST_CASE("serialization") SECTION("can style objects of a key differently") { fancy_serializer_stylizer stylizer; - stylizer.get_default_style().indent_step = 4; stylizer.get_default_style().multiline = true; - stylizer.get_or_insert_style("one line").indent_step = 0; stylizer.get_or_insert_style("one line").multiline = false; auto str = fancy_to_string( @@ -264,8 +260,7 @@ TEST_CASE("serialization") SECTION("changes propagate (unless overridden)") { fancy_serializer_stylizer stylizer; - stylizer.get_default_style().indent_step = 4; - stylizer.get_default_style().multiline = 4; + stylizer.get_default_style().multiline = true; stylizer.get_or_insert_style("one line").indent_step = 0; auto str = fancy_to_string( @@ -286,8 +281,7 @@ TEST_CASE("serialization") SECTION("given width") { fancy_serializer_style style; - style.indent_step = 4; - style.multiline = 4; + style.multiline = true; auto str = fancy_to_string({"foo", 1, 2, 3, false, {{"one", 1}}}, style); CHECK(str == dedent(R"( [ From e7b02c10dd008a525da5307b33a8aefdd24995c8 Mon Sep 17 00:00:00 2001 From: Evan Driscoll Date: Sun, 3 Jun 2018 00:00:12 -0500 Subject: [PATCH 26/41] Spaces after a colon (in {"k": v}) controllable separately from multiline --- .../detail/output/fancy_serializer.hpp | 9 ++++- single_include/nlohmann/json.hpp | 9 ++++- test/src/unit-fancy-serialization.cpp | 40 +++++++++++++++---- 3 files changed, 48 insertions(+), 10 deletions(-) diff --git a/include/nlohmann/detail/output/fancy_serializer.hpp b/include/nlohmann/detail/output/fancy_serializer.hpp index 580ecb571..c210992b9 100644 --- a/include/nlohmann/detail/output/fancy_serializer.hpp +++ b/include/nlohmann/detail/output/fancy_serializer.hpp @@ -34,7 +34,14 @@ struct fancy_serializer_style unsigned int strings_maximum_length = 0; + bool space_after_colon = false; + bool multiline = false; + + void set_old_multiline() + { + space_after_colon = multiline = true; + } }; template @@ -211,7 +218,7 @@ class fancy_serializer const fancy_serializer_style* active_style) { const auto new_indent = (depth + 1) * active_style->indent_step * active_style->multiline; - const int newline_len = (active_style->indent_step > 0); + const int newline_len = active_style->space_after_colon; o->write_characters(indent_string.c_str(), new_indent); o->write_character('\"'); diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index fa5b7f251..5b78a676e 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -10104,7 +10104,14 @@ struct fancy_serializer_style unsigned int strings_maximum_length = 0; + bool space_after_colon = false; + bool multiline = false; + + void set_old_multiline() + { + space_after_colon = multiline = true; + } }; template @@ -10281,7 +10288,7 @@ class fancy_serializer const fancy_serializer_style* active_style) { const auto new_indent = (depth + 1) * active_style->indent_step * active_style->multiline; - const int newline_len = (active_style->indent_step > 0); + const int newline_len = active_style->space_after_colon; o->write_characters(indent_string.c_str(), new_indent); o->write_character('\"'); diff --git a/test/src/unit-fancy-serialization.cpp b/test/src/unit-fancy-serialization.cpp index edb371593..9c13177a3 100644 --- a/test/src/unit-fancy-serialization.cpp +++ b/test/src/unit-fancy-serialization.cpp @@ -201,7 +201,7 @@ TEST_CASE("serialization") auto str_flat = fancy_to_string({1, {1}}, style); CHECK(str_flat == "[1,[...]]"); - style.multiline = true; + style.set_old_multiline(); auto str_lines = fancy_to_string({1, {1}}, style); CHECK(str_lines == dedent(R"( [ @@ -218,7 +218,7 @@ TEST_CASE("serialization") auto str_flat = fancy_to_string({1, {{"one", 1}}}, style); CHECK(str_flat == "[1,{...}]"); - style.multiline = true; + style.set_old_multiline(); auto str_lines = fancy_to_string({1, {{"one", 1}}}, style); CHECK(str_lines == dedent(R"( [ @@ -233,7 +233,7 @@ TEST_CASE("serialization") SECTION("can style objects of a key differently") { fancy_serializer_stylizer stylizer; - stylizer.get_default_style().multiline = true; + stylizer.get_default_style().set_old_multiline(); stylizer.get_or_insert_style("one line").multiline = false; auto str = fancy_to_string( @@ -260,7 +260,7 @@ TEST_CASE("serialization") SECTION("changes propagate (unless overridden)") { fancy_serializer_stylizer stylizer; - stylizer.get_default_style().multiline = true; + stylizer.get_default_style().set_old_multiline(); stylizer.get_or_insert_style("one line").indent_step = 0; auto str = fancy_to_string( @@ -278,10 +278,34 @@ TEST_CASE("serialization") } } + SECTION("Spaces after commas are controllable separately from multiline") + { + SECTION("colons") + { + fancy_serializer_style style; + style.space_after_colon = true; + auto str = fancy_to_string({{"one", 1}}, style); + CHECK(str == "{\"one\": 1}"); + } + + SECTION("multiline can have no space") + { + fancy_serializer_style style; + style.set_old_multiline(); + style.space_after_colon = false; + auto str = fancy_to_string({{"one", 1}}, style); + CHECK(str == dedent(R"( + { + "one":1 + })")); + + } + } + SECTION("given width") { fancy_serializer_style style; - style.multiline = true; + style.set_old_multiline(); auto str = fancy_to_string({"foo", 1, 2, 3, false, {{"one", 1}}}, style); CHECK(str == dedent(R"( [ @@ -301,7 +325,7 @@ TEST_CASE("serialization") fancy_serializer_style style; style.indent_step = 1; style.indent_char = '\t'; - style.multiline = true; + style.set_old_multiline(); auto str = fancy_to_string({"foo", 1, 2, 3, false, {{"one", 1}}}, style); CHECK(str == @@ -323,7 +347,7 @@ TEST_CASE("serialization") fancy_serializer_style style; style.indent_step = 300; style.indent_char = 'X'; - style.multiline = true; + style.set_old_multiline(); auto str = fancy_to_string({1, {1}}, style); @@ -342,7 +366,7 @@ TEST_CASE("serialization") fancy_serializer_style style; style.indent_step = 300; style.indent_char = 'X'; - style.multiline = true; + style.set_old_multiline(); auto str = fancy_to_string({{"key", {{"key", 1}}}}, style); From 860661987f62e2d7938edb77dc8c1b12cc8dac2b Mon Sep 17 00:00:00 2001 From: Evan Driscoll Date: Sun, 3 Jun 2018 00:02:30 -0500 Subject: [PATCH 27/41] Spaces after commas (in [1, 2]) is controllable separately from multiline --- include/nlohmann/detail/output/fancy_serializer.hpp | 11 +++++++++-- single_include/nlohmann/json.hpp | 11 +++++++++-- test/src/unit-fancy-serialization.cpp | 8 ++++++++ 3 files changed, 26 insertions(+), 4 deletions(-) diff --git a/include/nlohmann/detail/output/fancy_serializer.hpp b/include/nlohmann/detail/output/fancy_serializer.hpp index c210992b9..ee6a71d28 100644 --- a/include/nlohmann/detail/output/fancy_serializer.hpp +++ b/include/nlohmann/detail/output/fancy_serializer.hpp @@ -35,12 +35,13 @@ struct fancy_serializer_style unsigned int strings_maximum_length = 0; bool space_after_colon = false; + bool space_after_comma = false; bool multiline = false; void set_old_multiline() { - space_after_colon = multiline = true; + space_after_colon = space_after_comma = multiline = true; } }; @@ -298,6 +299,12 @@ class fancy_serializer } const int newline_len = (active_style->multiline ? 1 : 0); + using pair = std::pair; + auto comma_string = + active_style->multiline ? pair(",\n", 2) : + active_style->space_after_comma ? pair(", ", 2) : + pair(",", 1); + o->write_characters("[\n", 1 + newline_len); // first n-1 elements @@ -306,7 +313,7 @@ class fancy_serializer { o->write_characters(indent_string.c_str(), new_indent); dump(*i, ensure_ascii, depth + 1, active_style); - o->write_characters(",\n", 1 + newline_len); + o->write_characters(comma_string.first, comma_string.second); } // last element diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 5b78a676e..c1ede78ab 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -10105,12 +10105,13 @@ struct fancy_serializer_style unsigned int strings_maximum_length = 0; bool space_after_colon = false; + bool space_after_comma = false; bool multiline = false; void set_old_multiline() { - space_after_colon = multiline = true; + space_after_colon = space_after_comma = multiline = true; } }; @@ -10368,6 +10369,12 @@ class fancy_serializer } const int newline_len = (active_style->multiline ? 1 : 0); + using pair = std::pair; + auto comma_string = + active_style->multiline ? pair(",\n", 2) : + active_style->space_after_comma ? pair(", ", 2) : + pair(",", 1); + o->write_characters("[\n", 1 + newline_len); // first n-1 elements @@ -10376,7 +10383,7 @@ class fancy_serializer { o->write_characters(indent_string.c_str(), new_indent); dump(*i, ensure_ascii, depth + 1, active_style); - o->write_characters(",\n", 1 + newline_len); + o->write_characters(comma_string.first, comma_string.second); } // last element diff --git a/test/src/unit-fancy-serialization.cpp b/test/src/unit-fancy-serialization.cpp index 9c13177a3..9e350678d 100644 --- a/test/src/unit-fancy-serialization.cpp +++ b/test/src/unit-fancy-serialization.cpp @@ -280,6 +280,14 @@ TEST_CASE("serialization") SECTION("Spaces after commas are controllable separately from multiline") { + SECTION("commas") + { + fancy_serializer_style style; + style.space_after_comma = true; + auto str = fancy_to_string({1, 2, 3}, style); + CHECK(str == "[1, 2, 3]"); + } + SECTION("colons") { fancy_serializer_style style; From 6c746460c64591519beb01d7c84bd0fcb1f0ae91 Mon Sep 17 00:00:00 2001 From: Evan Driscoll Date: Sun, 3 Jun 2018 21:51:45 -0500 Subject: [PATCH 28/41] Add missing header file --- include/nlohmann/detail/output/fancy_serializer.hpp | 1 + single_include/nlohmann/json.hpp | 1 + 2 files changed, 2 insertions(+) diff --git a/include/nlohmann/detail/output/fancy_serializer.hpp b/include/nlohmann/detail/output/fancy_serializer.hpp index ee6a71d28..684a0852f 100644 --- a/include/nlohmann/detail/output/fancy_serializer.hpp +++ b/include/nlohmann/detail/output/fancy_serializer.hpp @@ -13,6 +13,7 @@ #include // string #include // is_same #include +#include #include #include diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index c1ede78ab..aef95084a 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -10076,6 +10076,7 @@ class serializer #include // string #include // is_same #include +#include // #include From 91971e394fec2b210fafc9e5cd13250f8e0229bf Mon Sep 17 00:00:00 2001 From: Evan Driscoll Date: Sun, 3 Jun 2018 21:52:28 -0500 Subject: [PATCH 29/41] Provide accessors into json_pointer --- include/nlohmann/detail/json_pointer.hpp | 35 ++++++++++++++++++++ single_include/nlohmann/json.hpp | 35 ++++++++++++++++++++ test/src/unit-json_pointer.cpp | 41 ++++++++++++++++++++++++ 3 files changed, 111 insertions(+) diff --git a/include/nlohmann/detail/json_pointer.hpp b/include/nlohmann/detail/json_pointer.hpp index fce8001a5..e183ff968 100644 --- a/include/nlohmann/detail/json_pointer.hpp +++ b/include/nlohmann/detail/json_pointer.hpp @@ -19,6 +19,9 @@ class json_pointer friend class basic_json; public: + typedef std::vector::const_iterator const_iterator; + typedef std::vector::const_reverse_iterator const_reverse_iterator; + /*! @brief create JSON pointer @@ -75,6 +78,38 @@ class json_pointer return to_string(); } + const_iterator cbegin() const + { + return reference_tokens.cbegin(); + } + + const_iterator cend() const + { + return reference_tokens.cend(); + } + + const_reverse_iterator crbegin() const + { + return reference_tokens.crbegin(); + } + + const_reverse_iterator crend() const + { + return reference_tokens.crend(); + } + + json_pointer appended(std::string const& next) const + { + json_pointer copy(*this); + copy.reference_tokens.push_back(next); + return copy; + } + + json_pointer appended(size_t next) const + { + return appended(std::to_string(next)); + } + /*! @param[in] s reference token to be converted into an array index diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index aef95084a..22c58bc18 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -10585,6 +10585,9 @@ class json_pointer friend class basic_json; public: + typedef std::vector::const_iterator const_iterator; + typedef std::vector::const_reverse_iterator const_reverse_iterator; + /*! @brief create JSON pointer @@ -10641,6 +10644,38 @@ class json_pointer return to_string(); } + const_iterator cbegin() const + { + return reference_tokens.cbegin(); + } + + const_iterator cend() const + { + return reference_tokens.cend(); + } + + const_reverse_iterator crbegin() const + { + return reference_tokens.crbegin(); + } + + const_reverse_iterator crend() const + { + return reference_tokens.crend(); + } + + json_pointer appended(std::string const& next) const + { + json_pointer copy(*this); + copy.reference_tokens.push_back(next); + return copy; + } + + json_pointer appended(size_t next) const + { + return appended(std::to_string(next)); + } + /*! @param[in] s reference token to be converted into an array index diff --git a/test/src/unit-json_pointer.cpp b/test/src/unit-json_pointer.cpp index ec49e1147..0ecfcb47a 100644 --- a/test/src/unit-json_pointer.cpp +++ b/test/src/unit-json_pointer.cpp @@ -441,6 +441,47 @@ TEST_CASE("JSON pointers") } } + SECTION("iterator access") + { + SECTION("forward iterator access") + { + auto ptr = "/foo/bar/c%d"_json_pointer; + std::vector components(ptr.cbegin(), ptr.cend()); + CHECK(components.size() == 3u); + CHECK(components[0] == "foo"); + CHECK(components[1] == "bar"); + CHECK(components[2] == "c%d"); + } + + SECTION("reverse iterator access") + { + auto ptr = "/foo/bar/c%d"_json_pointer; + std::vector rcomponents(ptr.crbegin(), ptr.crend()); + CHECK(rcomponents.size() == 3u); + CHECK(rcomponents[2] == "foo"); + CHECK(rcomponents[1] == "bar"); + CHECK(rcomponents[0] == "c%d"); + } + } + + SECTION("appending") + { + SECTION("can append to empty string pointer") + { + auto ptr = ""_json_pointer; + CHECK(ptr.to_string() == ""); + + ptr = ptr.appended("foo"); + CHECK(ptr.to_string() == "/foo"); + + ptr = ptr.appended("bar"); + CHECK(ptr.to_string() == "/foo/bar"); + + ptr = ptr.appended(10); + CHECK(ptr.to_string() == "/foo/bar/10"); + } + } + SECTION("conversion") { SECTION("array") From ca3f3959a8682f322e8adfbd74ce7eb3f050b08f Mon Sep 17 00:00:00 2001 From: Evan Driscoll Date: Sun, 3 Jun 2018 22:02:07 -0500 Subject: [PATCH 30/41] Refactor: fancy_serializer tracks a json_pointer context --- .../detail/output/fancy_serializer.hpp | 38 +- single_include/nlohmann/json.hpp | 949 +++++++++--------- 2 files changed, 506 insertions(+), 481 deletions(-) diff --git a/include/nlohmann/detail/output/fancy_serializer.hpp b/include/nlohmann/detail/output/fancy_serializer.hpp index 684a0852f..e9a2b7c67 100644 --- a/include/nlohmann/detail/output/fancy_serializer.hpp +++ b/include/nlohmann/detail/output/fancy_serializer.hpp @@ -22,6 +22,7 @@ #include #include #include +#include namespace nlohmann { @@ -103,6 +104,7 @@ class fancy_serializer using number_float_t = typename BasicJsonType::number_float_t; using number_integer_t = typename BasicJsonType::number_integer_t; using number_unsigned_t = typename BasicJsonType::number_unsigned_t; + using json_pointer_t = json_pointer; static constexpr uint8_t UTF8_ACCEPT = 0; static constexpr uint8_t UTF8_REJECT = 1; @@ -123,7 +125,7 @@ class fancy_serializer void dump(const BasicJsonType& val, const bool ensure_ascii) { - dump(val, ensure_ascii, 0, &stylizer.get_default_style()); + dump(val, ensure_ascii, 0, &stylizer.get_default_style(), json_pointer_t()); } private: @@ -146,19 +148,25 @@ class fancy_serializer void dump(const BasicJsonType& val, const bool ensure_ascii, const unsigned int depth, - const fancy_serializer_style* active_style) + const fancy_serializer_style* active_style, + const json_pointer_t& context) { + if (context.cbegin() != context.cend()) + { + active_style = stylizer.get_new_style_or_active(*context.crbegin(), active_style); + } + switch (val.m_type) { case value_t::object: { - dump_object(val, ensure_ascii, depth, active_style); + dump_object(val, ensure_ascii, depth, active_style, context); return; } case value_t::array: { - dump_array(val, ensure_ascii, depth, active_style); + dump_array(val, ensure_ascii, depth, active_style, context); return; } @@ -217,7 +225,8 @@ class fancy_serializer template void dump_object_key_value( Iterator i, bool ensure_ascii, unsigned int depth, - const fancy_serializer_style* active_style) + const fancy_serializer_style* active_style, + const json_pointer_t& context) { const auto new_indent = (depth + 1) * active_style->indent_step * active_style->multiline; const int newline_len = active_style->space_after_colon; @@ -226,14 +235,14 @@ class fancy_serializer o->write_character('\"'); prim_serializer.dump_escaped(*o, i->first, ensure_ascii); o->write_characters("\": ", 2 + newline_len); - auto new_style = stylizer.get_new_style_or_active(i->first, active_style); - dump(i->second, ensure_ascii, depth + 1, new_style); + dump(i->second, ensure_ascii, depth + 1, active_style, context.appended(i->first)); } void dump_object(const BasicJsonType& val, bool ensure_ascii, unsigned int depth, - const fancy_serializer_style* active_style) + const fancy_serializer_style* active_style, + const json_pointer_t& context) { if (val.m_value.object->empty()) { @@ -261,14 +270,14 @@ class fancy_serializer auto i = val.m_value.object->cbegin(); for (std::size_t cnt = 0; cnt < val.m_value.object->size() - 1; ++cnt, ++i) { - dump_object_key_value(i, ensure_ascii, depth, active_style); + dump_object_key_value(i, ensure_ascii, depth, active_style, context); o->write_characters(",\n", 1 + newline_len); } // last element assert(i != val.m_value.object->cend()); assert(std::next(i) == val.m_value.object->cend()); - dump_object_key_value(i, ensure_ascii, depth, active_style); + dump_object_key_value(i, ensure_ascii, depth, active_style, context); o->write_characters("\n", newline_len); o->write_characters(indent_string.c_str(), old_indent); @@ -278,7 +287,8 @@ class fancy_serializer void dump_array(const BasicJsonType& val, bool ensure_ascii, unsigned int depth, - const fancy_serializer_style* active_style) + const fancy_serializer_style* active_style, + const json_pointer_t& context) { if (val.m_value.array->empty()) { @@ -313,14 +323,16 @@ class fancy_serializer i != val.m_value.array->cend() - 1; ++i) { o->write_characters(indent_string.c_str(), new_indent); - dump(*i, ensure_ascii, depth + 1, active_style); + dump(*i, ensure_ascii, depth + 1, active_style, + context.appended(i - val.m_value.array->cbegin())); o->write_characters(comma_string.first, comma_string.second); } // last element assert(not val.m_value.array->empty()); o->write_characters(indent_string.c_str(), new_indent); - dump(val.m_value.array->back(), ensure_ascii, depth + 1, active_style); + dump(val.m_value.array->back(), ensure_ascii, depth + 1, active_style, + context.appended(val.m_value.array->size())); o->write_characters("\n", newline_len); o->write_characters(indent_string.c_str(), old_indent); diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 22c58bc18..418b36e76 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -10092,474 +10092,6 @@ class serializer // #include - -namespace nlohmann -{ - -struct fancy_serializer_style -{ - unsigned int indent_step = 4; - char indent_char = ' '; - - unsigned int depth_limit = std::numeric_limits::max(); - - unsigned int strings_maximum_length = 0; - - bool space_after_colon = false; - bool space_after_comma = false; - - bool multiline = false; - - void set_old_multiline() - { - space_after_colon = space_after_comma = multiline = true; - } -}; - -template -class basic_fancy_serializer_stylizer -{ - public: - using string_t = typename BasicJsonType::string_t; - - basic_fancy_serializer_stylizer(fancy_serializer_style const& ds) - : default_style(ds) - {} - - basic_fancy_serializer_stylizer() = default; - - public: - const fancy_serializer_style& get_default_style() const - { - return default_style; - } - - fancy_serializer_style& get_default_style() - { - return default_style; - } - - const fancy_serializer_style* get_new_style_or_active( - const string_t& j, - const fancy_serializer_style* active_style) const - { - auto iter = key_styles.find(j); - return iter == key_styles.end() ? active_style : &iter->second; - } - - fancy_serializer_style& get_or_insert_style(const string_t& j) - { - return key_styles[j]; - } - - private: - fancy_serializer_style default_style; - - std::map key_styles; -}; - -namespace detail -{ -/////////////////// -// serialization // -/////////////////// - -template -class fancy_serializer -{ - using stylizer_t = basic_fancy_serializer_stylizer; - using primitive_serializer_t = primitive_serializer; - using string_t = typename BasicJsonType::string_t; - using number_float_t = typename BasicJsonType::number_float_t; - using number_integer_t = typename BasicJsonType::number_integer_t; - using number_unsigned_t = typename BasicJsonType::number_unsigned_t; - static constexpr uint8_t UTF8_ACCEPT = 0; - static constexpr uint8_t UTF8_REJECT = 1; - - public: - /*! - @param[in] s output stream to serialize to - @param[in] ichar indentation character to use - */ - fancy_serializer(output_adapter_t s, - const stylizer_t& st) - : o(std::move(s)), stylizer(st), - indent_string(512, st.get_default_style().indent_char) - {} - - // delete because of pointer members - fancy_serializer(const fancy_serializer&) = delete; - fancy_serializer& operator=(const fancy_serializer&) = delete; - - void dump(const BasicJsonType& val, const bool ensure_ascii) - { - dump(val, ensure_ascii, 0, &stylizer.get_default_style()); - } - - private: - /*! - @brief internal implementation of the serialization function - - This function is called by the public member function dump and organizes - the serialization internally. The indentation level is propagated as - additional parameter. In case of arrays and objects, the function is - called recursively. - - - strings and object keys are escaped using `escape_string()` - - integer numbers are converted implicitly via `operator<<` - - floating-point numbers are converted to a string using `"%g"` format - - @param[in] val value to serialize - @param[in] pretty_print whether the output shall be pretty-printed - @param[in] depth the current recursive depth - */ - void dump(const BasicJsonType& val, - const bool ensure_ascii, - const unsigned int depth, - const fancy_serializer_style* active_style) - { - switch (val.m_type) - { - case value_t::object: - { - dump_object(val, ensure_ascii, depth, active_style); - return; - } - - case value_t::array: - { - dump_array(val, ensure_ascii, depth, active_style); - return; - } - - case value_t::string: - { - dump_string(*val.m_value.string, ensure_ascii, active_style); - return; - } - - case value_t::boolean: - { - if (val.m_value.boolean) - { - o->write_characters("true", 4); - } - else - { - o->write_characters("false", 5); - } - return; - } - - case value_t::number_integer: - { - prim_serializer.dump_integer(*o, val.m_value.number_integer); - return; - } - - case value_t::number_unsigned: - { - prim_serializer.dump_integer(*o, val.m_value.number_unsigned); - return; - } - - case value_t::number_float: - { - prim_serializer.dump_float(*o, val.m_value.number_float); - return; - } - - case value_t::discarded: - { - o->write_characters("", 11); - return; - } - - case value_t::null: - { - o->write_characters("null", 4); - return; - } - } - } - - private: - template - void dump_object_key_value( - Iterator i, bool ensure_ascii, unsigned int depth, - const fancy_serializer_style* active_style) - { - const auto new_indent = (depth + 1) * active_style->indent_step * active_style->multiline; - const int newline_len = active_style->space_after_colon; - - o->write_characters(indent_string.c_str(), new_indent); - o->write_character('\"'); - prim_serializer.dump_escaped(*o, i->first, ensure_ascii); - o->write_characters("\": ", 2 + newline_len); - auto new_style = stylizer.get_new_style_or_active(i->first, active_style); - dump(i->second, ensure_ascii, depth + 1, new_style); - } - - void dump_object(const BasicJsonType& val, - bool ensure_ascii, - unsigned int depth, - const fancy_serializer_style* active_style) - { - if (val.m_value.object->empty()) - { - o->write_characters("{}", 2); - return; - } - else if (depth >= active_style->depth_limit) - { - o->write_characters("{...}", 5); - return; - } - - // variable to hold indentation for recursive calls - const auto old_indent = depth * active_style->indent_step * active_style->multiline; - const auto new_indent = (depth + 1) * active_style->indent_step * active_style->multiline; - if (JSON_UNLIKELY(indent_string.size() < new_indent)) - { - indent_string.resize(indent_string.size() * 2, active_style->indent_char); - } - const int newline_len = (active_style->multiline ? 1 : 0); - - o->write_characters("{\n", 1 + newline_len); - - // first n-1 elements - auto i = val.m_value.object->cbegin(); - for (std::size_t cnt = 0; cnt < val.m_value.object->size() - 1; ++cnt, ++i) - { - dump_object_key_value(i, ensure_ascii, depth, active_style); - o->write_characters(",\n", 1 + newline_len); - } - - // last element - assert(i != val.m_value.object->cend()); - assert(std::next(i) == val.m_value.object->cend()); - dump_object_key_value(i, ensure_ascii, depth, active_style); - - o->write_characters("\n", newline_len); - o->write_characters(indent_string.c_str(), old_indent); - o->write_character('}'); - } - - void dump_array(const BasicJsonType& val, - bool ensure_ascii, - unsigned int depth, - const fancy_serializer_style* active_style) - { - if (val.m_value.array->empty()) - { - o->write_characters("[]", 2); - return; - } - else if (depth >= active_style->depth_limit) - { - o->write_characters("[...]", 5); - return; - } - - // variable to hold indentation for recursive calls - const auto old_indent = depth * active_style->indent_step * active_style->multiline;; - const auto new_indent = (depth + 1) * active_style->indent_step * active_style->multiline;; - if (JSON_UNLIKELY(indent_string.size() < new_indent)) - { - indent_string.resize(indent_string.size() * 2, active_style->indent_char); - } - const int newline_len = (active_style->multiline ? 1 : 0); - - using pair = std::pair; - auto comma_string = - active_style->multiline ? pair(",\n", 2) : - active_style->space_after_comma ? pair(", ", 2) : - pair(",", 1); - - o->write_characters("[\n", 1 + newline_len); - - // first n-1 elements - for (auto i = val.m_value.array->cbegin(); - i != val.m_value.array->cend() - 1; ++i) - { - o->write_characters(indent_string.c_str(), new_indent); - dump(*i, ensure_ascii, depth + 1, active_style); - o->write_characters(comma_string.first, comma_string.second); - } - - // last element - assert(not val.m_value.array->empty()); - o->write_characters(indent_string.c_str(), new_indent); - dump(val.m_value.array->back(), ensure_ascii, depth + 1, active_style); - - o->write_characters("\n", newline_len); - o->write_characters(indent_string.c_str(), old_indent); - o->write_character(']'); - } - - void dump_string(const string_t& str, bool ensure_ascii, - const fancy_serializer_style* active_style) - { - o->write_character('\"'); - if (active_style->strings_maximum_length == 0) - { - prim_serializer.dump_escaped(*o, str, ensure_ascii); - } - else - { - std::stringstream ss; - nlohmann::detail::output_adapter o_string(ss); - nlohmann::detail::output_adapter_t oo_string = o_string; - prim_serializer.dump_escaped(*oo_string, str, ensure_ascii); - - std::string full_str = ss.str(); - if (full_str.size() <= active_style->strings_maximum_length) - { - o->write_characters(full_str.c_str(), full_str.size()); - } - else - { - const unsigned start_len = [](unsigned int maxl) - { - if (maxl <= 3) - { - // There is only room for the ellipsis, - // no characters from the string - return 0u; - } - else if (maxl <= 5) - { - // With four allowed characters, we add in the - // first from the string. With five, we add in - // the *last* instead, so still just one at - // the start. - return 1u; - } - else - { - // We subtract three for the ellipsis - // and one for the last character. - return maxl - 4; - } - }(active_style->strings_maximum_length); - - const unsigned end_len = - active_style->strings_maximum_length >= 5 ? 1 : 0; - - const unsigned ellipsis_length = - active_style->strings_maximum_length >= 3 - ? 3 - : active_style->strings_maximum_length; - - o->write_characters(full_str.c_str(), start_len); - o->write_characters("...", ellipsis_length); - o->write_characters(full_str.c_str() + str.size() - end_len, end_len); - } - } - o->write_character('\"'); - } - - private: - /// the output of the fancy_serializer - output_adapter_t o = nullptr; - - /// Used for serializing "base" objects. Strings are sort of - /// counted in this, but not completely. - primitive_serializer_t prim_serializer; - - /// the indentation string - string_t indent_string; - - /// Output style - const stylizer_t stylizer; -}; -} - -template -std::ostream& fancy_dump(std::ostream& o, const BasicJsonType& j, - basic_fancy_serializer_stylizer const& stylizer) -{ - // do the actual serialization - detail::fancy_serializer s(detail::output_adapter(o), stylizer); - s.dump(j, false); - return o; -} - -template -std::ostream& fancy_dump(std::ostream& o, const BasicJsonType& j, - fancy_serializer_style style) -{ - basic_fancy_serializer_stylizer stylizer(style); - return fancy_dump(o, j, stylizer); -} - -} - -// #include - - -#include -#include - -namespace nlohmann -{ -namespace detail -{ -template -class json_ref -{ - public: - using value_type = BasicJsonType; - - json_ref(value_type&& value) - : owned_value(std::move(value)), value_ref(&owned_value), is_rvalue(true) - {} - - json_ref(const value_type& value) - : value_ref(const_cast(&value)), is_rvalue(false) - {} - - json_ref(std::initializer_list init) - : owned_value(init), value_ref(&owned_value), is_rvalue(true) - {} - - template - json_ref(Args&& ... args) - : owned_value(std::forward(args)...), value_ref(&owned_value), is_rvalue(true) - {} - - // class should be movable only - json_ref(json_ref&&) = default; - json_ref(const json_ref&) = delete; - json_ref& operator=(const json_ref&) = delete; - - value_type moved_or_copied() const - { - if (is_rvalue) - { - return std::move(*value_ref); - } - return *value_ref; - } - - value_type const& operator*() const - { - return *static_cast(value_ref); - } - - value_type const* operator->() const - { - return static_cast(value_ref); - } - - private: - mutable value_type owned_value = nullptr; - value_type* value_ref = nullptr; - const bool is_rvalue; -}; -} -} - // #include @@ -11296,6 +10828,487 @@ class json_pointer }; } + +namespace nlohmann +{ + +struct fancy_serializer_style +{ + unsigned int indent_step = 4; + char indent_char = ' '; + + unsigned int depth_limit = std::numeric_limits::max(); + + unsigned int strings_maximum_length = 0; + + bool space_after_colon = false; + bool space_after_comma = false; + + bool multiline = false; + + void set_old_multiline() + { + space_after_colon = space_after_comma = multiline = true; + } +}; + +template +class basic_fancy_serializer_stylizer +{ + public: + using string_t = typename BasicJsonType::string_t; + + basic_fancy_serializer_stylizer(fancy_serializer_style const& ds) + : default_style(ds) + {} + + basic_fancy_serializer_stylizer() = default; + + public: + const fancy_serializer_style& get_default_style() const + { + return default_style; + } + + fancy_serializer_style& get_default_style() + { + return default_style; + } + + const fancy_serializer_style* get_new_style_or_active( + const string_t& j, + const fancy_serializer_style* active_style) const + { + auto iter = key_styles.find(j); + return iter == key_styles.end() ? active_style : &iter->second; + } + + fancy_serializer_style& get_or_insert_style(const string_t& j) + { + return key_styles[j]; + } + + private: + fancy_serializer_style default_style; + + std::map key_styles; +}; + +namespace detail +{ +/////////////////// +// serialization // +/////////////////// + +template +class fancy_serializer +{ + using stylizer_t = basic_fancy_serializer_stylizer; + using primitive_serializer_t = primitive_serializer; + using string_t = typename BasicJsonType::string_t; + using number_float_t = typename BasicJsonType::number_float_t; + using number_integer_t = typename BasicJsonType::number_integer_t; + using number_unsigned_t = typename BasicJsonType::number_unsigned_t; + using json_pointer_t = json_pointer; + static constexpr uint8_t UTF8_ACCEPT = 0; + static constexpr uint8_t UTF8_REJECT = 1; + + public: + /*! + @param[in] s output stream to serialize to + @param[in] ichar indentation character to use + */ + fancy_serializer(output_adapter_t s, + const stylizer_t& st) + : o(std::move(s)), stylizer(st), + indent_string(512, st.get_default_style().indent_char) + {} + + // delete because of pointer members + fancy_serializer(const fancy_serializer&) = delete; + fancy_serializer& operator=(const fancy_serializer&) = delete; + + void dump(const BasicJsonType& val, const bool ensure_ascii) + { + dump(val, ensure_ascii, 0, &stylizer.get_default_style(), json_pointer_t()); + } + + private: + /*! + @brief internal implementation of the serialization function + + This function is called by the public member function dump and organizes + the serialization internally. The indentation level is propagated as + additional parameter. In case of arrays and objects, the function is + called recursively. + + - strings and object keys are escaped using `escape_string()` + - integer numbers are converted implicitly via `operator<<` + - floating-point numbers are converted to a string using `"%g"` format + + @param[in] val value to serialize + @param[in] pretty_print whether the output shall be pretty-printed + @param[in] depth the current recursive depth + */ + void dump(const BasicJsonType& val, + const bool ensure_ascii, + const unsigned int depth, + const fancy_serializer_style* active_style, + const json_pointer_t& context) + { + if (context.cbegin() != context.cend()) + { + active_style = stylizer.get_new_style_or_active(*context.crbegin(), active_style); + } + + switch (val.m_type) + { + case value_t::object: + { + dump_object(val, ensure_ascii, depth, active_style, context); + return; + } + + case value_t::array: + { + dump_array(val, ensure_ascii, depth, active_style, context); + return; + } + + case value_t::string: + { + dump_string(*val.m_value.string, ensure_ascii, active_style); + return; + } + + case value_t::boolean: + { + if (val.m_value.boolean) + { + o->write_characters("true", 4); + } + else + { + o->write_characters("false", 5); + } + return; + } + + case value_t::number_integer: + { + prim_serializer.dump_integer(*o, val.m_value.number_integer); + return; + } + + case value_t::number_unsigned: + { + prim_serializer.dump_integer(*o, val.m_value.number_unsigned); + return; + } + + case value_t::number_float: + { + prim_serializer.dump_float(*o, val.m_value.number_float); + return; + } + + case value_t::discarded: + { + o->write_characters("", 11); + return; + } + + case value_t::null: + { + o->write_characters("null", 4); + return; + } + } + } + + private: + template + void dump_object_key_value( + Iterator i, bool ensure_ascii, unsigned int depth, + const fancy_serializer_style* active_style, + const json_pointer_t& context) + { + const auto new_indent = (depth + 1) * active_style->indent_step * active_style->multiline; + const int newline_len = active_style->space_after_colon; + + o->write_characters(indent_string.c_str(), new_indent); + o->write_character('\"'); + prim_serializer.dump_escaped(*o, i->first, ensure_ascii); + o->write_characters("\": ", 2 + newline_len); + dump(i->second, ensure_ascii, depth + 1, active_style, context.appended(i->first)); + } + + void dump_object(const BasicJsonType& val, + bool ensure_ascii, + unsigned int depth, + const fancy_serializer_style* active_style, + const json_pointer_t& context) + { + if (val.m_value.object->empty()) + { + o->write_characters("{}", 2); + return; + } + else if (depth >= active_style->depth_limit) + { + o->write_characters("{...}", 5); + return; + } + + // variable to hold indentation for recursive calls + const auto old_indent = depth * active_style->indent_step * active_style->multiline; + const auto new_indent = (depth + 1) * active_style->indent_step * active_style->multiline; + if (JSON_UNLIKELY(indent_string.size() < new_indent)) + { + indent_string.resize(indent_string.size() * 2, active_style->indent_char); + } + const int newline_len = (active_style->multiline ? 1 : 0); + + o->write_characters("{\n", 1 + newline_len); + + // first n-1 elements + auto i = val.m_value.object->cbegin(); + for (std::size_t cnt = 0; cnt < val.m_value.object->size() - 1; ++cnt, ++i) + { + dump_object_key_value(i, ensure_ascii, depth, active_style, context); + o->write_characters(",\n", 1 + newline_len); + } + + // last element + assert(i != val.m_value.object->cend()); + assert(std::next(i) == val.m_value.object->cend()); + dump_object_key_value(i, ensure_ascii, depth, active_style, context); + + o->write_characters("\n", newline_len); + o->write_characters(indent_string.c_str(), old_indent); + o->write_character('}'); + } + + void dump_array(const BasicJsonType& val, + bool ensure_ascii, + unsigned int depth, + const fancy_serializer_style* active_style, + const json_pointer_t& context) + { + if (val.m_value.array->empty()) + { + o->write_characters("[]", 2); + return; + } + else if (depth >= active_style->depth_limit) + { + o->write_characters("[...]", 5); + return; + } + + // variable to hold indentation for recursive calls + const auto old_indent = depth * active_style->indent_step * active_style->multiline;; + const auto new_indent = (depth + 1) * active_style->indent_step * active_style->multiline;; + if (JSON_UNLIKELY(indent_string.size() < new_indent)) + { + indent_string.resize(indent_string.size() * 2, active_style->indent_char); + } + const int newline_len = (active_style->multiline ? 1 : 0); + + using pair = std::pair; + auto comma_string = + active_style->multiline ? pair(",\n", 2) : + active_style->space_after_comma ? pair(", ", 2) : + pair(",", 1); + + o->write_characters("[\n", 1 + newline_len); + + // first n-1 elements + for (auto i = val.m_value.array->cbegin(); + i != val.m_value.array->cend() - 1; ++i) + { + o->write_characters(indent_string.c_str(), new_indent); + dump(*i, ensure_ascii, depth + 1, active_style, + context.appended(i - val.m_value.array->cbegin())); + o->write_characters(comma_string.first, comma_string.second); + } + + // last element + assert(not val.m_value.array->empty()); + o->write_characters(indent_string.c_str(), new_indent); + dump(val.m_value.array->back(), ensure_ascii, depth + 1, active_style, + context.appended(val.m_value.array->size())); + + o->write_characters("\n", newline_len); + o->write_characters(indent_string.c_str(), old_indent); + o->write_character(']'); + } + + void dump_string(const string_t& str, bool ensure_ascii, + const fancy_serializer_style* active_style) + { + o->write_character('\"'); + if (active_style->strings_maximum_length == 0) + { + prim_serializer.dump_escaped(*o, str, ensure_ascii); + } + else + { + std::stringstream ss; + nlohmann::detail::output_adapter o_string(ss); + nlohmann::detail::output_adapter_t oo_string = o_string; + prim_serializer.dump_escaped(*oo_string, str, ensure_ascii); + + std::string full_str = ss.str(); + if (full_str.size() <= active_style->strings_maximum_length) + { + o->write_characters(full_str.c_str(), full_str.size()); + } + else + { + const unsigned start_len = [](unsigned int maxl) + { + if (maxl <= 3) + { + // There is only room for the ellipsis, + // no characters from the string + return 0u; + } + else if (maxl <= 5) + { + // With four allowed characters, we add in the + // first from the string. With five, we add in + // the *last* instead, so still just one at + // the start. + return 1u; + } + else + { + // We subtract three for the ellipsis + // and one for the last character. + return maxl - 4; + } + }(active_style->strings_maximum_length); + + const unsigned end_len = + active_style->strings_maximum_length >= 5 ? 1 : 0; + + const unsigned ellipsis_length = + active_style->strings_maximum_length >= 3 + ? 3 + : active_style->strings_maximum_length; + + o->write_characters(full_str.c_str(), start_len); + o->write_characters("...", ellipsis_length); + o->write_characters(full_str.c_str() + str.size() - end_len, end_len); + } + } + o->write_character('\"'); + } + + private: + /// the output of the fancy_serializer + output_adapter_t o = nullptr; + + /// Used for serializing "base" objects. Strings are sort of + /// counted in this, but not completely. + primitive_serializer_t prim_serializer; + + /// the indentation string + string_t indent_string; + + /// Output style + const stylizer_t stylizer; +}; +} + +template +std::ostream& fancy_dump(std::ostream& o, const BasicJsonType& j, + basic_fancy_serializer_stylizer const& stylizer) +{ + // do the actual serialization + detail::fancy_serializer s(detail::output_adapter(o), stylizer); + s.dump(j, false); + return o; +} + +template +std::ostream& fancy_dump(std::ostream& o, const BasicJsonType& j, + fancy_serializer_style style) +{ + basic_fancy_serializer_stylizer stylizer(style); + return fancy_dump(o, j, stylizer); +} + +} + +// #include + + +#include +#include + +namespace nlohmann +{ +namespace detail +{ +template +class json_ref +{ + public: + using value_type = BasicJsonType; + + json_ref(value_type&& value) + : owned_value(std::move(value)), value_ref(&owned_value), is_rvalue(true) + {} + + json_ref(const value_type& value) + : value_ref(const_cast(&value)), is_rvalue(false) + {} + + json_ref(std::initializer_list init) + : owned_value(init), value_ref(&owned_value), is_rvalue(true) + {} + + template + json_ref(Args&& ... args) + : owned_value(std::forward(args)...), value_ref(&owned_value), is_rvalue(true) + {} + + // class should be movable only + json_ref(json_ref&&) = default; + json_ref(const json_ref&) = delete; + json_ref& operator=(const json_ref&) = delete; + + value_type moved_or_copied() const + { + if (is_rvalue) + { + return std::move(*value_ref); + } + return *value_ref; + } + + value_type const& operator*() const + { + return *static_cast(value_ref); + } + + value_type const* operator->() const + { + return static_cast(value_ref); + } + + private: + mutable value_type owned_value = nullptr; + value_type* value_ref = nullptr; + const bool is_rvalue; +}; +} +} + +// #include + // #include From d0eb2f21d3d05fc867657bb9eed906cff705a671 Mon Sep 17 00:00:00 2001 From: Evan Driscoll Date: Sun, 3 Jun 2018 22:06:27 -0500 Subject: [PATCH 31/41] Refactor: fancy_serializer passes json_pointer to stylizer --- .../detail/output/fancy_serializer.hpp | 20 ++++++++++--------- single_include/nlohmann/json.hpp | 20 ++++++++++--------- 2 files changed, 22 insertions(+), 18 deletions(-) diff --git a/include/nlohmann/detail/output/fancy_serializer.hpp b/include/nlohmann/detail/output/fancy_serializer.hpp index e9a2b7c67..354fb2940 100644 --- a/include/nlohmann/detail/output/fancy_serializer.hpp +++ b/include/nlohmann/detail/output/fancy_serializer.hpp @@ -52,6 +52,7 @@ class basic_fancy_serializer_stylizer { public: using string_t = typename BasicJsonType::string_t; + using json_pointer_t = json_pointer; basic_fancy_serializer_stylizer(fancy_serializer_style const& ds) : default_style(ds) @@ -71,16 +72,20 @@ class basic_fancy_serializer_stylizer } const fancy_serializer_style* get_new_style_or_active( - const string_t& j, + const json_pointer_t& pointer, const fancy_serializer_style* active_style) const { - auto iter = key_styles.find(j); + if (pointer.cbegin() == pointer.cend()) + { + return &get_default_style(); + } + auto iter = key_styles.find(*pointer.crbegin()); return iter == key_styles.end() ? active_style : &iter->second; } - fancy_serializer_style& get_or_insert_style(const string_t& j) + fancy_serializer_style& get_or_insert_style(const string_t& key) { - return key_styles[j]; + return key_styles[key]; } private: @@ -125,7 +130,7 @@ class fancy_serializer void dump(const BasicJsonType& val, const bool ensure_ascii) { - dump(val, ensure_ascii, 0, &stylizer.get_default_style(), json_pointer_t()); + dump(val, ensure_ascii, 0, nullptr, json_pointer_t()); } private: @@ -151,10 +156,7 @@ class fancy_serializer const fancy_serializer_style* active_style, const json_pointer_t& context) { - if (context.cbegin() != context.cend()) - { - active_style = stylizer.get_new_style_or_active(*context.crbegin(), active_style); - } + active_style = stylizer.get_new_style_or_active(context, active_style); switch (val.m_type) { diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 418b36e76..99c9ae923 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -10857,6 +10857,7 @@ class basic_fancy_serializer_stylizer { public: using string_t = typename BasicJsonType::string_t; + using json_pointer_t = json_pointer; basic_fancy_serializer_stylizer(fancy_serializer_style const& ds) : default_style(ds) @@ -10876,16 +10877,20 @@ class basic_fancy_serializer_stylizer } const fancy_serializer_style* get_new_style_or_active( - const string_t& j, + const json_pointer_t& pointer, const fancy_serializer_style* active_style) const { - auto iter = key_styles.find(j); + if (pointer.cbegin() == pointer.cend()) + { + return &get_default_style(); + } + auto iter = key_styles.find(*pointer.crbegin()); return iter == key_styles.end() ? active_style : &iter->second; } - fancy_serializer_style& get_or_insert_style(const string_t& j) + fancy_serializer_style& get_or_insert_style(const string_t& key) { - return key_styles[j]; + return key_styles[key]; } private: @@ -10930,7 +10935,7 @@ class fancy_serializer void dump(const BasicJsonType& val, const bool ensure_ascii) { - dump(val, ensure_ascii, 0, &stylizer.get_default_style(), json_pointer_t()); + dump(val, ensure_ascii, 0, nullptr, json_pointer_t()); } private: @@ -10956,10 +10961,7 @@ class fancy_serializer const fancy_serializer_style* active_style, const json_pointer_t& context) { - if (context.cbegin() != context.cend()) - { - active_style = stylizer.get_new_style_or_active(*context.crbegin(), active_style); - } + active_style = stylizer.get_new_style_or_active(context, active_style); switch (val.m_type) { From 5f0870ef3489b3a3c98dda5432dd8135a24ef884 Mon Sep 17 00:00:00 2001 From: Evan Driscoll Date: Sun, 3 Jun 2018 22:26:38 -0500 Subject: [PATCH 32/41] Expose the matcher predicate from fancy stylizer --- .../detail/output/fancy_serializer.hpp | 42 +++++++++++++++---- single_include/nlohmann/json.hpp | 42 +++++++++++++++---- test/src/unit-fancy-serialization.cpp | 4 +- 3 files changed, 68 insertions(+), 20 deletions(-) diff --git a/include/nlohmann/detail/output/fancy_serializer.hpp b/include/nlohmann/detail/output/fancy_serializer.hpp index 354fb2940..80a4c441a 100644 --- a/include/nlohmann/detail/output/fancy_serializer.hpp +++ b/include/nlohmann/detail/output/fancy_serializer.hpp @@ -14,6 +14,8 @@ #include // is_same #include #include +#include +#include #include #include @@ -53,6 +55,7 @@ class basic_fancy_serializer_stylizer public: using string_t = typename BasicJsonType::string_t; using json_pointer_t = json_pointer; + using matcher_predicate = std::function; basic_fancy_serializer_stylizer(fancy_serializer_style const& ds) : default_style(ds) @@ -75,23 +78,44 @@ class basic_fancy_serializer_stylizer const json_pointer_t& pointer, const fancy_serializer_style* active_style) const { - if (pointer.cbegin() == pointer.cend()) + for (auto const& pair : styles) { - return &get_default_style(); + if (pair.first(pointer)) + { + return &pair.second; + } } - auto iter = key_styles.find(*pointer.crbegin()); - return iter == key_styles.end() ? active_style : &iter->second; + return active_style; } - fancy_serializer_style& get_or_insert_style(const string_t& key) + fancy_serializer_style& register_style( + matcher_predicate p, + fancy_serializer_style style = fancy_serializer_style()) { - return key_styles[key]; + styles.emplace_back(p, style); + return styles.back().second; + } + + fancy_serializer_style& register_key_matcher_style( + string_t str, + fancy_serializer_style style = fancy_serializer_style()) + { + return register_style([str](const json_pointer_t& pointer) + { + return (pointer.cbegin() != pointer.cend()) + && (*pointer.crbegin() == str); + }, + style); + } + + fancy_serializer_style& last_registered_style() + { + return styles.back().second; } private: fancy_serializer_style default_style; - - std::map key_styles; + std::vector> styles; }; namespace detail @@ -130,7 +154,7 @@ class fancy_serializer void dump(const BasicJsonType& val, const bool ensure_ascii) { - dump(val, ensure_ascii, 0, nullptr, json_pointer_t()); + dump(val, ensure_ascii, 0, &stylizer.get_default_style(), json_pointer_t()); } private: diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 99c9ae923..3165d8129 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -10077,6 +10077,8 @@ class serializer #include // is_same #include #include +#include +#include // #include @@ -10858,6 +10860,7 @@ class basic_fancy_serializer_stylizer public: using string_t = typename BasicJsonType::string_t; using json_pointer_t = json_pointer; + using matcher_predicate = std::function; basic_fancy_serializer_stylizer(fancy_serializer_style const& ds) : default_style(ds) @@ -10880,23 +10883,44 @@ class basic_fancy_serializer_stylizer const json_pointer_t& pointer, const fancy_serializer_style* active_style) const { - if (pointer.cbegin() == pointer.cend()) + for (auto const& pair : styles) { - return &get_default_style(); + if (pair.first(pointer)) + { + return &pair.second; + } } - auto iter = key_styles.find(*pointer.crbegin()); - return iter == key_styles.end() ? active_style : &iter->second; + return active_style; } - fancy_serializer_style& get_or_insert_style(const string_t& key) + fancy_serializer_style& register_style( + matcher_predicate p, + fancy_serializer_style style = fancy_serializer_style()) { - return key_styles[key]; + styles.emplace_back(p, style); + return styles.back().second; + } + + fancy_serializer_style& register_key_matcher_style( + string_t str, + fancy_serializer_style style = fancy_serializer_style()) + { + return register_style([str](const json_pointer_t& pointer) + { + return (pointer.cbegin() != pointer.cend()) + && (*pointer.crbegin() == str); + }, + style); + } + + fancy_serializer_style& last_registered_style() + { + return styles.back().second; } private: fancy_serializer_style default_style; - - std::map key_styles; + std::vector> styles; }; namespace detail @@ -10935,7 +10959,7 @@ class fancy_serializer void dump(const BasicJsonType& val, const bool ensure_ascii) { - dump(val, ensure_ascii, 0, nullptr, json_pointer_t()); + dump(val, ensure_ascii, 0, &stylizer.get_default_style(), json_pointer_t()); } private: diff --git a/test/src/unit-fancy-serialization.cpp b/test/src/unit-fancy-serialization.cpp index 9e350678d..e9e23c6c8 100644 --- a/test/src/unit-fancy-serialization.cpp +++ b/test/src/unit-fancy-serialization.cpp @@ -234,7 +234,7 @@ TEST_CASE("serialization") { fancy_serializer_stylizer stylizer; stylizer.get_default_style().set_old_multiline(); - stylizer.get_or_insert_style("one line").multiline = false; + stylizer.register_key_matcher_style("one line").multiline = false; auto str = fancy_to_string( { @@ -261,7 +261,7 @@ TEST_CASE("serialization") { fancy_serializer_stylizer stylizer; stylizer.get_default_style().set_old_multiline(); - stylizer.get_or_insert_style("one line").indent_step = 0; + stylizer.register_key_matcher_style("one line").indent_step = 0; auto str = fancy_to_string( { From c7a64b8846ec92a0260f4550ec50c563d2824c55 Mon Sep 17 00:00:00 2001 From: Evan Driscoll Date: Sun, 3 Jun 2018 22:41:42 -0500 Subject: [PATCH 33/41] Example of more powerful matcher being useful --- test/src/unit-fancy-serialization.cpp | 48 +++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/test/src/unit-fancy-serialization.cpp b/test/src/unit-fancy-serialization.cpp index e9e23c6c8..5b287f33a 100644 --- a/test/src/unit-fancy-serialization.cpp +++ b/test/src/unit-fancy-serialization.cpp @@ -28,10 +28,12 @@ SOFTWARE. */ #include "catch.hpp" +#include #include using nlohmann::json; +using nlohmann::json_pointer; using nlohmann::fancy_dump; using nlohmann::fancy_serializer_style; using nlohmann::fancy_serializer_stylizer; @@ -276,6 +278,52 @@ TEST_CASE("serialization") "one line": {"still one line":[1,2]} })")); } + + SECTION("example of more sophisticated matcher") + { + fancy_serializer_stylizer stylizer; + stylizer.get_default_style().set_old_multiline(); + + stylizer.register_style( + [] (const json_pointer& context) + { + // Matches if context[-2] is "each elem on one line" + return (context.cend() - context.cbegin() >= 2) + && (*(context.cend() - 2) == "each elem on one line"); + } + ).space_after_comma = true; + + auto str = fancy_to_string( + { + { + "each elem on one line", { + {1, 2, 3, 4, 5}, + {1, 2, 3, 4, 5} + }, + }, + { + "fully multiline", { + {1, 2, 3}, + } + } + }, + stylizer); + + CHECK(str == dedent(R"( + { + "each elem on one line": [ + [1, 2, 3, 4, 5], + [1, 2, 3, 4, 5] + ], + "fully multiline": [ + [ + 1, + 2, + 3 + ] + ] + })")); + } } SECTION("Spaces after commas are controllable separately from multiline") From edb6b25569b49d5b907d4df8e24abaf71c6d0dbd Mon Sep 17 00:00:00 2001 From: Evan Driscoll Date: Mon, 4 Jun 2018 21:59:21 -0500 Subject: [PATCH 34/41] Fancy printer predicate now takes current JSON object The API is ugly at the moment. Will fix with an ugly implementation soon. --- .../detail/output/fancy_serializer.hpp | 56 +++++++++++--- single_include/nlohmann/json.hpp | 56 +++++++++++--- test/src/unit-fancy-serialization.cpp | 73 ++++++++++++++----- 3 files changed, 147 insertions(+), 38 deletions(-) diff --git a/include/nlohmann/detail/output/fancy_serializer.hpp b/include/nlohmann/detail/output/fancy_serializer.hpp index 80a4c441a..7949db491 100644 --- a/include/nlohmann/detail/output/fancy_serializer.hpp +++ b/include/nlohmann/detail/output/fancy_serializer.hpp @@ -43,19 +43,31 @@ struct fancy_serializer_style bool multiline = false; - void set_old_multiline() - { - space_after_colon = space_after_comma = multiline = true; - } + fancy_serializer_style() = default; + + fancy_serializer_style(bool s_colon, bool s_comma, bool ml) + : space_after_colon(s_colon), space_after_comma(s_comma), multiline(ml) + {} + + static const fancy_serializer_style preset_compact; + static const fancy_serializer_style preset_one_line; + static const fancy_serializer_style preset_multiline; }; +const fancy_serializer_style fancy_serializer_style::preset_compact(false, false, false); +const fancy_serializer_style fancy_serializer_style::preset_one_line(true, true, false); +const fancy_serializer_style fancy_serializer_style::preset_multiline(true, true, true); + template class basic_fancy_serializer_stylizer { public: using string_t = typename BasicJsonType::string_t; using json_pointer_t = json_pointer; - using matcher_predicate = std::function; + + using json_matcher_predicate = std::function; + using context_matcher_predicate = std::function; + using matcher_predicate = std::function; basic_fancy_serializer_stylizer(fancy_serializer_style const& ds) : default_style(ds) @@ -76,11 +88,12 @@ class basic_fancy_serializer_stylizer const fancy_serializer_style* get_new_style_or_active( const json_pointer_t& pointer, + const json& j, const fancy_serializer_style* active_style) const { for (auto const& pair : styles) { - if (pair.first(pointer)) + if (pair.first(pointer, j)) { return &pair.second; } @@ -96,15 +109,40 @@ class basic_fancy_serializer_stylizer return styles.back().second; } + fancy_serializer_style& register_style( + json_matcher_predicate p, + fancy_serializer_style style = fancy_serializer_style()) + { + auto wrapper = [p](const json_pointer_t&, const BasicJsonType & j) + { + return p(j); + }; + styles.emplace_back(wrapper, style); + return styles.back().second; + } + + fancy_serializer_style& register_style( + context_matcher_predicate p, + fancy_serializer_style style = fancy_serializer_style()) + { + auto wrapper = [p](const json_pointer_t& c, const BasicJsonType&) + { + return p(c); + }; + styles.emplace_back(wrapper, style); + return styles.back().second; + } + fancy_serializer_style& register_key_matcher_style( string_t str, fancy_serializer_style style = fancy_serializer_style()) { - return register_style([str](const json_pointer_t& pointer) + using pred = context_matcher_predicate; + return register_style(pred([str](const json_pointer_t& pointer) { return (pointer.cbegin() != pointer.cend()) && (*pointer.crbegin() == str); - }, + }), style); } @@ -180,7 +218,7 @@ class fancy_serializer const fancy_serializer_style* active_style, const json_pointer_t& context) { - active_style = stylizer.get_new_style_or_active(context, active_style); + active_style = stylizer.get_new_style_or_active(context, val, active_style); switch (val.m_type) { diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 3165d8129..f94f42998 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -10848,19 +10848,31 @@ struct fancy_serializer_style bool multiline = false; - void set_old_multiline() - { - space_after_colon = space_after_comma = multiline = true; - } + fancy_serializer_style() = default; + + fancy_serializer_style(bool s_colon, bool s_comma, bool ml) + : space_after_colon(s_colon), space_after_comma(s_comma), multiline(ml) + {} + + static const fancy_serializer_style preset_compact; + static const fancy_serializer_style preset_one_line; + static const fancy_serializer_style preset_multiline; }; +const fancy_serializer_style fancy_serializer_style::preset_compact(false, false, false); +const fancy_serializer_style fancy_serializer_style::preset_one_line(true, true, false); +const fancy_serializer_style fancy_serializer_style::preset_multiline(true, true, true); + template class basic_fancy_serializer_stylizer { public: using string_t = typename BasicJsonType::string_t; using json_pointer_t = json_pointer; - using matcher_predicate = std::function; + + using json_matcher_predicate = std::function; + using context_matcher_predicate = std::function; + using matcher_predicate = std::function; basic_fancy_serializer_stylizer(fancy_serializer_style const& ds) : default_style(ds) @@ -10881,11 +10893,12 @@ class basic_fancy_serializer_stylizer const fancy_serializer_style* get_new_style_or_active( const json_pointer_t& pointer, + const json& j, const fancy_serializer_style* active_style) const { for (auto const& pair : styles) { - if (pair.first(pointer)) + if (pair.first(pointer, j)) { return &pair.second; } @@ -10901,15 +10914,40 @@ class basic_fancy_serializer_stylizer return styles.back().second; } + fancy_serializer_style& register_style( + json_matcher_predicate p, + fancy_serializer_style style = fancy_serializer_style()) + { + auto wrapper = [p](const json_pointer_t&, const BasicJsonType & j) + { + return p(j); + }; + styles.emplace_back(wrapper, style); + return styles.back().second; + } + + fancy_serializer_style& register_style( + context_matcher_predicate p, + fancy_serializer_style style = fancy_serializer_style()) + { + auto wrapper = [p](const json_pointer_t& c, const BasicJsonType&) + { + return p(c); + }; + styles.emplace_back(wrapper, style); + return styles.back().second; + } + fancy_serializer_style& register_key_matcher_style( string_t str, fancy_serializer_style style = fancy_serializer_style()) { - return register_style([str](const json_pointer_t& pointer) + using pred = context_matcher_predicate; + return register_style(pred([str](const json_pointer_t& pointer) { return (pointer.cbegin() != pointer.cend()) && (*pointer.crbegin() == str); - }, + }), style); } @@ -10985,7 +11023,7 @@ class fancy_serializer const fancy_serializer_style* active_style, const json_pointer_t& context) { - active_style = stylizer.get_new_style_or_active(context, active_style); + active_style = stylizer.get_new_style_or_active(context, val, active_style); switch (val.m_type) { diff --git a/test/src/unit-fancy-serialization.cpp b/test/src/unit-fancy-serialization.cpp index 5b287f33a..2c9954072 100644 --- a/test/src/unit-fancy-serialization.cpp +++ b/test/src/unit-fancy-serialization.cpp @@ -203,7 +203,8 @@ TEST_CASE("serialization") auto str_flat = fancy_to_string({1, {1}}, style); CHECK(str_flat == "[1,[...]]"); - style.set_old_multiline(); + style = fancy_serializer_style::preset_multiline; + style.depth_limit = 1; auto str_lines = fancy_to_string({1, {1}}, style); CHECK(str_lines == dedent(R"( [ @@ -220,7 +221,8 @@ TEST_CASE("serialization") auto str_flat = fancy_to_string({1, {{"one", 1}}}, style); CHECK(str_flat == "[1,{...}]"); - style.set_old_multiline(); + style = fancy_serializer_style::preset_multiline; + style.depth_limit = 1; auto str_lines = fancy_to_string({1, {{"one", 1}}}, style); CHECK(str_lines == dedent(R"( [ @@ -235,8 +237,8 @@ TEST_CASE("serialization") SECTION("can style objects of a key differently") { fancy_serializer_stylizer stylizer; - stylizer.get_default_style().set_old_multiline(); - stylizer.register_key_matcher_style("one line").multiline = false; + stylizer.get_default_style() = fancy_serializer_style::preset_multiline; + stylizer.register_key_matcher_style("one line"); auto str = fancy_to_string( { @@ -262,8 +264,8 @@ TEST_CASE("serialization") SECTION("changes propagate (unless overridden)") { fancy_serializer_stylizer stylizer; - stylizer.get_default_style().set_old_multiline(); - stylizer.register_key_matcher_style("one line").indent_step = 0; + stylizer.get_default_style() = fancy_serializer_style::preset_multiline; + stylizer.register_key_matcher_style("one line"); auto str = fancy_to_string( { @@ -279,18 +281,20 @@ TEST_CASE("serialization") })")); } - SECTION("example of more sophisticated matcher") + SECTION("example of more sophisticated context matcher") { + using pred = fancy_serializer_stylizer::context_matcher_predicate; + fancy_serializer_stylizer stylizer; - stylizer.get_default_style().set_old_multiline(); + stylizer.get_default_style() = fancy_serializer_style::preset_multiline; stylizer.register_style( - [] (const json_pointer& context) + pred([] (const json_pointer& context) { // Matches if context[-2] is "each elem on one line" return (context.cend() - context.cbegin() >= 2) && (*(context.cend() - 2) == "each elem on one line"); - } + }) ).space_after_comma = true; auto str = fancy_to_string( @@ -324,6 +328,40 @@ TEST_CASE("serialization") ] })")); } + + SECTION("example of more sophisticated json matcher") + { + using pred = fancy_serializer_stylizer::json_matcher_predicate; + + fancy_serializer_stylizer stylizer; + stylizer.get_default_style() = fancy_serializer_style::preset_multiline; + + stylizer.register_style( + pred([] (const json & j) + { + return j.type() == json::value_t::array; + }) + ) = fancy_serializer_style::preset_one_line; + + auto str = fancy_to_string( + { + { + "an array", {1, 2, 3} + }, + { + "an object", {{"key", "val"}} + } + }, + stylizer); + + CHECK(str == dedent(R"( + { + "an array": [1, 2, 3], + "an object": { + "key": "val" + } + })")); + } } SECTION("Spaces after commas are controllable separately from multiline") @@ -346,8 +384,7 @@ TEST_CASE("serialization") SECTION("multiline can have no space") { - fancy_serializer_style style; - style.set_old_multiline(); + fancy_serializer_style style = fancy_serializer_style::preset_multiline; style.space_after_colon = false; auto str = fancy_to_string({{"one", 1}}, style); CHECK(str == dedent(R"( @@ -360,8 +397,7 @@ TEST_CASE("serialization") SECTION("given width") { - fancy_serializer_style style; - style.set_old_multiline(); + fancy_serializer_style style = fancy_serializer_style::preset_multiline; auto str = fancy_to_string({"foo", 1, 2, 3, false, {{"one", 1}}}, style); CHECK(str == dedent(R"( [ @@ -378,10 +414,9 @@ TEST_CASE("serialization") SECTION("given fill") { - fancy_serializer_style style; + fancy_serializer_style style = fancy_serializer_style::preset_multiline; style.indent_step = 1; style.indent_char = '\t'; - style.set_old_multiline(); auto str = fancy_to_string({"foo", 1, 2, 3, false, {{"one", 1}}}, style); CHECK(str == @@ -400,10 +435,9 @@ TEST_CASE("serialization") SECTION("indent_char is honored for deep indents in lists") { - fancy_serializer_style style; + fancy_serializer_style style = fancy_serializer_style::preset_multiline; style.indent_step = 300; style.indent_char = 'X'; - style.set_old_multiline(); auto str = fancy_to_string({1, {1}}, style); @@ -419,10 +453,9 @@ TEST_CASE("serialization") SECTION("indent_char is honored for deep indents in objects") { - fancy_serializer_style style; + fancy_serializer_style style = fancy_serializer_style::preset_multiline; style.indent_step = 300; style.indent_char = 'X'; - style.set_old_multiline(); auto str = fancy_to_string({{"key", {{"key", 1}}}}, style); From 494be1c445a5203c340282f689dcb73213ea8fbf Mon Sep 17 00:00:00 2001 From: Evan Driscoll Date: Mon, 4 Jun 2018 22:28:58 -0500 Subject: [PATCH 35/41] Refactor: rename the register_style overloads I really really wanted to name these the same and overload them, but I couldn't get the metaprogramming to work right. Here's a comment I wrote that describes the problems and what I *planned* to do: // What we want is the register_style overloads below. I chose to // keep them with the same name. But there are two problems with // that. First, because I need to wrap them in a std::function // when promoting to two arguments, I want to make register_style // themselves take the function parameter by a template argument // so it doesn't get type-erased "twice" (with two virtual // calls). But then that means that both versions would have the // generic signature "template // ... (Predicate, style)" and that would lead to ambiguous calls. // // The second problem is that ever if you keep the first parameter // a std::function, because 'json_pointer' is implicitly // convertible to a 'json', if you rely on the implicit conversion // to std::function then you'd get an ambugious call. // // So what we want is, using Concept terms: // // template ... (Predicate, style) // template ... (Predicate, style) // // where JsonCallable is additionally *not* // JsonPointerCallable. The following is my attempt to get that. I then wrote some code that is similar to this: #include struct Main {}; struct Secondary { Secondary(Main); }; // http://en.cppreference.com/w/cpp/types/void_t template struct make_void { typedef void type;}; template using void_t = typename make_void::type; template struct can_be_called_with_main : std::false_type { }; template struct can_be_called_with_main< T, void_t()(std::declval
()))> >: std::true_type { }; template struct can_be_called_with_secondary : std::false_type { }; template struct can_be_called_with_secondary< T, void_t()(std::declval()))> >: std::true_type { }; template auto func(Functor f) -> typename std::enable_if::value, int>::type { return 0; } template auto func(Functor f) -> typename std::enable_if< can_be_called_with_secondary::value && !can_be_called_with_main::value , int>::type { return 0; } auto x1 = func([] (Main) {}); auto x2 = func([] (Secondary) {}); where Main is like 'json' and Secondary like 'json_pointer'. Problem is it doesn't work -- in the SFIANE context, it looks like predicates of both `bool (json)` and `bool (json_pointer)` are callable with both json and json_pointer objects. In the case of `bool (json)` being called with a 'json_pointer', that is via the implicit conversion discussed in the comment above. In the caes of `bool (json_pointer)` being called with a `json`, my guess as to what is going on is that `json` provides an implicit to-anything conversion, which uses a `from_json` function. However, that isn't implemented in a SFIANE-friendly way -- when you try to actually make that conversion, there's a static_assert failure. An alternative approach would be to extract the first argument to the provided predicate via some technique like those described in https://functionalcpp.wordpress.com/2013/08/05/function-traits/, and then is_same them vs json and json_pointer. --- .../nlohmann/detail/output/fancy_serializer.hpp | 15 ++++++++------- single_include/nlohmann/json.hpp | 15 ++++++++------- test/src/unit-fancy-serialization.cpp | 16 ++++++---------- 3 files changed, 22 insertions(+), 24 deletions(-) diff --git a/include/nlohmann/detail/output/fancy_serializer.hpp b/include/nlohmann/detail/output/fancy_serializer.hpp index 7949db491..62798d5e4 100644 --- a/include/nlohmann/detail/output/fancy_serializer.hpp +++ b/include/nlohmann/detail/output/fancy_serializer.hpp @@ -109,8 +109,9 @@ class basic_fancy_serializer_stylizer return styles.back().second; } - fancy_serializer_style& register_style( - json_matcher_predicate p, + template + fancy_serializer_style& register_style_object_pred( + Predicate p, fancy_serializer_style style = fancy_serializer_style()) { auto wrapper = [p](const json_pointer_t&, const BasicJsonType & j) @@ -121,8 +122,9 @@ class basic_fancy_serializer_stylizer return styles.back().second; } - fancy_serializer_style& register_style( - context_matcher_predicate p, + template + fancy_serializer_style& register_style_context_pred( + Predicate p, fancy_serializer_style style = fancy_serializer_style()) { auto wrapper = [p](const json_pointer_t& c, const BasicJsonType&) @@ -137,12 +139,11 @@ class basic_fancy_serializer_stylizer string_t str, fancy_serializer_style style = fancy_serializer_style()) { - using pred = context_matcher_predicate; - return register_style(pred([str](const json_pointer_t& pointer) + return register_style_context_pred([str](const json_pointer_t& pointer) { return (pointer.cbegin() != pointer.cend()) && (*pointer.crbegin() == str); - }), + }, style); } diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index f94f42998..b888e1545 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -10914,8 +10914,9 @@ class basic_fancy_serializer_stylizer return styles.back().second; } - fancy_serializer_style& register_style( - json_matcher_predicate p, + template + fancy_serializer_style& register_style_object_pred( + Predicate p, fancy_serializer_style style = fancy_serializer_style()) { auto wrapper = [p](const json_pointer_t&, const BasicJsonType & j) @@ -10926,8 +10927,9 @@ class basic_fancy_serializer_stylizer return styles.back().second; } - fancy_serializer_style& register_style( - context_matcher_predicate p, + template + fancy_serializer_style& register_style_context_pred( + Predicate p, fancy_serializer_style style = fancy_serializer_style()) { auto wrapper = [p](const json_pointer_t& c, const BasicJsonType&) @@ -10942,12 +10944,11 @@ class basic_fancy_serializer_stylizer string_t str, fancy_serializer_style style = fancy_serializer_style()) { - using pred = context_matcher_predicate; - return register_style(pred([str](const json_pointer_t& pointer) + return register_style_context_pred([str](const json_pointer_t& pointer) { return (pointer.cbegin() != pointer.cend()) && (*pointer.crbegin() == str); - }), + }, style); } diff --git a/test/src/unit-fancy-serialization.cpp b/test/src/unit-fancy-serialization.cpp index 2c9954072..1ac0ec720 100644 --- a/test/src/unit-fancy-serialization.cpp +++ b/test/src/unit-fancy-serialization.cpp @@ -283,18 +283,16 @@ TEST_CASE("serialization") SECTION("example of more sophisticated context matcher") { - using pred = fancy_serializer_stylizer::context_matcher_predicate; - fancy_serializer_stylizer stylizer; stylizer.get_default_style() = fancy_serializer_style::preset_multiline; - stylizer.register_style( - pred([] (const json_pointer& context) + stylizer.register_style_context_pred( + [] (const json_pointer& context) { // Matches if context[-2] is "each elem on one line" return (context.cend() - context.cbegin() >= 2) && (*(context.cend() - 2) == "each elem on one line"); - }) + } ).space_after_comma = true; auto str = fancy_to_string( @@ -331,16 +329,14 @@ TEST_CASE("serialization") SECTION("example of more sophisticated json matcher") { - using pred = fancy_serializer_stylizer::json_matcher_predicate; - fancy_serializer_stylizer stylizer; stylizer.get_default_style() = fancy_serializer_style::preset_multiline; - stylizer.register_style( - pred([] (const json & j) + stylizer.register_style_object_pred( + [] (const json & j) { return j.type() == json::value_t::array; - }) + } ) = fancy_serializer_style::preset_one_line; auto str = fancy_to_string( From 680a85b7a2473f4b0a3889d5cdb3ebb397efa2b7 Mon Sep 17 00:00:00 2001 From: Evan Driscoll Date: Mon, 4 Jun 2018 22:41:11 -0500 Subject: [PATCH 36/41] Rename 'fancy_serializer_{style,stylizer}' to 'print_{...}' --- .../detail/output/fancy_serializer.hpp | 73 +++++++++--------- include/nlohmann/json.hpp | 2 +- single_include/nlohmann/json.hpp | 75 +++++++++---------- test/src/unit-fancy-serialization.cpp | 56 +++++++------- 4 files changed, 102 insertions(+), 104 deletions(-) diff --git a/include/nlohmann/detail/output/fancy_serializer.hpp b/include/nlohmann/detail/output/fancy_serializer.hpp index 62798d5e4..f40fb7532 100644 --- a/include/nlohmann/detail/output/fancy_serializer.hpp +++ b/include/nlohmann/detail/output/fancy_serializer.hpp @@ -29,7 +29,7 @@ namespace nlohmann { -struct fancy_serializer_style +struct print_style { unsigned int indent_step = 4; char indent_char = ' '; @@ -43,23 +43,23 @@ struct fancy_serializer_style bool multiline = false; - fancy_serializer_style() = default; + print_style() = default; - fancy_serializer_style(bool s_colon, bool s_comma, bool ml) + print_style(bool s_colon, bool s_comma, bool ml) : space_after_colon(s_colon), space_after_comma(s_comma), multiline(ml) {} - static const fancy_serializer_style preset_compact; - static const fancy_serializer_style preset_one_line; - static const fancy_serializer_style preset_multiline; + static const print_style preset_compact; + static const print_style preset_one_line; + static const print_style preset_multiline; }; -const fancy_serializer_style fancy_serializer_style::preset_compact(false, false, false); -const fancy_serializer_style fancy_serializer_style::preset_one_line(true, true, false); -const fancy_serializer_style fancy_serializer_style::preset_multiline(true, true, true); +const print_style print_style::preset_compact(false, false, false); +const print_style print_style::preset_one_line(true, true, false); +const print_style print_style::preset_multiline(true, true, true); template -class basic_fancy_serializer_stylizer +class basic_print_stylizer { public: using string_t = typename BasicJsonType::string_t; @@ -69,27 +69,27 @@ class basic_fancy_serializer_stylizer using context_matcher_predicate = std::function; using matcher_predicate = std::function; - basic_fancy_serializer_stylizer(fancy_serializer_style const& ds) + basic_print_stylizer(print_style const& ds) : default_style(ds) {} - basic_fancy_serializer_stylizer() = default; + basic_print_stylizer() = default; public: - const fancy_serializer_style& get_default_style() const + const print_style& get_default_style() const { return default_style; } - fancy_serializer_style& get_default_style() + print_style& get_default_style() { return default_style; } - const fancy_serializer_style* get_new_style_or_active( + const print_style* get_new_style_or_active( const json_pointer_t& pointer, const json& j, - const fancy_serializer_style* active_style) const + const print_style* active_style) const { for (auto const& pair : styles) { @@ -101,18 +101,18 @@ class basic_fancy_serializer_stylizer return active_style; } - fancy_serializer_style& register_style( + print_style& register_style( matcher_predicate p, - fancy_serializer_style style = fancy_serializer_style()) + print_style style = print_style()) { styles.emplace_back(p, style); return styles.back().second; } template - fancy_serializer_style& register_style_object_pred( + print_style& register_style_object_pred( Predicate p, - fancy_serializer_style style = fancy_serializer_style()) + print_style style = print_style()) { auto wrapper = [p](const json_pointer_t&, const BasicJsonType & j) { @@ -123,9 +123,9 @@ class basic_fancy_serializer_stylizer } template - fancy_serializer_style& register_style_context_pred( + print_style& register_style_context_pred( Predicate p, - fancy_serializer_style style = fancy_serializer_style()) + print_style style = print_style()) { auto wrapper = [p](const json_pointer_t& c, const BasicJsonType&) { @@ -135,9 +135,9 @@ class basic_fancy_serializer_stylizer return styles.back().second; } - fancy_serializer_style& register_key_matcher_style( + print_style& register_key_matcher_style( string_t str, - fancy_serializer_style style = fancy_serializer_style()) + print_style style = print_style()) { return register_style_context_pred([str](const json_pointer_t& pointer) { @@ -147,14 +147,14 @@ class basic_fancy_serializer_stylizer style); } - fancy_serializer_style& last_registered_style() + print_style& last_registered_style() { return styles.back().second; } private: - fancy_serializer_style default_style; - std::vector> styles; + print_style default_style; + std::vector> styles; }; namespace detail @@ -166,7 +166,7 @@ namespace detail template class fancy_serializer { - using stylizer_t = basic_fancy_serializer_stylizer; + using stylizer_t = basic_print_stylizer; using primitive_serializer_t = primitive_serializer; using string_t = typename BasicJsonType::string_t; using number_float_t = typename BasicJsonType::number_float_t; @@ -216,7 +216,7 @@ class fancy_serializer void dump(const BasicJsonType& val, const bool ensure_ascii, const unsigned int depth, - const fancy_serializer_style* active_style, + const print_style* active_style, const json_pointer_t& context) { active_style = stylizer.get_new_style_or_active(context, val, active_style); @@ -290,7 +290,7 @@ class fancy_serializer template void dump_object_key_value( Iterator i, bool ensure_ascii, unsigned int depth, - const fancy_serializer_style* active_style, + const print_style* active_style, const json_pointer_t& context) { const auto new_indent = (depth + 1) * active_style->indent_step * active_style->multiline; @@ -306,7 +306,7 @@ class fancy_serializer void dump_object(const BasicJsonType& val, bool ensure_ascii, unsigned int depth, - const fancy_serializer_style* active_style, + const print_style* active_style, const json_pointer_t& context) { if (val.m_value.object->empty()) @@ -352,7 +352,7 @@ class fancy_serializer void dump_array(const BasicJsonType& val, bool ensure_ascii, unsigned int depth, - const fancy_serializer_style* active_style, + const print_style* active_style, const json_pointer_t& context) { if (val.m_value.array->empty()) @@ -405,7 +405,7 @@ class fancy_serializer } void dump_string(const string_t& str, bool ensure_ascii, - const fancy_serializer_style* active_style) + const print_style* active_style) { o->write_character('\"'); if (active_style->strings_maximum_length == 0) @@ -484,7 +484,7 @@ class fancy_serializer template std::ostream& fancy_dump(std::ostream& o, const BasicJsonType& j, - basic_fancy_serializer_stylizer const& stylizer) + basic_print_stylizer const& stylizer) { // do the actual serialization detail::fancy_serializer s(detail::output_adapter(o), stylizer); @@ -493,10 +493,9 @@ std::ostream& fancy_dump(std::ostream& o, const BasicJsonType& j, } template -std::ostream& fancy_dump(std::ostream& o, const BasicJsonType& j, - fancy_serializer_style style) +std::ostream& fancy_dump(std::ostream& o, const BasicJsonType& j, print_style style) { - basic_fancy_serializer_stylizer stylizer(style); + basic_print_stylizer stylizer(style); return fancy_dump(o, j, stylizer); } diff --git a/include/nlohmann/json.hpp b/include/nlohmann/json.hpp index 16984d5f7..bfe032045 100644 --- a/include/nlohmann/json.hpp +++ b/include/nlohmann/json.hpp @@ -7625,7 +7625,7 @@ class basic_json /// @} }; -using fancy_serializer_stylizer = basic_fancy_serializer_stylizer; +using print_stylizer = basic_print_stylizer; } // namespace nlohmann /////////////////////// diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index b888e1545..d72fbf1a2 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -10834,7 +10834,7 @@ class json_pointer namespace nlohmann { -struct fancy_serializer_style +struct print_style { unsigned int indent_step = 4; char indent_char = ' '; @@ -10848,23 +10848,23 @@ struct fancy_serializer_style bool multiline = false; - fancy_serializer_style() = default; + print_style() = default; - fancy_serializer_style(bool s_colon, bool s_comma, bool ml) + print_style(bool s_colon, bool s_comma, bool ml) : space_after_colon(s_colon), space_after_comma(s_comma), multiline(ml) {} - static const fancy_serializer_style preset_compact; - static const fancy_serializer_style preset_one_line; - static const fancy_serializer_style preset_multiline; + static const print_style preset_compact; + static const print_style preset_one_line; + static const print_style preset_multiline; }; -const fancy_serializer_style fancy_serializer_style::preset_compact(false, false, false); -const fancy_serializer_style fancy_serializer_style::preset_one_line(true, true, false); -const fancy_serializer_style fancy_serializer_style::preset_multiline(true, true, true); +const print_style print_style::preset_compact(false, false, false); +const print_style print_style::preset_one_line(true, true, false); +const print_style print_style::preset_multiline(true, true, true); template -class basic_fancy_serializer_stylizer +class basic_print_stylizer { public: using string_t = typename BasicJsonType::string_t; @@ -10874,27 +10874,27 @@ class basic_fancy_serializer_stylizer using context_matcher_predicate = std::function; using matcher_predicate = std::function; - basic_fancy_serializer_stylizer(fancy_serializer_style const& ds) + basic_print_stylizer(print_style const& ds) : default_style(ds) {} - basic_fancy_serializer_stylizer() = default; + basic_print_stylizer() = default; public: - const fancy_serializer_style& get_default_style() const + const print_style& get_default_style() const { return default_style; } - fancy_serializer_style& get_default_style() + print_style& get_default_style() { return default_style; } - const fancy_serializer_style* get_new_style_or_active( + const print_style* get_new_style_or_active( const json_pointer_t& pointer, const json& j, - const fancy_serializer_style* active_style) const + const print_style* active_style) const { for (auto const& pair : styles) { @@ -10906,18 +10906,18 @@ class basic_fancy_serializer_stylizer return active_style; } - fancy_serializer_style& register_style( + print_style& register_style( matcher_predicate p, - fancy_serializer_style style = fancy_serializer_style()) + print_style style = print_style()) { styles.emplace_back(p, style); return styles.back().second; } template - fancy_serializer_style& register_style_object_pred( + print_style& register_style_object_pred( Predicate p, - fancy_serializer_style style = fancy_serializer_style()) + print_style style = print_style()) { auto wrapper = [p](const json_pointer_t&, const BasicJsonType & j) { @@ -10928,9 +10928,9 @@ class basic_fancy_serializer_stylizer } template - fancy_serializer_style& register_style_context_pred( + print_style& register_style_context_pred( Predicate p, - fancy_serializer_style style = fancy_serializer_style()) + print_style style = print_style()) { auto wrapper = [p](const json_pointer_t& c, const BasicJsonType&) { @@ -10940,9 +10940,9 @@ class basic_fancy_serializer_stylizer return styles.back().second; } - fancy_serializer_style& register_key_matcher_style( + print_style& register_key_matcher_style( string_t str, - fancy_serializer_style style = fancy_serializer_style()) + print_style style = print_style()) { return register_style_context_pred([str](const json_pointer_t& pointer) { @@ -10952,14 +10952,14 @@ class basic_fancy_serializer_stylizer style); } - fancy_serializer_style& last_registered_style() + print_style& last_registered_style() { return styles.back().second; } private: - fancy_serializer_style default_style; - std::vector> styles; + print_style default_style; + std::vector> styles; }; namespace detail @@ -10971,7 +10971,7 @@ namespace detail template class fancy_serializer { - using stylizer_t = basic_fancy_serializer_stylizer; + using stylizer_t = basic_print_stylizer; using primitive_serializer_t = primitive_serializer; using string_t = typename BasicJsonType::string_t; using number_float_t = typename BasicJsonType::number_float_t; @@ -11021,7 +11021,7 @@ class fancy_serializer void dump(const BasicJsonType& val, const bool ensure_ascii, const unsigned int depth, - const fancy_serializer_style* active_style, + const print_style* active_style, const json_pointer_t& context) { active_style = stylizer.get_new_style_or_active(context, val, active_style); @@ -11095,7 +11095,7 @@ class fancy_serializer template void dump_object_key_value( Iterator i, bool ensure_ascii, unsigned int depth, - const fancy_serializer_style* active_style, + const print_style* active_style, const json_pointer_t& context) { const auto new_indent = (depth + 1) * active_style->indent_step * active_style->multiline; @@ -11111,7 +11111,7 @@ class fancy_serializer void dump_object(const BasicJsonType& val, bool ensure_ascii, unsigned int depth, - const fancy_serializer_style* active_style, + const print_style* active_style, const json_pointer_t& context) { if (val.m_value.object->empty()) @@ -11157,7 +11157,7 @@ class fancy_serializer void dump_array(const BasicJsonType& val, bool ensure_ascii, unsigned int depth, - const fancy_serializer_style* active_style, + const print_style* active_style, const json_pointer_t& context) { if (val.m_value.array->empty()) @@ -11210,7 +11210,7 @@ class fancy_serializer } void dump_string(const string_t& str, bool ensure_ascii, - const fancy_serializer_style* active_style) + const print_style* active_style) { o->write_character('\"'); if (active_style->strings_maximum_length == 0) @@ -11289,7 +11289,7 @@ class fancy_serializer template std::ostream& fancy_dump(std::ostream& o, const BasicJsonType& j, - basic_fancy_serializer_stylizer const& stylizer) + basic_print_stylizer const& stylizer) { // do the actual serialization detail::fancy_serializer s(detail::output_adapter(o), stylizer); @@ -11298,10 +11298,9 @@ std::ostream& fancy_dump(std::ostream& o, const BasicJsonType& j, } template -std::ostream& fancy_dump(std::ostream& o, const BasicJsonType& j, - fancy_serializer_style style) +std::ostream& fancy_dump(std::ostream& o, const BasicJsonType& j, print_style style) { - basic_fancy_serializer_stylizer stylizer(style); + basic_print_stylizer stylizer(style); return fancy_dump(o, j, stylizer); } @@ -18979,7 +18978,7 @@ class basic_json /// @} }; -using fancy_serializer_stylizer = basic_fancy_serializer_stylizer; +using print_stylizer = basic_print_stylizer; } // namespace nlohmann /////////////////////// diff --git a/test/src/unit-fancy-serialization.cpp b/test/src/unit-fancy-serialization.cpp index 1ac0ec720..6299bfef2 100644 --- a/test/src/unit-fancy-serialization.cpp +++ b/test/src/unit-fancy-serialization.cpp @@ -35,8 +35,8 @@ SOFTWARE. using nlohmann::json; using nlohmann::json_pointer; using nlohmann::fancy_dump; -using nlohmann::fancy_serializer_style; -using nlohmann::fancy_serializer_stylizer; +using nlohmann::print_style; +using nlohmann::print_stylizer; // Chops off the first line (if empty, but if it *isn't* empty you're // probably using this wrong), measures the leading indent on the @@ -73,14 +73,14 @@ std::string dedent(const char* str) return ans; } -std::string fancy_to_string(json j, fancy_serializer_style style = fancy_serializer_style()) +std::string fancy_to_string(json j, print_style style = print_style()) { std::stringstream ss; fancy_dump(ss, j, style); return ss.str(); } -std::string fancy_to_string(json j, fancy_serializer_stylizer stylizer) +std::string fancy_to_string(json j, print_stylizer stylizer) { std::stringstream ss; fancy_dump(ss, j, stylizer); @@ -134,7 +134,7 @@ TEST_CASE("serialization") SECTION("long strings can be shortened") { - fancy_serializer_style style; + print_style style; style.strings_maximum_length = 10; auto str = fancy_to_string( @@ -158,7 +158,7 @@ TEST_CASE("serialization") for (auto test : tests) { - fancy_serializer_style style; + print_style style; style.strings_maximum_length = test.first; auto str = fancy_to_string(quick, style); CHECK(str == test.second); @@ -167,7 +167,7 @@ TEST_CASE("serialization") SECTION("But you cannot ask for a length of zero; that means unlimited") { - fancy_serializer_style style; + print_style style; style.strings_maximum_length = 0; auto str = fancy_to_string( @@ -179,7 +179,7 @@ TEST_CASE("serialization") SECTION("\"Limiting\" to something long doesn't do anything") { - fancy_serializer_style style; + print_style style; style.strings_maximum_length = 100; auto str = fancy_to_string( @@ -197,13 +197,13 @@ TEST_CASE("serialization") { SECTION("recursing past the maximum depth with a list elides the subobjects") { - fancy_serializer_style style; + print_style style; style.depth_limit = 1; auto str_flat = fancy_to_string({1, {1}}, style); CHECK(str_flat == "[1,[...]]"); - style = fancy_serializer_style::preset_multiline; + style = print_style::preset_multiline; style.depth_limit = 1; auto str_lines = fancy_to_string({1, {1}}, style); CHECK(str_lines == dedent(R"( @@ -215,13 +215,13 @@ TEST_CASE("serialization") SECTION("recursing past the maximum depth with an object elides the subobjects") { - fancy_serializer_style style; + print_style style; style.depth_limit = 1; auto str_flat = fancy_to_string({1, {{"one", 1}}}, style); CHECK(str_flat == "[1,{...}]"); - style = fancy_serializer_style::preset_multiline; + style = print_style::preset_multiline; style.depth_limit = 1; auto str_lines = fancy_to_string({1, {{"one", 1}}}, style); CHECK(str_lines == dedent(R"( @@ -236,8 +236,8 @@ TEST_CASE("serialization") { SECTION("can style objects of a key differently") { - fancy_serializer_stylizer stylizer; - stylizer.get_default_style() = fancy_serializer_style::preset_multiline; + print_stylizer stylizer; + stylizer.get_default_style() = print_style::preset_multiline; stylizer.register_key_matcher_style("one line"); auto str = fancy_to_string( @@ -263,8 +263,8 @@ TEST_CASE("serialization") SECTION("changes propagate (unless overridden)") { - fancy_serializer_stylizer stylizer; - stylizer.get_default_style() = fancy_serializer_style::preset_multiline; + print_stylizer stylizer; + stylizer.get_default_style() = print_style::preset_multiline; stylizer.register_key_matcher_style("one line"); auto str = fancy_to_string( @@ -283,8 +283,8 @@ TEST_CASE("serialization") SECTION("example of more sophisticated context matcher") { - fancy_serializer_stylizer stylizer; - stylizer.get_default_style() = fancy_serializer_style::preset_multiline; + print_stylizer stylizer; + stylizer.get_default_style() = print_style::preset_multiline; stylizer.register_style_context_pred( [] (const json_pointer& context) @@ -329,15 +329,15 @@ TEST_CASE("serialization") SECTION("example of more sophisticated json matcher") { - fancy_serializer_stylizer stylizer; - stylizer.get_default_style() = fancy_serializer_style::preset_multiline; + print_stylizer stylizer; + stylizer.get_default_style() = print_style::preset_multiline; stylizer.register_style_object_pred( [] (const json & j) { return j.type() == json::value_t::array; } - ) = fancy_serializer_style::preset_one_line; + ) = print_style::preset_one_line; auto str = fancy_to_string( { @@ -364,7 +364,7 @@ TEST_CASE("serialization") { SECTION("commas") { - fancy_serializer_style style; + print_style style; style.space_after_comma = true; auto str = fancy_to_string({1, 2, 3}, style); CHECK(str == "[1, 2, 3]"); @@ -372,7 +372,7 @@ TEST_CASE("serialization") SECTION("colons") { - fancy_serializer_style style; + print_style style; style.space_after_colon = true; auto str = fancy_to_string({{"one", 1}}, style); CHECK(str == "{\"one\": 1}"); @@ -380,7 +380,7 @@ TEST_CASE("serialization") SECTION("multiline can have no space") { - fancy_serializer_style style = fancy_serializer_style::preset_multiline; + print_style style = print_style::preset_multiline; style.space_after_colon = false; auto str = fancy_to_string({{"one", 1}}, style); CHECK(str == dedent(R"( @@ -393,7 +393,7 @@ TEST_CASE("serialization") SECTION("given width") { - fancy_serializer_style style = fancy_serializer_style::preset_multiline; + print_style style = print_style::preset_multiline; auto str = fancy_to_string({"foo", 1, 2, 3, false, {{"one", 1}}}, style); CHECK(str == dedent(R"( [ @@ -410,7 +410,7 @@ TEST_CASE("serialization") SECTION("given fill") { - fancy_serializer_style style = fancy_serializer_style::preset_multiline; + print_style style = print_style::preset_multiline; style.indent_step = 1; style.indent_char = '\t'; @@ -431,7 +431,7 @@ TEST_CASE("serialization") SECTION("indent_char is honored for deep indents in lists") { - fancy_serializer_style style = fancy_serializer_style::preset_multiline; + print_style style = print_style::preset_multiline; style.indent_step = 300; style.indent_char = 'X'; @@ -449,7 +449,7 @@ TEST_CASE("serialization") SECTION("indent_char is honored for deep indents in objects") { - fancy_serializer_style style = fancy_serializer_style::preset_multiline; + print_style style = print_style::preset_multiline; style.indent_step = 300; style.indent_char = 'X'; From d7677fca6f2eabb5259a41a4ad6e0c2522162cd5 Mon Sep 17 00:00:00 2001 From: Evan Driscoll Date: Tue, 5 Jun 2018 22:04:48 -0500 Subject: [PATCH 37/41] Refactor: some TMP machinery to lead to better overloading The takes_argument trait is coutesy of @N00byEdge --- .../detail/output/fancy_serializer.hpp | 54 ++++++++++++++++++- single_include/nlohmann/json.hpp | 54 ++++++++++++++++++- 2 files changed, 104 insertions(+), 4 deletions(-) diff --git a/include/nlohmann/detail/output/fancy_serializer.hpp b/include/nlohmann/detail/output/fancy_serializer.hpp index f40fb7532..3387fdebc 100644 --- a/include/nlohmann/detail/output/fancy_serializer.hpp +++ b/include/nlohmann/detail/output/fancy_serializer.hpp @@ -29,6 +29,46 @@ namespace nlohmann { +namespace details +{ +// Some metaprogramming stuff. The point here is to distinguish +// functions and function objects that take 'json' and +// 'json_pointer' as the first argument. This can't be done +// conventionally because there are implicit conversions in both +// directions, so a function type that matches one will match the +// other. (The conversion from json to json_pointer doesn't really +// exist if you try to use it, but it does in the SFIANE context.) +// +// So we define takes_argument to see if Func(Arg) is +// not only legal but without undergoing any conversions on +// Arg. That's where 'metawrapper' comes into play. We actually +// check if Func(metawrapper) is legal. That takes up the one +// implicit conversion that's allowed. +// +// See also the uses below. + +template struct make_void +{ + typedef void type; +}; +template using void_t = typename make_void::type; + +template +struct metawrapper +{ + operator T const& (); +}; + +template +struct takes_arguments_impl : std::false_type { }; + +template +struct takes_arguments_impl()(metawrapper()...))>, F, Args...> : std::true_type { }; + +template +struct takes_arguments : takes_arguments_impl { }; +} + struct print_style { unsigned int indent_step = 4; @@ -109,10 +149,12 @@ class basic_print_stylizer return styles.back().second; } + // Predicate is conceptually 'bool (json)' here template - print_style& register_style_object_pred( + auto register_style_object_pred( Predicate p, print_style style = print_style()) + -> typename std::enable_if::value, print_style&>::type { auto wrapper = [p](const json_pointer_t&, const BasicJsonType & j) { @@ -122,10 +164,18 @@ class basic_print_stylizer return styles.back().second; } + // Predicate is conceptually 'bool (json_pointer)' here... + // + // ...But we have to 'json' instead (or rather, BasicJsonType) + // because json has an apparent (in the SFIANE context) implicit + // conversion from and two everything. Including + // 'metawrapper'. So if you pass 'bool (json)', it + // will look like it can pass a metawrapper to it template - print_style& register_style_context_pred( + auto register_style_context_pred( Predicate p, print_style style = print_style()) + -> typename std::enable_if < !details::takes_arguments::value, print_style& >::type { auto wrapper = [p](const json_pointer_t& c, const BasicJsonType&) { diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index d72fbf1a2..a3ec2705b 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -10834,6 +10834,46 @@ class json_pointer namespace nlohmann { +namespace details +{ +// Some metaprogramming stuff. The point here is to distinguish +// functions and function objects that take 'json' and +// 'json_pointer' as the first argument. This can't be done +// conventionally because there are implicit conversions in both +// directions, so a function type that matches one will match the +// other. (The conversion from json to json_pointer doesn't really +// exist if you try to use it, but it does in the SFIANE context.) +// +// So we define takes_argument to see if Func(Arg) is +// not only legal but without undergoing any conversions on +// Arg. That's where 'metawrapper' comes into play. We actually +// check if Func(metawrapper) is legal. That takes up the one +// implicit conversion that's allowed. +// +// See also the uses below. + +template struct make_void +{ + typedef void type; +}; +template using void_t = typename make_void::type; + +template +struct metawrapper +{ + operator T const& (); +}; + +template +struct takes_arguments_impl : std::false_type { }; + +template +struct takes_arguments_impl()(metawrapper()...))>, F, Args...> : std::true_type { }; + +template +struct takes_arguments : takes_arguments_impl { }; +} + struct print_style { unsigned int indent_step = 4; @@ -10914,10 +10954,12 @@ class basic_print_stylizer return styles.back().second; } + // Predicate is conceptually 'bool (json)' here template - print_style& register_style_object_pred( + auto register_style_object_pred( Predicate p, print_style style = print_style()) + -> typename std::enable_if::value, print_style&>::type { auto wrapper = [p](const json_pointer_t&, const BasicJsonType & j) { @@ -10927,10 +10969,18 @@ class basic_print_stylizer return styles.back().second; } + // Predicate is conceptually 'bool (json_pointer)' here... + // + // ...But we have to 'json' instead (or rather, BasicJsonType) + // because json has an apparent (in the SFIANE context) implicit + // conversion from and two everything. Including + // 'metawrapper'. So if you pass 'bool (json)', it + // will look like it can pass a metawrapper to it template - print_style& register_style_context_pred( + auto register_style_context_pred( Predicate p, print_style style = print_style()) + -> typename std::enable_if < !details::takes_arguments::value, print_style& >::type { auto wrapper = [p](const json_pointer_t& c, const BasicJsonType&) { From fe09e0dd6bd08f0a4b4e8eaeae71bb59977f7ce2 Mon Sep 17 00:00:00 2001 From: Evan Driscoll Date: Tue, 5 Jun 2018 22:25:17 -0500 Subject: [PATCH 38/41] Overload 'register_style' template instead of awkward multi-naming Hopefully this is an improvement. :-) --- include/nlohmann/detail/output/fancy_serializer.hpp | 6 +++--- single_include/nlohmann/json.hpp | 6 +++--- test/src/unit-fancy-serialization.cpp | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/include/nlohmann/detail/output/fancy_serializer.hpp b/include/nlohmann/detail/output/fancy_serializer.hpp index 3387fdebc..7e57e1c79 100644 --- a/include/nlohmann/detail/output/fancy_serializer.hpp +++ b/include/nlohmann/detail/output/fancy_serializer.hpp @@ -151,7 +151,7 @@ class basic_print_stylizer // Predicate is conceptually 'bool (json)' here template - auto register_style_object_pred( + auto register_style( Predicate p, print_style style = print_style()) -> typename std::enable_if::value, print_style&>::type @@ -172,7 +172,7 @@ class basic_print_stylizer // 'metawrapper'. So if you pass 'bool (json)', it // will look like it can pass a metawrapper to it template - auto register_style_context_pred( + auto register_style( Predicate p, print_style style = print_style()) -> typename std::enable_if < !details::takes_arguments::value, print_style& >::type @@ -189,7 +189,7 @@ class basic_print_stylizer string_t str, print_style style = print_style()) { - return register_style_context_pred([str](const json_pointer_t& pointer) + return register_style([str](const json_pointer_t& pointer) { return (pointer.cbegin() != pointer.cend()) && (*pointer.crbegin() == str); diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index a3ec2705b..5c7a28de6 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -10956,7 +10956,7 @@ class basic_print_stylizer // Predicate is conceptually 'bool (json)' here template - auto register_style_object_pred( + auto register_style( Predicate p, print_style style = print_style()) -> typename std::enable_if::value, print_style&>::type @@ -10977,7 +10977,7 @@ class basic_print_stylizer // 'metawrapper'. So if you pass 'bool (json)', it // will look like it can pass a metawrapper to it template - auto register_style_context_pred( + auto register_style( Predicate p, print_style style = print_style()) -> typename std::enable_if < !details::takes_arguments::value, print_style& >::type @@ -10994,7 +10994,7 @@ class basic_print_stylizer string_t str, print_style style = print_style()) { - return register_style_context_pred([str](const json_pointer_t& pointer) + return register_style([str](const json_pointer_t& pointer) { return (pointer.cbegin() != pointer.cend()) && (*pointer.crbegin() == str); diff --git a/test/src/unit-fancy-serialization.cpp b/test/src/unit-fancy-serialization.cpp index 6299bfef2..99aec3bd1 100644 --- a/test/src/unit-fancy-serialization.cpp +++ b/test/src/unit-fancy-serialization.cpp @@ -286,7 +286,7 @@ TEST_CASE("serialization") print_stylizer stylizer; stylizer.get_default_style() = print_style::preset_multiline; - stylizer.register_style_context_pred( + stylizer.register_style( [] (const json_pointer& context) { // Matches if context[-2] is "each elem on one line" @@ -332,7 +332,7 @@ TEST_CASE("serialization") print_stylizer stylizer; stylizer.get_default_style() = print_style::preset_multiline; - stylizer.register_style_object_pred( + stylizer.register_style( [] (const json & j) { return j.type() == json::value_t::array; From 6ce7cc04704afa63f42b8ac6fe588b0533d06ce4 Mon Sep 17 00:00:00 2001 From: Evan Driscoll Date: Tue, 5 Jun 2018 23:01:31 -0500 Subject: [PATCH 39/41] Write documentation on styles --- doc/StyledPrettyPrinting.md | 103 ++++++++++++++++++++++++++++++++++++ 1 file changed, 103 insertions(+) create mode 100644 doc/StyledPrettyPrinting.md diff --git a/doc/StyledPrettyPrinting.md b/doc/StyledPrettyPrinting.md new file mode 100644 index 000000000..6b5857c34 --- /dev/null +++ b/doc/StyledPrettyPrinting.md @@ -0,0 +1,103 @@ +# Styled Pretty Printing + +The library provides a pretty printer supporting flexible customization of the layout. There are two concepts used by the library: + +* A _style_ defines how printed objects are formatted. +* A _stylizer_ determines what style to use, based on the current context being printed. + +At some level, this functionality is analogous to a mix between a source code formatter (like clang-format or astyle) and CSS. + +## Styles -- Uniform Formatting + +If you want the entire document to be formatted the same, all you need to do is provide a single `nlohmann::print_style` instance to the `nlohmann::styled_dump` function. There are three “presets” you can use, `print_style::preset_compact()`, `print_style::preset_one_line()`, and `print_style::multiline()`. When creating a new `print_style` object, the default matches `preset_compact`. + +Here is how to use the presets along with `styled_dump`: + + using nlohmann::json; + using nlohmann::print_style; + using nlohmann::styled_dump; + + json j = {"foo", 1, 2, 3, false, {{"one", 1}}} + + styled_dump(std::cout, j, print_style::preset_compact()); + // Result: ["foo",1,2,3,false,{"one":1}] + + styled_dump(std::cout, j, print_style::preset_one_line()); + // Result: ["foo", 1, 2, 3, false, {"one": 1}] + + styled_dump(std::cout, j, print_style::multiline()); + // Result (no comment symbols, of course): + //[ + // "foo", + // 1, + // 2, + // 3, + // false, + // { + // "one": 1 + // } + //] + +Styles can, of course, be customized. The following fields are provided: + +### `print_style::indent_step` and `print_style::indent_char` + +These fields provide the step width and character to use for indentation. The step is in number of characters; e.g., don't set `indent_step` 8 and set `indent_char` to `'\t'`. + +This parameter is ignored if no indentation occurs. + +:warning: Currently, it is not possible to change indentation depending on context. + +:question: Is that something we want to allow? + +### `strings_maximum_length` + +This will truncate strings internally that are too long. The value is the maximum length of the string, not counting the quotation marks (which are always printed). It cannot be 0. + +If the string is short enough, it will be displayed in full. Otherwise, space permitting, it will print the longest prefix possible, followed by `...`, followed by the last character of the string. If it is too long even for that, it will leave off the last character, then the prefix entirely, and then start shortening the ellipsis. + +Example: + + print_style style; + style.strings_maximum_length = 10; + + json j = "The quick brown fox jumps over the lazy brown dog"; + styled_dump(std::cout, j, style); + // Prints: "The qu...g" (including quotes) + +### :exclamation: TODO: `list_maximum_length` + +This is analogous to `strings_maximum_length`, but for lists. When this option takes effect, the result will not be valid JSON. + +### `depth_limit` + +This option specifies a maximum recursion depth beyond which objects should be elided, replaced with ellipses. A `0` value will print values at the top-level, but compound objects (arrays and objects) will print with ellipses internally. + +Example: + + print_style style = print_style::preset_one_line(); + style.depth_limit = 1; + + json j = {1, {1}}; + styled_dump(std::cout, j, style); + +### `space_after_colon`, `space_after_comma` + +These Boolean values specify whether a space should be printed after their respective separators. The setting of these options makes the difference between `preset_compact` and `preset_one_line`. + +:question: [Issue 229](https://github.com/nlohmann/json/issues/229) has a number of comments, and implementation work, with a similar structure to mine, but with strings that had what to print here. So instead of `space_after_colon`, there was a string the user would set to either `":"` or `": "` as appropriate. I could change to that approach, but I think I prefer this one. I am dogmatically enforcing that the setting not result in syntactically-incorrect JSON. Related to `multiline`, next, it means I don't have to look through that string for a `\n` to see if the next line needs to indent. The main thing I can think of here is maybe the user would sometimes want to print *multiple* spaces, or a tab -- but (i) that seems easy enough to add (especially multiple spaces), and (ii) I suspect the main reason to do this is alignment, and that would need to know more about context. + +:question: Another alternative choice I could have made here (I think better-founded) is to combine these into one `space_after_separator`. This would sort of bring this in alignment with `multiline` -- e.g., there is no way (within a single style; it can be done with stylizers, later) to make objects multi-line and arrays single-line. + +### `multiline` + +This indicates whether to print newlines separating list entries or object key/value pairs. It is the difference between `preset_one_line` and `preset_multiline`. + +### :exclamation: TODO: Other future attributes + +Some other things I think could be wanted/useful: + +* `ensure_ascii` -- this is currently a fixed argument passed through the serializers's `dump` function, but should probably be split out here +* `array_max_length` -- inspired by [this comment](https://github.com/nlohmann/json/issues/229#issuecomment-300783790), where a user wanted to be able to print a nine-element list as a 3x3 array (three lines, three elements/line). This should be easy to do for simple cases, but I suspect that aligning into columns might be useful and that'd be harder. +* `spaces_inside_braces`/`spaces_inside_brackets` -- `[ 1, 2 ]` and `{ "key": 1 }` +* Anything that might affect the formatting of numbers? From e2974c38e4153d3a23790519305aeaead65b6675 Mon Sep 17 00:00:00 2001 From: Evan Driscoll Date: Tue, 5 Jun 2018 22:51:11 -0500 Subject: [PATCH 40/41] Rename fancy->styled --- .../detail/output/fancy_serializer.hpp | 22 +++---- include/nlohmann/json.hpp | 2 +- single_include/nlohmann/json.hpp | 24 ++++---- test/src/unit-fancy-serialization.cpp | 60 +++++++++---------- 4 files changed, 54 insertions(+), 54 deletions(-) diff --git a/include/nlohmann/detail/output/fancy_serializer.hpp b/include/nlohmann/detail/output/fancy_serializer.hpp index 7e57e1c79..6033035dc 100644 --- a/include/nlohmann/detail/output/fancy_serializer.hpp +++ b/include/nlohmann/detail/output/fancy_serializer.hpp @@ -214,7 +214,7 @@ namespace detail /////////////////// template -class fancy_serializer +class styled_serializer { using stylizer_t = basic_print_stylizer; using primitive_serializer_t = primitive_serializer; @@ -231,15 +231,15 @@ class fancy_serializer @param[in] s output stream to serialize to @param[in] ichar indentation character to use */ - fancy_serializer(output_adapter_t s, - const stylizer_t& st) + styled_serializer(output_adapter_t s, + const stylizer_t& st) : o(std::move(s)), stylizer(st), indent_string(512, st.get_default_style().indent_char) {} // delete because of pointer members - fancy_serializer(const fancy_serializer&) = delete; - fancy_serializer& operator=(const fancy_serializer&) = delete; + styled_serializer(const styled_serializer&) = delete; + styled_serializer& operator=(const styled_serializer&) = delete; void dump(const BasicJsonType& val, const bool ensure_ascii) { @@ -517,7 +517,7 @@ class fancy_serializer } private: - /// the output of the fancy_serializer + /// the output of the styled_serializer output_adapter_t o = nullptr; /// Used for serializing "base" objects. Strings are sort of @@ -533,20 +533,20 @@ class fancy_serializer } template -std::ostream& fancy_dump(std::ostream& o, const BasicJsonType& j, - basic_print_stylizer const& stylizer) +std::ostream& styled_dump(std::ostream& o, const BasicJsonType& j, + basic_print_stylizer const& stylizer) { // do the actual serialization - detail::fancy_serializer s(detail::output_adapter(o), stylizer); + detail::styled_serializer s(detail::output_adapter(o), stylizer); s.dump(j, false); return o; } template -std::ostream& fancy_dump(std::ostream& o, const BasicJsonType& j, print_style style) +std::ostream& styled_dump(std::ostream& o, const BasicJsonType& j, print_style style) { basic_print_stylizer stylizer(style); - return fancy_dump(o, j, stylizer); + return styled_dump(o, j, stylizer); } } diff --git a/include/nlohmann/json.hpp b/include/nlohmann/json.hpp index bfe032045..4512ad746 100644 --- a/include/nlohmann/json.hpp +++ b/include/nlohmann/json.hpp @@ -167,7 +167,7 @@ class basic_json friend ::nlohmann::json_pointer; friend ::nlohmann::detail::parser; friend ::nlohmann::detail::serializer; - friend ::nlohmann::detail::fancy_serializer; + friend ::nlohmann::detail::styled_serializer; template friend class ::nlohmann::detail::iter_impl; template diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 5c7a28de6..405cc80ee 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -11019,7 +11019,7 @@ namespace detail /////////////////// template -class fancy_serializer +class styled_serializer { using stylizer_t = basic_print_stylizer; using primitive_serializer_t = primitive_serializer; @@ -11036,15 +11036,15 @@ class fancy_serializer @param[in] s output stream to serialize to @param[in] ichar indentation character to use */ - fancy_serializer(output_adapter_t s, - const stylizer_t& st) + styled_serializer(output_adapter_t s, + const stylizer_t& st) : o(std::move(s)), stylizer(st), indent_string(512, st.get_default_style().indent_char) {} // delete because of pointer members - fancy_serializer(const fancy_serializer&) = delete; - fancy_serializer& operator=(const fancy_serializer&) = delete; + styled_serializer(const styled_serializer&) = delete; + styled_serializer& operator=(const styled_serializer&) = delete; void dump(const BasicJsonType& val, const bool ensure_ascii) { @@ -11322,7 +11322,7 @@ class fancy_serializer } private: - /// the output of the fancy_serializer + /// the output of the styled_serializer output_adapter_t o = nullptr; /// Used for serializing "base" objects. Strings are sort of @@ -11338,20 +11338,20 @@ class fancy_serializer } template -std::ostream& fancy_dump(std::ostream& o, const BasicJsonType& j, - basic_print_stylizer const& stylizer) +std::ostream& styled_dump(std::ostream& o, const BasicJsonType& j, + basic_print_stylizer const& stylizer) { // do the actual serialization - detail::fancy_serializer s(detail::output_adapter(o), stylizer); + detail::styled_serializer s(detail::output_adapter(o), stylizer); s.dump(j, false); return o; } template -std::ostream& fancy_dump(std::ostream& o, const BasicJsonType& j, print_style style) +std::ostream& styled_dump(std::ostream& o, const BasicJsonType& j, print_style style) { basic_print_stylizer stylizer(style); - return fancy_dump(o, j, stylizer); + return styled_dump(o, j, stylizer); } } @@ -11570,7 +11570,7 @@ class basic_json friend ::nlohmann::json_pointer; friend ::nlohmann::detail::parser; friend ::nlohmann::detail::serializer; - friend ::nlohmann::detail::fancy_serializer; + friend ::nlohmann::detail::styled_serializer; template friend class ::nlohmann::detail::iter_impl; template diff --git a/test/src/unit-fancy-serialization.cpp b/test/src/unit-fancy-serialization.cpp index 99aec3bd1..197a75257 100644 --- a/test/src/unit-fancy-serialization.cpp +++ b/test/src/unit-fancy-serialization.cpp @@ -34,7 +34,7 @@ SOFTWARE. using nlohmann::json; using nlohmann::json_pointer; -using nlohmann::fancy_dump; +using nlohmann::styled_dump; using nlohmann::print_style; using nlohmann::print_stylizer; @@ -73,17 +73,17 @@ std::string dedent(const char* str) return ans; } -std::string fancy_to_string(json j, print_style style = print_style()) +std::string styled_to_string(json j, print_style style = print_style()) { std::stringstream ss; - fancy_dump(ss, j, style); + styled_dump(ss, j, style); return ss.str(); } -std::string fancy_to_string(json j, print_stylizer stylizer) +std::string styled_to_string(json j, print_stylizer stylizer) { std::stringstream ss; - fancy_dump(ss, j, stylizer); + styled_dump(ss, j, stylizer); return ss.str(); } @@ -93,31 +93,31 @@ TEST_CASE("serialization") { SECTION("null") { - auto str = fancy_to_string({}); + auto str = styled_to_string({}); CHECK(str == "null"); } SECTION("true") { - auto str = fancy_to_string(true); + auto str = styled_to_string(true); CHECK(str == "true"); } SECTION("false") { - auto str = fancy_to_string(false); + auto str = styled_to_string(false); CHECK(str == "false"); } SECTION("integer") { - auto str = fancy_to_string(10); + auto str = styled_to_string(10); CHECK(str == "10"); } SECTION("floating point") { - auto str = fancy_to_string(7.5); + auto str = styled_to_string(7.5); CHECK(str == "7.5"); } } @@ -126,7 +126,7 @@ TEST_CASE("serialization") { SECTION("long strings usually print") { - auto str = fancy_to_string( + auto str = styled_to_string( "The quick brown fox jumps over the lazy brown dog"); CHECK(str == "\"The quick brown fox jumps over the lazy brown dog\""); @@ -137,7 +137,7 @@ TEST_CASE("serialization") print_style style; style.strings_maximum_length = 10; - auto str = fancy_to_string( + auto str = styled_to_string( "The quick brown fox jumps over the lazy brown dog", style); CHECK(str == "\"The qu...g\""); @@ -160,7 +160,7 @@ TEST_CASE("serialization") { print_style style; style.strings_maximum_length = test.first; - auto str = fancy_to_string(quick, style); + auto str = styled_to_string(quick, style); CHECK(str == test.second); } } @@ -170,7 +170,7 @@ TEST_CASE("serialization") print_style style; style.strings_maximum_length = 0; - auto str = fancy_to_string( + auto str = styled_to_string( "The quick brown fox jumps over the lazy brown dog", style); CHECK(str == @@ -182,7 +182,7 @@ TEST_CASE("serialization") print_style style; style.strings_maximum_length = 100; - auto str = fancy_to_string( + auto str = styled_to_string( "The quick brown fox jumps over the lazy brown dog", style); CHECK(str == @@ -200,12 +200,12 @@ TEST_CASE("serialization") print_style style; style.depth_limit = 1; - auto str_flat = fancy_to_string({1, {1}}, style); + auto str_flat = styled_to_string({1, {1}}, style); CHECK(str_flat == "[1,[...]]"); style = print_style::preset_multiline; style.depth_limit = 1; - auto str_lines = fancy_to_string({1, {1}}, style); + auto str_lines = styled_to_string({1, {1}}, style); CHECK(str_lines == dedent(R"( [ 1, @@ -218,12 +218,12 @@ TEST_CASE("serialization") print_style style; style.depth_limit = 1; - auto str_flat = fancy_to_string({1, {{"one", 1}}}, style); + auto str_flat = styled_to_string({1, {{"one", 1}}}, style); CHECK(str_flat == "[1,{...}]"); style = print_style::preset_multiline; style.depth_limit = 1; - auto str_lines = fancy_to_string({1, {{"one", 1}}}, style); + auto str_lines = styled_to_string({1, {{"one", 1}}}, style); CHECK(str_lines == dedent(R"( [ 1, @@ -240,7 +240,7 @@ TEST_CASE("serialization") stylizer.get_default_style() = print_style::preset_multiline; stylizer.register_key_matcher_style("one line"); - auto str = fancy_to_string( + auto str = styled_to_string( { { "one line", {1, 2} @@ -267,7 +267,7 @@ TEST_CASE("serialization") stylizer.get_default_style() = print_style::preset_multiline; stylizer.register_key_matcher_style("one line"); - auto str = fancy_to_string( + auto str = styled_to_string( { { "one line", {{"still one line", {1, 2}}} @@ -295,7 +295,7 @@ TEST_CASE("serialization") } ).space_after_comma = true; - auto str = fancy_to_string( + auto str = styled_to_string( { { "each elem on one line", { @@ -339,7 +339,7 @@ TEST_CASE("serialization") } ) = print_style::preset_one_line; - auto str = fancy_to_string( + auto str = styled_to_string( { { "an array", {1, 2, 3} @@ -366,7 +366,7 @@ TEST_CASE("serialization") { print_style style; style.space_after_comma = true; - auto str = fancy_to_string({1, 2, 3}, style); + auto str = styled_to_string({1, 2, 3}, style); CHECK(str == "[1, 2, 3]"); } @@ -374,7 +374,7 @@ TEST_CASE("serialization") { print_style style; style.space_after_colon = true; - auto str = fancy_to_string({{"one", 1}}, style); + auto str = styled_to_string({{"one", 1}}, style); CHECK(str == "{\"one\": 1}"); } @@ -382,7 +382,7 @@ TEST_CASE("serialization") { print_style style = print_style::preset_multiline; style.space_after_colon = false; - auto str = fancy_to_string({{"one", 1}}, style); + auto str = styled_to_string({{"one", 1}}, style); CHECK(str == dedent(R"( { "one":1 @@ -394,7 +394,7 @@ TEST_CASE("serialization") SECTION("given width") { print_style style = print_style::preset_multiline; - auto str = fancy_to_string({"foo", 1, 2, 3, false, {{"one", 1}}}, style); + auto str = styled_to_string({"foo", 1, 2, 3, false, {{"one", 1}}}, style); CHECK(str == dedent(R"( [ "foo", @@ -414,7 +414,7 @@ TEST_CASE("serialization") style.indent_step = 1; style.indent_char = '\t'; - auto str = fancy_to_string({"foo", 1, 2, 3, false, {{"one", 1}}}, style); + auto str = styled_to_string({"foo", 1, 2, 3, false, {{"one", 1}}}, style); CHECK(str == "[\n" "\t\"foo\",\n" @@ -435,7 +435,7 @@ TEST_CASE("serialization") style.indent_step = 300; style.indent_char = 'X'; - auto str = fancy_to_string({1, {1}}, style); + auto str = styled_to_string({1, {1}}, style); std::string indent(300, 'X'); CHECK(str == @@ -453,7 +453,7 @@ TEST_CASE("serialization") style.indent_step = 300; style.indent_char = 'X'; - auto str = fancy_to_string({{"key", {{"key", 1}}}}, style); + auto str = styled_to_string({{"key", {{"key", 1}}}}, style); std::string indent(300, 'X'); CHECK(str == From 47acb302d21f65913703b435baf1c2ce3026c37b Mon Sep 17 00:00:00 2001 From: Evan Driscoll Date: Wed, 6 Jun 2018 00:21:02 -0500 Subject: [PATCH 41/41] Write stylizer documentation --- doc/StyledPrettyPrinting.md | 129 ++++++++++++++++++++++++++++++++++++ 1 file changed, 129 insertions(+) diff --git a/doc/StyledPrettyPrinting.md b/doc/StyledPrettyPrinting.md index 6b5857c34..141da8f5a 100644 --- a/doc/StyledPrettyPrinting.md +++ b/doc/StyledPrettyPrinting.md @@ -101,3 +101,132 @@ Some other things I think could be wanted/useful: * `array_max_length` -- inspired by [this comment](https://github.com/nlohmann/json/issues/229#issuecomment-300783790), where a user wanted to be able to print a nine-element list as a 3x3 array (three lines, three elements/line). This should be easy to do for simple cases, but I suspect that aligning into columns might be useful and that'd be harder. * `spaces_inside_braces`/`spaces_inside_brackets` -- `[ 1, 2 ]` and `{ "key": 1 }` * Anything that might affect the formatting of numbers? + +## Stylizers -- Flexible Formatting + +You may want to change the style used by the printer depending on the context or the object being printed. Stylizers provide a way to do this. Harking back to the analogy about code formatters and CSS, stylizers implement the CSS portion of that analogy. As the pretty printer performs its recursive traversal, it will query the stylizer to determine what style changes (if any) should be applied to the current subtree. (Like CSS, those changes only apply to that subtree, and are automatically unapplied when the subtree is finished.) + +:exclamation: At the moment, the above description is a bit wrong -- the stylizer doesn't provide *changes*, it provides a complete new style. It's as if for every CSS selector you provide, you have to list all possible CSS attributes or let them be the default value; it can't cascade up from the context, *whatever* that might be. This should be fixed by the final version of this PR. Basically, fields of `print_style` will conceptually become `optional<.>`, where `none` indicates that the value should bubble up from the parent context. (In actuality, I'll be using something other than `optional` that will be both more compact and not be C++17. :-)) + +A stylizer stores a list of predicates on the current context and node. As the serializer visits each node in the tree, the stylizer will query each predicate in turn to determine if it wants to style the current node, appyling a corresponding style provided with the predicate if so. + +:exclamation: Because of the prior point, this is also wrong. Right now it just stops at the first predicate that returns `true`, and takes its style. This actually in some sense means it's doing the exact opposite of what it will eventually do -- the *last* predicate to run will be the one that applies. + +:question: Actually now that I type that out, maybe instead of predicates, it should just be a list of functions that are allowed to mutate, if they're interested, the style. Hmmm. + +Each predicate can take either or both of two parameters: + +* A `json_pointer` object providing the context to the current node. +* The `json` object (or rather, a `const&` to it) that is the root of the current subtree. + +Predicates and styles are registered with the function `register_style`. + +In addition, there is a convenience function, `register_key_matcher_style`; given a string, this will generate a context predicate that will trigger when the current object is the value of the associated key. For example, given `"foo"`, it will trigger for (and hence style) `[1, 2]` in `{"foo": [1, 2], "bar": 7}`. + +Both `register_style` and `register_key_matcher_style` can be used in a couple different ways, depending on how many changes to the style are needed: + +* If a `print_style` object is already available, it can be passed as a second argument: `stylizer.register_style(pred, my_style)` +* `register_style` and `register_key_matcher_style` return a mutable reference to the style that will be used when the predicate matches, so it can be used as, for example, `stylizer.register_style(pred) = print_style::preset_one_line()` +* `last_registered_style()` returns a mutable reference to the last style added with a `register` call + +:question: I think I want to remove the mutable reference return thing and remove `last_registered_style` entirely. Those were convenience things from before I had the presets, `last_registered_style` isn't even needed or used in my tests any more, and the second bullet point is only used once in a way that isn't trivial (`stylizer.register_style(...).space_after_comma = true`). Or I could remove `last_registered_style()` but leave the mutable return on `register`. Thoughts? + +The predicate can be any object that is callable as `p(json)`, `p(json_pointer)`, or `p(json_pointer, json)`. (It probably expects a `const&` for each, as mentioned above.) + +A stylizer can be provided a default `print_style` that is used for the root object. + +:question: I feel like there's some uglyness here. To get the overloaded `register_style` functions, I had to do some template hackery to avoid ambiguous calls, *and* to avoid needing to go through two `std::function` calls. ("And" as in if you wanted to do either and keep the ability to pass both `pred(json)` and `pred(json_pointer)` using functions with the same name, you'd hit my problems.) I don't feel confident that this is as well-written as it could be. One problem I ran into was the use of `json_pointer`. (1) I had to add accessors so predicates can to get to the components of it (see examples, below). (2) I'm not happy with those accessors. Should it have `size()` and `[]` too? `begin` and `end`? If yes on `begin` and `end`, should they return `iterator` (and so `json_pointer` now has a mutable API) or `const_iterator`? (3) There are (apparent) implicit conversions both from `json` to `json_pointer` and vice versa, which made my template hackery on `register_style` a lot harder to figure out, though only slightly more complex to actually implement and explain. (Note: the conversion from `json` to `json_pointer` will fail, but it's a hard error and not SFIANE-friendly. Actually, now that I think about it -- disabling that conversion would be very plesant.) + +:question: So one specific idea is to not use `json_pointer`. Another thought I had was that it could pass the entire line of `json` objects (or rather, `const*`) along with the textual path, in case that's useful to anyone. + +Example of `register_key_matcher_style`: + + print_stylizer stylizer(print_style::preset_multiline()); + stylizer.register_key_matcher_style("one line", print_style::preset_one_line()); + + json j = { + {"one line", {1, 2}}, + {"two lines", {1, 2}} + }; + styled_dump(std::cout, j, stylizer); + + // Prints (no comments): + //{ + // "one line": [1,2], + // "two lines": [ + // 1, + // 2 + // ] + //} + +Example of `register_style` with a context-based predicate: + + print_stylizer stylizer(print_style::preset_multiline()); + + stylizer.register_style( + [] (const json_pointer& context) + { + // Matches if context[-2] is "each elem on one line" + // -- so uses preset_one_line for *subobjects* of that. + // (register_key_matcher_style would put the list + // subobject on one line, but we want elements on multiple + // lines.) + return (context.cend() - context.cbegin() >= 2) + && (*(context.cend() - 2) == "each elem on one line"); + } + ) = print_style::preset_one_line(); + + json j = { + { + "each elem on one line", { + {1, 2, 3, 4, 5}, + {1, 2, 3, 4, 5} + }, + }, + { + "fully multiline", { + {1, 2, 3}, + } + }; + + styled_dump(std::cout, j, stylizer); + // Prints (no comments): + //{ + // "each elem on one line": [ + // [1, 2, 3, 4, 5], + // [1, 2, 3, 4, 5] + // ], + // "fully multiline": [ + // [ + // 1, + // 2, + // 3 + // ] + // ] + //} + +Example of `register_style` with an object predicate: + + print_stylizer stylizer; + stylizer.get_default_style() = print_style::preset_multiline; + + stylizer.register_style( + [] (const json & j) + { + return j.type() == json::value_t::array; + } + ) = print_style::preset_one_line; + + json j = { + {"an array", {1, 2, 3}}, + {"an object", {{"key", "val"}}} + }; + + styled_dump(std::cout, j, stylizer); + // Prints (no comment): + //{ + // "an array": [1, 2, 3], + // "an object": { + // "key": "val" + // } + //}