mirror of
https://github.com/nlohmann/json.git
synced 2026-09-10 18:27:59 +00:00
Split the write-buffer helpers and write indentation directly
Follow-up to @gregmarr's review: put_chars() was doing four unrelated jobs, so give the two that can be made safe their own entry points. - put_literal(): takes the literal by reference and deduces the length from the array bound, so the 27 hand-counted lengths at the call sites can no longer drift from the literals they describe. A literal is checked at compile time to fit the buffer, so this path needs no write-through branch. - put_buffer(): takes the fixed-size buffer itself rather than a bare pointer, so the length can be checked against the buffer's own bound. - put_indent(): memsets the indentation into the write buffer, filling and flushing it as needed. This removes indent_string entirely, and with it both bugs of #5186: the indentation string was grown by doubling, which is not enough when indent_step more than doubles it (a heap over-read - dump(2000) read 2000 bytes out of a 1024-byte string), and the grown part was filled with a space instead of the configured indent_char. next_indent() keeps that PR's assertion against the unsigned indentation accumulation wrapping on deep nesting. put_chars() keeps the two cases that are genuinely a pointer and a count: the run-length copies out of the string being escaped, and to_chars() output. Tests cover an indent_step wider than the write buffer, a non-space indentation character past the old growth point, and nesting whose accumulated indentation spans several buffer-fulls. All three fail against develop. Signed-off-by: Niels Lohmann <mail@nlohmann.me>
This commit is contained in:
@@ -9,14 +9,14 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <algorithm> // reverse, remove, fill, find, none_of
|
#include <algorithm> // reverse, remove, fill, find, none_of, min
|
||||||
#include <array> // array
|
#include <array> // array
|
||||||
#include <clocale> // localeconv, lconv
|
#include <clocale> // localeconv, lconv
|
||||||
#include <cmath> // labs, isfinite, isnan, signbit
|
#include <cmath> // labs, isfinite, isnan, signbit
|
||||||
#include <cstddef> // size_t, ptrdiff_t
|
#include <cstddef> // size_t, ptrdiff_t
|
||||||
#include <cstdint> // uint8_t
|
#include <cstdint> // uint8_t
|
||||||
#include <cstdio> // snprintf
|
#include <cstdio> // snprintf
|
||||||
#include <cstring> // memcpy
|
#include <cstring> // memcpy, memset
|
||||||
#include <limits> // numeric_limits
|
#include <limits> // numeric_limits
|
||||||
#include <string> // string, char_traits
|
#include <string> // string, char_traits
|
||||||
#include <iomanip> // setfill, setw
|
#include <iomanip> // setfill, setw
|
||||||
@@ -73,7 +73,6 @@ class serializer
|
|||||||
, thousands_sep(loc->thousands_sep == nullptr ? '\0' : std::char_traits<char>::to_char_type(* (loc->thousands_sep)))
|
, thousands_sep(loc->thousands_sep == nullptr ? '\0' : std::char_traits<char>::to_char_type(* (loc->thousands_sep)))
|
||||||
, decimal_point(loc->decimal_point == nullptr ? '\0' : std::char_traits<char>::to_char_type(* (loc->decimal_point)))
|
, decimal_point(loc->decimal_point == nullptr ? '\0' : std::char_traits<char>::to_char_type(* (loc->decimal_point)))
|
||||||
, indent_char(ichar)
|
, indent_char(ichar)
|
||||||
, indent_string(512, indent_char)
|
|
||||||
, error_handler(error_handler_)
|
, error_handler(error_handler_)
|
||||||
{}
|
{}
|
||||||
|
|
||||||
@@ -137,44 +136,40 @@ class serializer
|
|||||||
{
|
{
|
||||||
if (val.m_data.m_value.object->empty())
|
if (val.m_data.m_value.object->empty())
|
||||||
{
|
{
|
||||||
put_chars("{}", 2);
|
put_literal("{}");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pretty_print)
|
if (pretty_print)
|
||||||
{
|
{
|
||||||
put_chars("{\n", 2);
|
put_literal("{\n");
|
||||||
|
|
||||||
// variable to hold indentation for recursive calls
|
// variable to hold indentation for recursive calls
|
||||||
const auto new_indent = current_indent + indent_step;
|
const auto new_indent = next_indent(current_indent, indent_step);
|
||||||
if (JSON_HEDLEY_UNLIKELY(indent_string.size() < new_indent))
|
|
||||||
{
|
|
||||||
indent_string.resize(indent_string.size() * 2, ' ');
|
|
||||||
}
|
|
||||||
|
|
||||||
// first n-1 elements
|
// first n-1 elements
|
||||||
auto i = val.m_data.m_value.object->cbegin();
|
auto i = val.m_data.m_value.object->cbegin();
|
||||||
for (std::size_t cnt = 0; cnt < val.m_data.m_value.object->size() - 1; ++cnt, ++i)
|
for (std::size_t cnt = 0; cnt < val.m_data.m_value.object->size() - 1; ++cnt, ++i)
|
||||||
{
|
{
|
||||||
put_chars(indent_string.c_str(), new_indent);
|
put_indent(new_indent);
|
||||||
put_char('\"');
|
put_char('\"');
|
||||||
dump_escaped(i->first, ensure_ascii);
|
dump_escaped(i->first, ensure_ascii);
|
||||||
put_chars("\": ", 3);
|
put_literal("\": ");
|
||||||
dump_internal(i->second, true, ensure_ascii, indent_step, new_indent);
|
dump_internal(i->second, true, ensure_ascii, indent_step, new_indent);
|
||||||
put_chars(",\n", 2);
|
put_literal(",\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
// last element
|
// last element
|
||||||
JSON_ASSERT(i != val.m_data.m_value.object->cend());
|
JSON_ASSERT(i != val.m_data.m_value.object->cend());
|
||||||
JSON_ASSERT(std::next(i) == val.m_data.m_value.object->cend());
|
JSON_ASSERT(std::next(i) == val.m_data.m_value.object->cend());
|
||||||
put_chars(indent_string.c_str(), new_indent);
|
put_indent(new_indent);
|
||||||
put_char('\"');
|
put_char('\"');
|
||||||
dump_escaped(i->first, ensure_ascii);
|
dump_escaped(i->first, ensure_ascii);
|
||||||
put_chars("\": ", 3);
|
put_literal("\": ");
|
||||||
dump_internal(i->second, true, ensure_ascii, indent_step, new_indent);
|
dump_internal(i->second, true, ensure_ascii, indent_step, new_indent);
|
||||||
|
|
||||||
put_char('\n');
|
put_char('\n');
|
||||||
put_chars(indent_string.c_str(), current_indent);
|
put_indent(current_indent);
|
||||||
put_char('}');
|
put_char('}');
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -187,7 +182,7 @@ class serializer
|
|||||||
{
|
{
|
||||||
put_char('\"');
|
put_char('\"');
|
||||||
dump_escaped(i->first, ensure_ascii);
|
dump_escaped(i->first, ensure_ascii);
|
||||||
put_chars("\":", 2);
|
put_literal("\":");
|
||||||
dump_internal(i->second, false, ensure_ascii, indent_step, current_indent);
|
dump_internal(i->second, false, ensure_ascii, indent_step, current_indent);
|
||||||
put_char(',');
|
put_char(',');
|
||||||
}
|
}
|
||||||
@@ -197,7 +192,7 @@ class serializer
|
|||||||
JSON_ASSERT(std::next(i) == val.m_data.m_value.object->cend());
|
JSON_ASSERT(std::next(i) == val.m_data.m_value.object->cend());
|
||||||
put_char('\"');
|
put_char('\"');
|
||||||
dump_escaped(i->first, ensure_ascii);
|
dump_escaped(i->first, ensure_ascii);
|
||||||
put_chars("\":", 2);
|
put_literal("\":");
|
||||||
dump_internal(i->second, false, ensure_ascii, indent_step, current_indent);
|
dump_internal(i->second, false, ensure_ascii, indent_step, current_indent);
|
||||||
|
|
||||||
put_char('}');
|
put_char('}');
|
||||||
@@ -210,37 +205,33 @@ class serializer
|
|||||||
{
|
{
|
||||||
if (val.m_data.m_value.array->empty())
|
if (val.m_data.m_value.array->empty())
|
||||||
{
|
{
|
||||||
put_chars("[]", 2);
|
put_literal("[]");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pretty_print)
|
if (pretty_print)
|
||||||
{
|
{
|
||||||
put_chars("[\n", 2);
|
put_literal("[\n");
|
||||||
|
|
||||||
// variable to hold indentation for recursive calls
|
// variable to hold indentation for recursive calls
|
||||||
const auto new_indent = current_indent + indent_step;
|
const auto new_indent = next_indent(current_indent, indent_step);
|
||||||
if (JSON_HEDLEY_UNLIKELY(indent_string.size() < new_indent))
|
|
||||||
{
|
|
||||||
indent_string.resize(indent_string.size() * 2, ' ');
|
|
||||||
}
|
|
||||||
|
|
||||||
// first n-1 elements
|
// first n-1 elements
|
||||||
for (auto i = val.m_data.m_value.array->cbegin();
|
for (auto i = val.m_data.m_value.array->cbegin();
|
||||||
i != val.m_data.m_value.array->cend() - 1; ++i)
|
i != val.m_data.m_value.array->cend() - 1; ++i)
|
||||||
{
|
{
|
||||||
put_chars(indent_string.c_str(), new_indent);
|
put_indent(new_indent);
|
||||||
dump_internal(*i, true, ensure_ascii, indent_step, new_indent);
|
dump_internal(*i, true, ensure_ascii, indent_step, new_indent);
|
||||||
put_chars(",\n", 2);
|
put_literal(",\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
// last element
|
// last element
|
||||||
JSON_ASSERT(!val.m_data.m_value.array->empty());
|
JSON_ASSERT(!val.m_data.m_value.array->empty());
|
||||||
put_chars(indent_string.c_str(), new_indent);
|
put_indent(new_indent);
|
||||||
dump_internal(val.m_data.m_value.array->back(), true, ensure_ascii, indent_step, new_indent);
|
dump_internal(val.m_data.m_value.array->back(), true, ensure_ascii, indent_step, new_indent);
|
||||||
|
|
||||||
put_char('\n');
|
put_char('\n');
|
||||||
put_chars(indent_string.c_str(), current_indent);
|
put_indent(current_indent);
|
||||||
put_char(']');
|
put_char(']');
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -277,18 +268,14 @@ class serializer
|
|||||||
{
|
{
|
||||||
if (pretty_print)
|
if (pretty_print)
|
||||||
{
|
{
|
||||||
put_chars("{\n", 2);
|
put_literal("{\n");
|
||||||
|
|
||||||
// variable to hold indentation for recursive calls
|
// variable to hold indentation for recursive calls
|
||||||
const auto new_indent = current_indent + indent_step;
|
const auto new_indent = next_indent(current_indent, indent_step);
|
||||||
if (JSON_HEDLEY_UNLIKELY(indent_string.size() < new_indent))
|
|
||||||
{
|
|
||||||
indent_string.resize(indent_string.size() * 2, ' ');
|
|
||||||
}
|
|
||||||
|
|
||||||
put_chars(indent_string.c_str(), new_indent);
|
put_indent(new_indent);
|
||||||
|
|
||||||
put_chars("\"bytes\": [", 10);
|
put_literal("\"bytes\": [");
|
||||||
|
|
||||||
if (!val.m_data.m_value.binary->empty())
|
if (!val.m_data.m_value.binary->empty())
|
||||||
{
|
{
|
||||||
@@ -296,30 +283,30 @@ class serializer
|
|||||||
i != val.m_data.m_value.binary->cend() - 1; ++i)
|
i != val.m_data.m_value.binary->cend() - 1; ++i)
|
||||||
{
|
{
|
||||||
dump_integer(*i);
|
dump_integer(*i);
|
||||||
put_chars(", ", 2);
|
put_literal(", ");
|
||||||
}
|
}
|
||||||
dump_integer(val.m_data.m_value.binary->back());
|
dump_integer(val.m_data.m_value.binary->back());
|
||||||
}
|
}
|
||||||
|
|
||||||
put_chars("],\n", 3);
|
put_literal("],\n");
|
||||||
put_chars(indent_string.c_str(), new_indent);
|
put_indent(new_indent);
|
||||||
|
|
||||||
put_chars("\"subtype\": ", 11);
|
put_literal("\"subtype\": ");
|
||||||
if (val.m_data.m_value.binary->has_subtype())
|
if (val.m_data.m_value.binary->has_subtype())
|
||||||
{
|
{
|
||||||
dump_integer(val.m_data.m_value.binary->subtype());
|
dump_integer(val.m_data.m_value.binary->subtype());
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
put_chars("null", 4);
|
put_literal("null");
|
||||||
}
|
}
|
||||||
put_char('\n');
|
put_char('\n');
|
||||||
put_chars(indent_string.c_str(), current_indent);
|
put_indent(current_indent);
|
||||||
put_char('}');
|
put_char('}');
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
put_chars("{\"bytes\":[", 10);
|
put_literal("{\"bytes\":[");
|
||||||
|
|
||||||
if (!val.m_data.m_value.binary->empty())
|
if (!val.m_data.m_value.binary->empty())
|
||||||
{
|
{
|
||||||
@@ -332,7 +319,7 @@ class serializer
|
|||||||
dump_integer(val.m_data.m_value.binary->back());
|
dump_integer(val.m_data.m_value.binary->back());
|
||||||
}
|
}
|
||||||
|
|
||||||
put_chars("],\"subtype\":", 12);
|
put_literal("],\"subtype\":");
|
||||||
if (val.m_data.m_value.binary->has_subtype())
|
if (val.m_data.m_value.binary->has_subtype())
|
||||||
{
|
{
|
||||||
dump_integer(val.m_data.m_value.binary->subtype());
|
dump_integer(val.m_data.m_value.binary->subtype());
|
||||||
@@ -340,7 +327,7 @@ class serializer
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
put_chars("null}", 5);
|
put_literal("null}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
@@ -350,11 +337,11 @@ class serializer
|
|||||||
{
|
{
|
||||||
if (val.m_data.m_value.boolean)
|
if (val.m_data.m_value.boolean)
|
||||||
{
|
{
|
||||||
put_chars("true", 4);
|
put_literal("true");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
put_chars("false", 5);
|
put_literal("false");
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -379,13 +366,13 @@ class serializer
|
|||||||
|
|
||||||
case value_t::discarded:
|
case value_t::discarded:
|
||||||
{
|
{
|
||||||
put_chars("<discarded>", 11);
|
put_literal("<discarded>");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
case value_t::null:
|
case value_t::null:
|
||||||
{
|
{
|
||||||
put_chars("null", 4);
|
put_literal("null");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -394,6 +381,19 @@ class serializer
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief the indentation level to use for the children of the current value
|
||||||
|
|
||||||
|
A very large @a indent_step can wrap the unsigned accumulation on deep
|
||||||
|
nesting, which would silently truncate the indentation.
|
||||||
|
*/
|
||||||
|
static unsigned int next_indent(const unsigned int current_indent, const unsigned int indent_step)
|
||||||
|
{
|
||||||
|
const unsigned int new_indent = current_indent + indent_step;
|
||||||
|
JSON_ASSERT(new_indent >= current_indent);
|
||||||
|
return new_indent;
|
||||||
|
}
|
||||||
|
|
||||||
JSON_PRIVATE_UNLESS_TESTED:
|
JSON_PRIVATE_UNLESS_TESTED:
|
||||||
/*!
|
/*!
|
||||||
@brief dump escaped string
|
@brief dump escaped string
|
||||||
@@ -446,7 +446,7 @@ class serializer
|
|||||||
// preserve output order, then write the run directly
|
// preserve output order, then write the run directly
|
||||||
if (bytes != 0)
|
if (bytes != 0)
|
||||||
{
|
{
|
||||||
put_chars(string_buffer.data(), bytes);
|
put_buffer(string_buffer, bytes);
|
||||||
bytes = 0;
|
bytes = 0;
|
||||||
}
|
}
|
||||||
put_chars(s.data() + i, run);
|
put_chars(s.data() + i, run);
|
||||||
@@ -548,7 +548,7 @@ class serializer
|
|||||||
// written ("\uxxxx\uxxxx\0") for one code point
|
// written ("\uxxxx\uxxxx\0") for one code point
|
||||||
if (string_buffer.size() - bytes < 13)
|
if (string_buffer.size() - bytes < 13)
|
||||||
{
|
{
|
||||||
put_chars(string_buffer.data(), bytes);
|
put_buffer(string_buffer, bytes);
|
||||||
bytes = 0;
|
bytes = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -607,7 +607,7 @@ class serializer
|
|||||||
// written ("\uxxxx\uxxxx\0") for one code point
|
// written ("\uxxxx\uxxxx\0") for one code point
|
||||||
if (string_buffer.size() - bytes < 13)
|
if (string_buffer.size() - bytes < 13)
|
||||||
{
|
{
|
||||||
put_chars(string_buffer.data(), bytes);
|
put_buffer(string_buffer, bytes);
|
||||||
bytes = 0;
|
bytes = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -646,7 +646,7 @@ class serializer
|
|||||||
// write buffer
|
// write buffer
|
||||||
if (bytes > 0)
|
if (bytes > 0)
|
||||||
{
|
{
|
||||||
put_chars(string_buffer.data(), bytes);
|
put_buffer(string_buffer, bytes);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -662,22 +662,22 @@ class serializer
|
|||||||
case error_handler_t::ignore:
|
case error_handler_t::ignore:
|
||||||
{
|
{
|
||||||
// write all accepted bytes
|
// write all accepted bytes
|
||||||
put_chars(string_buffer.data(), bytes_after_last_accept);
|
put_buffer(string_buffer, bytes_after_last_accept);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case error_handler_t::replace:
|
case error_handler_t::replace:
|
||||||
{
|
{
|
||||||
// write all accepted bytes
|
// write all accepted bytes
|
||||||
put_chars(string_buffer.data(), bytes_after_last_accept);
|
put_buffer(string_buffer, bytes_after_last_accept);
|
||||||
// add a replacement character
|
// add a replacement character
|
||||||
if (ensure_ascii)
|
if (ensure_ascii)
|
||||||
{
|
{
|
||||||
put_chars("\\ufffd", 6);
|
put_literal("\\ufffd");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
put_chars("\xEF\xBF\xBD", 3);
|
put_literal("\xEF\xBF\xBD");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -713,6 +713,66 @@ class serializer
|
|||||||
adapter (after flushing what is pending), so large string/number payloads
|
adapter (after flushing what is pending), so large string/number payloads
|
||||||
are not copied an extra time.
|
are not copied an extra time.
|
||||||
*/
|
*/
|
||||||
|
/*!
|
||||||
|
@brief append @a indent indentation characters to the write buffer
|
||||||
|
|
||||||
|
Writes the indentation straight into the buffer instead of copying it out of
|
||||||
|
a pre-grown indentation string, so no auxiliary string has to be sized,
|
||||||
|
resized, or kept in sync with the deepest nesting level reached. An
|
||||||
|
indentation wider than the buffer simply fills and flushes it repeatedly.
|
||||||
|
*/
|
||||||
|
void put_indent(unsigned int indent)
|
||||||
|
{
|
||||||
|
while (indent > 0)
|
||||||
|
{
|
||||||
|
if (JSON_HEDLEY_UNLIKELY(write_buffer_pos == write_buffer.size()))
|
||||||
|
{
|
||||||
|
flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::size_t chunk = (std::min)(static_cast<std::size_t>(indent),
|
||||||
|
write_buffer.size() - write_buffer_pos);
|
||||||
|
std::memset(write_buffer.data() + write_buffer_pos, indent_char, chunk);
|
||||||
|
write_buffer_pos += chunk;
|
||||||
|
indent -= static_cast<unsigned int>(chunk);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief append a string literal to the write buffer
|
||||||
|
|
||||||
|
The length comes from the array bound rather than a hand-written count, so
|
||||||
|
it cannot drift out of sync with the literal. A literal always fits into the
|
||||||
|
buffer (checked at compile time), so unlike @ref put_chars this needs no
|
||||||
|
write-through path for oversized runs.
|
||||||
|
*/
|
||||||
|
template<std::size_t N>
|
||||||
|
void put_literal(const char (&s)[N])
|
||||||
|
{
|
||||||
|
static_assert(N >= 2, "put_literal expects a non-empty string literal");
|
||||||
|
static_assert(N - 1 < write_buffer_size, "string literal must fit into the write buffer");
|
||||||
|
|
||||||
|
if (JSON_HEDLEY_UNLIKELY(write_buffer_pos + (N - 1) > write_buffer.size()))
|
||||||
|
{
|
||||||
|
flush();
|
||||||
|
}
|
||||||
|
std::memcpy(write_buffer.data() + write_buffer_pos, s, N - 1);
|
||||||
|
write_buffer_pos += N - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief append the first @a length characters of a fixed-size buffer
|
||||||
|
|
||||||
|
Same as @ref put_chars, but the buffer carries its own bound, so the length
|
||||||
|
can be checked against it - which a bare pointer plus count cannot do.
|
||||||
|
*/
|
||||||
|
template<std::size_t N>
|
||||||
|
void put_buffer(const std::array<char, N>& buffer, std::size_t length)
|
||||||
|
{
|
||||||
|
JSON_ASSERT(length <= N);
|
||||||
|
put_chars(buffer.data(), length);
|
||||||
|
}
|
||||||
|
|
||||||
JSON_HEDLEY_NON_NULL(2)
|
JSON_HEDLEY_NON_NULL(2)
|
||||||
void put_chars(const char* s, std::size_t length)
|
void put_chars(const char* s, std::size_t length)
|
||||||
{
|
{
|
||||||
@@ -925,7 +985,7 @@ class serializer
|
|||||||
*(--buffer_ptr) = static_cast<char>('0' + abs_value);
|
*(--buffer_ptr) = static_cast<char>('0' + abs_value);
|
||||||
}
|
}
|
||||||
|
|
||||||
put_chars(number_buffer.data(), n_chars);
|
put_buffer(number_buffer, n_chars);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@@ -941,7 +1001,7 @@ class serializer
|
|||||||
// NaN / inf
|
// NaN / inf
|
||||||
if (!std::isfinite(x))
|
if (!std::isfinite(x))
|
||||||
{
|
{
|
||||||
put_chars("null", 4);
|
put_literal("null");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1013,7 +1073,7 @@ class serializer
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
put_chars(number_buffer.data(), static_cast<std::size_t>(len));
|
put_buffer(number_buffer, static_cast<std::size_t>(len));
|
||||||
|
|
||||||
// determine if we need to append ".0"
|
// determine if we need to append ".0"
|
||||||
const bool value_is_int_like =
|
const bool value_is_int_like =
|
||||||
@@ -1025,7 +1085,7 @@ class serializer
|
|||||||
|
|
||||||
if (value_is_int_like)
|
if (value_is_int_like)
|
||||||
{
|
{
|
||||||
put_chars(".0", 2);
|
put_literal(".0");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1130,15 +1190,14 @@ class serializer
|
|||||||
|
|
||||||
/// the indentation character
|
/// the indentation character
|
||||||
const char indent_char;
|
const char indent_char;
|
||||||
/// the indentation string
|
|
||||||
string_t indent_string;
|
|
||||||
|
|
||||||
/// error_handler how to react on decoding errors
|
/// error_handler how to react on decoding errors
|
||||||
const error_handler_t error_handler;
|
const error_handler_t error_handler;
|
||||||
|
|
||||||
/// buffer collecting output before it is flushed to the output adapter, so
|
/// buffer collecting output before it is flushed to the output adapter, so
|
||||||
/// that the many small structural writes become few bulk writes
|
/// that the many small structural writes become few bulk writes
|
||||||
std::array<char, 1024> write_buffer{{}};
|
static constexpr std::size_t write_buffer_size = 1024;
|
||||||
|
std::array<char, write_buffer_size> write_buffer{{}};
|
||||||
/// number of valid bytes currently held in @ref write_buffer
|
/// number of valid bytes currently held in @ref write_buffer
|
||||||
std::size_t write_buffer_pos = 0;
|
std::size_t write_buffer_pos = 0;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -20167,14 +20167,14 @@ NLOHMANN_JSON_NAMESPACE_END
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
#include <algorithm> // reverse, remove, fill, find, none_of
|
#include <algorithm> // reverse, remove, fill, find, none_of, min
|
||||||
#include <array> // array
|
#include <array> // array
|
||||||
#include <clocale> // localeconv, lconv
|
#include <clocale> // localeconv, lconv
|
||||||
#include <cmath> // labs, isfinite, isnan, signbit
|
#include <cmath> // labs, isfinite, isnan, signbit
|
||||||
#include <cstddef> // size_t, ptrdiff_t
|
#include <cstddef> // size_t, ptrdiff_t
|
||||||
#include <cstdint> // uint8_t
|
#include <cstdint> // uint8_t
|
||||||
#include <cstdio> // snprintf
|
#include <cstdio> // snprintf
|
||||||
#include <cstring> // memcpy
|
#include <cstring> // memcpy, memset
|
||||||
#include <limits> // numeric_limits
|
#include <limits> // numeric_limits
|
||||||
#include <string> // string, char_traits
|
#include <string> // string, char_traits
|
||||||
#include <iomanip> // setfill, setw
|
#include <iomanip> // setfill, setw
|
||||||
@@ -21359,7 +21359,6 @@ class serializer
|
|||||||
, thousands_sep(loc->thousands_sep == nullptr ? '\0' : std::char_traits<char>::to_char_type(* (loc->thousands_sep)))
|
, thousands_sep(loc->thousands_sep == nullptr ? '\0' : std::char_traits<char>::to_char_type(* (loc->thousands_sep)))
|
||||||
, decimal_point(loc->decimal_point == nullptr ? '\0' : std::char_traits<char>::to_char_type(* (loc->decimal_point)))
|
, decimal_point(loc->decimal_point == nullptr ? '\0' : std::char_traits<char>::to_char_type(* (loc->decimal_point)))
|
||||||
, indent_char(ichar)
|
, indent_char(ichar)
|
||||||
, indent_string(512, indent_char)
|
|
||||||
, error_handler(error_handler_)
|
, error_handler(error_handler_)
|
||||||
{}
|
{}
|
||||||
|
|
||||||
@@ -21423,44 +21422,40 @@ class serializer
|
|||||||
{
|
{
|
||||||
if (val.m_data.m_value.object->empty())
|
if (val.m_data.m_value.object->empty())
|
||||||
{
|
{
|
||||||
put_chars("{}", 2);
|
put_literal("{}");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pretty_print)
|
if (pretty_print)
|
||||||
{
|
{
|
||||||
put_chars("{\n", 2);
|
put_literal("{\n");
|
||||||
|
|
||||||
// variable to hold indentation for recursive calls
|
// variable to hold indentation for recursive calls
|
||||||
const auto new_indent = current_indent + indent_step;
|
const auto new_indent = next_indent(current_indent, indent_step);
|
||||||
if (JSON_HEDLEY_UNLIKELY(indent_string.size() < new_indent))
|
|
||||||
{
|
|
||||||
indent_string.resize(indent_string.size() * 2, ' ');
|
|
||||||
}
|
|
||||||
|
|
||||||
// first n-1 elements
|
// first n-1 elements
|
||||||
auto i = val.m_data.m_value.object->cbegin();
|
auto i = val.m_data.m_value.object->cbegin();
|
||||||
for (std::size_t cnt = 0; cnt < val.m_data.m_value.object->size() - 1; ++cnt, ++i)
|
for (std::size_t cnt = 0; cnt < val.m_data.m_value.object->size() - 1; ++cnt, ++i)
|
||||||
{
|
{
|
||||||
put_chars(indent_string.c_str(), new_indent);
|
put_indent(new_indent);
|
||||||
put_char('\"');
|
put_char('\"');
|
||||||
dump_escaped(i->first, ensure_ascii);
|
dump_escaped(i->first, ensure_ascii);
|
||||||
put_chars("\": ", 3);
|
put_literal("\": ");
|
||||||
dump_internal(i->second, true, ensure_ascii, indent_step, new_indent);
|
dump_internal(i->second, true, ensure_ascii, indent_step, new_indent);
|
||||||
put_chars(",\n", 2);
|
put_literal(",\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
// last element
|
// last element
|
||||||
JSON_ASSERT(i != val.m_data.m_value.object->cend());
|
JSON_ASSERT(i != val.m_data.m_value.object->cend());
|
||||||
JSON_ASSERT(std::next(i) == val.m_data.m_value.object->cend());
|
JSON_ASSERT(std::next(i) == val.m_data.m_value.object->cend());
|
||||||
put_chars(indent_string.c_str(), new_indent);
|
put_indent(new_indent);
|
||||||
put_char('\"');
|
put_char('\"');
|
||||||
dump_escaped(i->first, ensure_ascii);
|
dump_escaped(i->first, ensure_ascii);
|
||||||
put_chars("\": ", 3);
|
put_literal("\": ");
|
||||||
dump_internal(i->second, true, ensure_ascii, indent_step, new_indent);
|
dump_internal(i->second, true, ensure_ascii, indent_step, new_indent);
|
||||||
|
|
||||||
put_char('\n');
|
put_char('\n');
|
||||||
put_chars(indent_string.c_str(), current_indent);
|
put_indent(current_indent);
|
||||||
put_char('}');
|
put_char('}');
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -21473,7 +21468,7 @@ class serializer
|
|||||||
{
|
{
|
||||||
put_char('\"');
|
put_char('\"');
|
||||||
dump_escaped(i->first, ensure_ascii);
|
dump_escaped(i->first, ensure_ascii);
|
||||||
put_chars("\":", 2);
|
put_literal("\":");
|
||||||
dump_internal(i->second, false, ensure_ascii, indent_step, current_indent);
|
dump_internal(i->second, false, ensure_ascii, indent_step, current_indent);
|
||||||
put_char(',');
|
put_char(',');
|
||||||
}
|
}
|
||||||
@@ -21483,7 +21478,7 @@ class serializer
|
|||||||
JSON_ASSERT(std::next(i) == val.m_data.m_value.object->cend());
|
JSON_ASSERT(std::next(i) == val.m_data.m_value.object->cend());
|
||||||
put_char('\"');
|
put_char('\"');
|
||||||
dump_escaped(i->first, ensure_ascii);
|
dump_escaped(i->first, ensure_ascii);
|
||||||
put_chars("\":", 2);
|
put_literal("\":");
|
||||||
dump_internal(i->second, false, ensure_ascii, indent_step, current_indent);
|
dump_internal(i->second, false, ensure_ascii, indent_step, current_indent);
|
||||||
|
|
||||||
put_char('}');
|
put_char('}');
|
||||||
@@ -21496,37 +21491,33 @@ class serializer
|
|||||||
{
|
{
|
||||||
if (val.m_data.m_value.array->empty())
|
if (val.m_data.m_value.array->empty())
|
||||||
{
|
{
|
||||||
put_chars("[]", 2);
|
put_literal("[]");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pretty_print)
|
if (pretty_print)
|
||||||
{
|
{
|
||||||
put_chars("[\n", 2);
|
put_literal("[\n");
|
||||||
|
|
||||||
// variable to hold indentation for recursive calls
|
// variable to hold indentation for recursive calls
|
||||||
const auto new_indent = current_indent + indent_step;
|
const auto new_indent = next_indent(current_indent, indent_step);
|
||||||
if (JSON_HEDLEY_UNLIKELY(indent_string.size() < new_indent))
|
|
||||||
{
|
|
||||||
indent_string.resize(indent_string.size() * 2, ' ');
|
|
||||||
}
|
|
||||||
|
|
||||||
// first n-1 elements
|
// first n-1 elements
|
||||||
for (auto i = val.m_data.m_value.array->cbegin();
|
for (auto i = val.m_data.m_value.array->cbegin();
|
||||||
i != val.m_data.m_value.array->cend() - 1; ++i)
|
i != val.m_data.m_value.array->cend() - 1; ++i)
|
||||||
{
|
{
|
||||||
put_chars(indent_string.c_str(), new_indent);
|
put_indent(new_indent);
|
||||||
dump_internal(*i, true, ensure_ascii, indent_step, new_indent);
|
dump_internal(*i, true, ensure_ascii, indent_step, new_indent);
|
||||||
put_chars(",\n", 2);
|
put_literal(",\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
// last element
|
// last element
|
||||||
JSON_ASSERT(!val.m_data.m_value.array->empty());
|
JSON_ASSERT(!val.m_data.m_value.array->empty());
|
||||||
put_chars(indent_string.c_str(), new_indent);
|
put_indent(new_indent);
|
||||||
dump_internal(val.m_data.m_value.array->back(), true, ensure_ascii, indent_step, new_indent);
|
dump_internal(val.m_data.m_value.array->back(), true, ensure_ascii, indent_step, new_indent);
|
||||||
|
|
||||||
put_char('\n');
|
put_char('\n');
|
||||||
put_chars(indent_string.c_str(), current_indent);
|
put_indent(current_indent);
|
||||||
put_char(']');
|
put_char(']');
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -21563,18 +21554,14 @@ class serializer
|
|||||||
{
|
{
|
||||||
if (pretty_print)
|
if (pretty_print)
|
||||||
{
|
{
|
||||||
put_chars("{\n", 2);
|
put_literal("{\n");
|
||||||
|
|
||||||
// variable to hold indentation for recursive calls
|
// variable to hold indentation for recursive calls
|
||||||
const auto new_indent = current_indent + indent_step;
|
const auto new_indent = next_indent(current_indent, indent_step);
|
||||||
if (JSON_HEDLEY_UNLIKELY(indent_string.size() < new_indent))
|
|
||||||
{
|
|
||||||
indent_string.resize(indent_string.size() * 2, ' ');
|
|
||||||
}
|
|
||||||
|
|
||||||
put_chars(indent_string.c_str(), new_indent);
|
put_indent(new_indent);
|
||||||
|
|
||||||
put_chars("\"bytes\": [", 10);
|
put_literal("\"bytes\": [");
|
||||||
|
|
||||||
if (!val.m_data.m_value.binary->empty())
|
if (!val.m_data.m_value.binary->empty())
|
||||||
{
|
{
|
||||||
@@ -21582,30 +21569,30 @@ class serializer
|
|||||||
i != val.m_data.m_value.binary->cend() - 1; ++i)
|
i != val.m_data.m_value.binary->cend() - 1; ++i)
|
||||||
{
|
{
|
||||||
dump_integer(*i);
|
dump_integer(*i);
|
||||||
put_chars(", ", 2);
|
put_literal(", ");
|
||||||
}
|
}
|
||||||
dump_integer(val.m_data.m_value.binary->back());
|
dump_integer(val.m_data.m_value.binary->back());
|
||||||
}
|
}
|
||||||
|
|
||||||
put_chars("],\n", 3);
|
put_literal("],\n");
|
||||||
put_chars(indent_string.c_str(), new_indent);
|
put_indent(new_indent);
|
||||||
|
|
||||||
put_chars("\"subtype\": ", 11);
|
put_literal("\"subtype\": ");
|
||||||
if (val.m_data.m_value.binary->has_subtype())
|
if (val.m_data.m_value.binary->has_subtype())
|
||||||
{
|
{
|
||||||
dump_integer(val.m_data.m_value.binary->subtype());
|
dump_integer(val.m_data.m_value.binary->subtype());
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
put_chars("null", 4);
|
put_literal("null");
|
||||||
}
|
}
|
||||||
put_char('\n');
|
put_char('\n');
|
||||||
put_chars(indent_string.c_str(), current_indent);
|
put_indent(current_indent);
|
||||||
put_char('}');
|
put_char('}');
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
put_chars("{\"bytes\":[", 10);
|
put_literal("{\"bytes\":[");
|
||||||
|
|
||||||
if (!val.m_data.m_value.binary->empty())
|
if (!val.m_data.m_value.binary->empty())
|
||||||
{
|
{
|
||||||
@@ -21618,7 +21605,7 @@ class serializer
|
|||||||
dump_integer(val.m_data.m_value.binary->back());
|
dump_integer(val.m_data.m_value.binary->back());
|
||||||
}
|
}
|
||||||
|
|
||||||
put_chars("],\"subtype\":", 12);
|
put_literal("],\"subtype\":");
|
||||||
if (val.m_data.m_value.binary->has_subtype())
|
if (val.m_data.m_value.binary->has_subtype())
|
||||||
{
|
{
|
||||||
dump_integer(val.m_data.m_value.binary->subtype());
|
dump_integer(val.m_data.m_value.binary->subtype());
|
||||||
@@ -21626,7 +21613,7 @@ class serializer
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
put_chars("null}", 5);
|
put_literal("null}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
@@ -21636,11 +21623,11 @@ class serializer
|
|||||||
{
|
{
|
||||||
if (val.m_data.m_value.boolean)
|
if (val.m_data.m_value.boolean)
|
||||||
{
|
{
|
||||||
put_chars("true", 4);
|
put_literal("true");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
put_chars("false", 5);
|
put_literal("false");
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -21665,13 +21652,13 @@ class serializer
|
|||||||
|
|
||||||
case value_t::discarded:
|
case value_t::discarded:
|
||||||
{
|
{
|
||||||
put_chars("<discarded>", 11);
|
put_literal("<discarded>");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
case value_t::null:
|
case value_t::null:
|
||||||
{
|
{
|
||||||
put_chars("null", 4);
|
put_literal("null");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -21680,6 +21667,19 @@ class serializer
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief the indentation level to use for the children of the current value
|
||||||
|
|
||||||
|
A very large @a indent_step can wrap the unsigned accumulation on deep
|
||||||
|
nesting, which would silently truncate the indentation.
|
||||||
|
*/
|
||||||
|
static unsigned int next_indent(const unsigned int current_indent, const unsigned int indent_step)
|
||||||
|
{
|
||||||
|
const unsigned int new_indent = current_indent + indent_step;
|
||||||
|
JSON_ASSERT(new_indent >= current_indent);
|
||||||
|
return new_indent;
|
||||||
|
}
|
||||||
|
|
||||||
JSON_PRIVATE_UNLESS_TESTED:
|
JSON_PRIVATE_UNLESS_TESTED:
|
||||||
/*!
|
/*!
|
||||||
@brief dump escaped string
|
@brief dump escaped string
|
||||||
@@ -21732,7 +21732,7 @@ class serializer
|
|||||||
// preserve output order, then write the run directly
|
// preserve output order, then write the run directly
|
||||||
if (bytes != 0)
|
if (bytes != 0)
|
||||||
{
|
{
|
||||||
put_chars(string_buffer.data(), bytes);
|
put_buffer(string_buffer, bytes);
|
||||||
bytes = 0;
|
bytes = 0;
|
||||||
}
|
}
|
||||||
put_chars(s.data() + i, run);
|
put_chars(s.data() + i, run);
|
||||||
@@ -21834,7 +21834,7 @@ class serializer
|
|||||||
// written ("\uxxxx\uxxxx\0") for one code point
|
// written ("\uxxxx\uxxxx\0") for one code point
|
||||||
if (string_buffer.size() - bytes < 13)
|
if (string_buffer.size() - bytes < 13)
|
||||||
{
|
{
|
||||||
put_chars(string_buffer.data(), bytes);
|
put_buffer(string_buffer, bytes);
|
||||||
bytes = 0;
|
bytes = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -21893,7 +21893,7 @@ class serializer
|
|||||||
// written ("\uxxxx\uxxxx\0") for one code point
|
// written ("\uxxxx\uxxxx\0") for one code point
|
||||||
if (string_buffer.size() - bytes < 13)
|
if (string_buffer.size() - bytes < 13)
|
||||||
{
|
{
|
||||||
put_chars(string_buffer.data(), bytes);
|
put_buffer(string_buffer, bytes);
|
||||||
bytes = 0;
|
bytes = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -21932,7 +21932,7 @@ class serializer
|
|||||||
// write buffer
|
// write buffer
|
||||||
if (bytes > 0)
|
if (bytes > 0)
|
||||||
{
|
{
|
||||||
put_chars(string_buffer.data(), bytes);
|
put_buffer(string_buffer, bytes);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -21948,22 +21948,22 @@ class serializer
|
|||||||
case error_handler_t::ignore:
|
case error_handler_t::ignore:
|
||||||
{
|
{
|
||||||
// write all accepted bytes
|
// write all accepted bytes
|
||||||
put_chars(string_buffer.data(), bytes_after_last_accept);
|
put_buffer(string_buffer, bytes_after_last_accept);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case error_handler_t::replace:
|
case error_handler_t::replace:
|
||||||
{
|
{
|
||||||
// write all accepted bytes
|
// write all accepted bytes
|
||||||
put_chars(string_buffer.data(), bytes_after_last_accept);
|
put_buffer(string_buffer, bytes_after_last_accept);
|
||||||
// add a replacement character
|
// add a replacement character
|
||||||
if (ensure_ascii)
|
if (ensure_ascii)
|
||||||
{
|
{
|
||||||
put_chars("\\ufffd", 6);
|
put_literal("\\ufffd");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
put_chars("\xEF\xBF\xBD", 3);
|
put_literal("\xEF\xBF\xBD");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -21999,6 +21999,66 @@ class serializer
|
|||||||
adapter (after flushing what is pending), so large string/number payloads
|
adapter (after flushing what is pending), so large string/number payloads
|
||||||
are not copied an extra time.
|
are not copied an extra time.
|
||||||
*/
|
*/
|
||||||
|
/*!
|
||||||
|
@brief append @a indent indentation characters to the write buffer
|
||||||
|
|
||||||
|
Writes the indentation straight into the buffer instead of copying it out of
|
||||||
|
a pre-grown indentation string, so no auxiliary string has to be sized,
|
||||||
|
resized, or kept in sync with the deepest nesting level reached. An
|
||||||
|
indentation wider than the buffer simply fills and flushes it repeatedly.
|
||||||
|
*/
|
||||||
|
void put_indent(unsigned int indent)
|
||||||
|
{
|
||||||
|
while (indent > 0)
|
||||||
|
{
|
||||||
|
if (JSON_HEDLEY_UNLIKELY(write_buffer_pos == write_buffer.size()))
|
||||||
|
{
|
||||||
|
flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::size_t chunk = (std::min)(static_cast<std::size_t>(indent),
|
||||||
|
write_buffer.size() - write_buffer_pos);
|
||||||
|
std::memset(write_buffer.data() + write_buffer_pos, indent_char, chunk);
|
||||||
|
write_buffer_pos += chunk;
|
||||||
|
indent -= static_cast<unsigned int>(chunk);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief append a string literal to the write buffer
|
||||||
|
|
||||||
|
The length comes from the array bound rather than a hand-written count, so
|
||||||
|
it cannot drift out of sync with the literal. A literal always fits into the
|
||||||
|
buffer (checked at compile time), so unlike @ref put_chars this needs no
|
||||||
|
write-through path for oversized runs.
|
||||||
|
*/
|
||||||
|
template<std::size_t N>
|
||||||
|
void put_literal(const char (&s)[N])
|
||||||
|
{
|
||||||
|
static_assert(N >= 2, "put_literal expects a non-empty string literal");
|
||||||
|
static_assert(N - 1 < write_buffer_size, "string literal must fit into the write buffer");
|
||||||
|
|
||||||
|
if (JSON_HEDLEY_UNLIKELY(write_buffer_pos + (N - 1) > write_buffer.size()))
|
||||||
|
{
|
||||||
|
flush();
|
||||||
|
}
|
||||||
|
std::memcpy(write_buffer.data() + write_buffer_pos, s, N - 1);
|
||||||
|
write_buffer_pos += N - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief append the first @a length characters of a fixed-size buffer
|
||||||
|
|
||||||
|
Same as @ref put_chars, but the buffer carries its own bound, so the length
|
||||||
|
can be checked against it - which a bare pointer plus count cannot do.
|
||||||
|
*/
|
||||||
|
template<std::size_t N>
|
||||||
|
void put_buffer(const std::array<char, N>& buffer, std::size_t length)
|
||||||
|
{
|
||||||
|
JSON_ASSERT(length <= N);
|
||||||
|
put_chars(buffer.data(), length);
|
||||||
|
}
|
||||||
|
|
||||||
JSON_HEDLEY_NON_NULL(2)
|
JSON_HEDLEY_NON_NULL(2)
|
||||||
void put_chars(const char* s, std::size_t length)
|
void put_chars(const char* s, std::size_t length)
|
||||||
{
|
{
|
||||||
@@ -22211,7 +22271,7 @@ class serializer
|
|||||||
*(--buffer_ptr) = static_cast<char>('0' + abs_value);
|
*(--buffer_ptr) = static_cast<char>('0' + abs_value);
|
||||||
}
|
}
|
||||||
|
|
||||||
put_chars(number_buffer.data(), n_chars);
|
put_buffer(number_buffer, n_chars);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@@ -22227,7 +22287,7 @@ class serializer
|
|||||||
// NaN / inf
|
// NaN / inf
|
||||||
if (!std::isfinite(x))
|
if (!std::isfinite(x))
|
||||||
{
|
{
|
||||||
put_chars("null", 4);
|
put_literal("null");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -22299,7 +22359,7 @@ class serializer
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
put_chars(number_buffer.data(), static_cast<std::size_t>(len));
|
put_buffer(number_buffer, static_cast<std::size_t>(len));
|
||||||
|
|
||||||
// determine if we need to append ".0"
|
// determine if we need to append ".0"
|
||||||
const bool value_is_int_like =
|
const bool value_is_int_like =
|
||||||
@@ -22311,7 +22371,7 @@ class serializer
|
|||||||
|
|
||||||
if (value_is_int_like)
|
if (value_is_int_like)
|
||||||
{
|
{
|
||||||
put_chars(".0", 2);
|
put_literal(".0");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -22416,15 +22476,14 @@ class serializer
|
|||||||
|
|
||||||
/// the indentation character
|
/// the indentation character
|
||||||
const char indent_char;
|
const char indent_char;
|
||||||
/// the indentation string
|
|
||||||
string_t indent_string;
|
|
||||||
|
|
||||||
/// error_handler how to react on decoding errors
|
/// error_handler how to react on decoding errors
|
||||||
const error_handler_t error_handler;
|
const error_handler_t error_handler;
|
||||||
|
|
||||||
/// buffer collecting output before it is flushed to the output adapter, so
|
/// buffer collecting output before it is flushed to the output adapter, so
|
||||||
/// that the many small structural writes become few bulk writes
|
/// that the many small structural writes become few bulk writes
|
||||||
std::array<char, 1024> write_buffer{{}};
|
static constexpr std::size_t write_buffer_size = 1024;
|
||||||
|
std::array<char, write_buffer_size> write_buffer{{}};
|
||||||
/// number of valid bytes currently held in @ref write_buffer
|
/// number of valid bytes currently held in @ref write_buffer
|
||||||
std::size_t write_buffer_pos = 0;
|
std::size_t write_buffer_pos = 0;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -475,3 +475,51 @@ TEST_CASE("serialization of strings (bulk fast path)")
|
|||||||
CHECK(j.dump(-1, ' ', false, json::error_handler_t::ignore) == "\"validmore\"");
|
CHECK(j.dump(-1, ' ', false, json::error_handler_t::ignore) == "\"validmore\"");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("indentation is written straight into the write buffer")
|
||||||
|
{
|
||||||
|
// put_indent() memsets the indentation into the write buffer instead of
|
||||||
|
// copying it out of a pre-grown indentation string. These cases cover an
|
||||||
|
// indentation wider than the buffer, a non-space indentation character, and
|
||||||
|
// nesting deep enough that the accumulated indentation spans several
|
||||||
|
// buffer-fulls - the situations the old grow-a-string approach got wrong.
|
||||||
|
|
||||||
|
SECTION("indent_step wider than the write buffer")
|
||||||
|
{
|
||||||
|
const json j = {{"a", 1}};
|
||||||
|
// 2000 > the 1024-byte write buffer, and > the 512 the indentation
|
||||||
|
// string used to start at
|
||||||
|
CHECK(j.dump(2000) == "{\n" + std::string(2000, ' ') + "\"a\": 1\n}");
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("a non-space indentation character is used throughout")
|
||||||
|
{
|
||||||
|
const json j = {{"a", 1}};
|
||||||
|
// 600 is past the point where the indentation used to be grown, which
|
||||||
|
// is where a hard-coded space would have shown up
|
||||||
|
CHECK(j.dump(600, '\t') == "{\n" + std::string(600, '\t') + "\"a\": 1\n}");
|
||||||
|
CHECK(j.dump(3, '.') == "{\n...\"a\": 1\n}");
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("accumulated indentation spans several buffer-fulls")
|
||||||
|
{
|
||||||
|
// five levels deep at 400 per level: the innermost value is indented by
|
||||||
|
// 2000 characters, reached in steps that each straddle the buffer end
|
||||||
|
json j = json::array({1});
|
||||||
|
for (int i = 0; i < 4; ++i)
|
||||||
|
{
|
||||||
|
j = json::array({j});
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string out = j.dump(400);
|
||||||
|
CHECK(out.find(std::string("\n") + std::string(2000, ' ') + "1\n") != std::string::npos);
|
||||||
|
CHECK(json::parse(out) == j);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("indentation is unchanged for ordinary widths")
|
||||||
|
{
|
||||||
|
const json j = {{"a", {1, 2}}, {"b", nullptr}};
|
||||||
|
CHECK(j.dump(2) == "{\n \"a\": [\n 1,\n 2\n ],\n \"b\": null\n}");
|
||||||
|
CHECK(j.dump(0) == "{\n\"a\": [\n1,\n2\n],\n\"b\": null\n}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user