mirror of
https://github.com/nlohmann/json.git
synced 2026-08-22 00:53:18 +00:00
Serializing a container serializes its elements, so dump() descended into one call per nesting level. A value nested deeply enough exhausted the call stack and terminated the process with a segmentation fault - no exception, nothing the caller could catch. Parsing such a value works, as the parser is iterative, and so does destroying one, as #1436 made destruction iterative. Bound how far the descent goes rather than take the call stack away from it. The first 128 levels are written by exactly the code that always wrote them, and only below that does dump_iteratively write out what is left, keeping the containers it has entered on an explicit stack. Serializing can therefore no longer exhaust the stack, however deeply a value is nested, while a value nested less deeply than the bound pays only for one comparison per container. Writing every value that way instead measured between 2% and 20% slower - 20% on object-heavy documents - which is why the descent is kept for all but the values that cannot afford it. The bound costs nothing measurable: between -1.4% and +1.2% across compact and pretty output of number, integer, string, object-heavy, wide-object and deeply nested documents. The output is unchanged for every value. Both ways of writing a container emit the separator in front of every element but the first, rather than after every element but the last, which puts exactly one between each pair and none at the end. This fixes #5387 for dump(). The copy constructor is fixed in #5389. Signed-off-by: Niels Lohmann <mail@nlohmann.me>
1631 lines
61 KiB
C++
1631 lines
61 KiB
C++
// __ _____ _____ _____
|
|
// __| | __| | | | JSON for Modern C++
|
|
// | | |__ | | | | | | version 3.12.0
|
|
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
|
//
|
|
// SPDX-FileCopyrightText: 2008, 2009 Björn Hoehrmann <bjoern@hoehrmann.de>
|
|
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
#pragma once
|
|
|
|
#include <algorithm> // reverse, remove, fill, find, none_of, min
|
|
#include <array> // array
|
|
#include <clocale> // localeconv, lconv
|
|
#include <cmath> // labs, isfinite, isnan, signbit
|
|
#include <cstddef> // size_t, ptrdiff_t
|
|
#include <cstdint> // uint8_t
|
|
#include <cstdio> // snprintf
|
|
#include <cstring> // memcpy, memset
|
|
#include <limits> // numeric_limits
|
|
#include <string> // string, char_traits
|
|
#include <iomanip> // setfill, setw
|
|
#include <type_traits> // is_same
|
|
#include <utility> // move
|
|
#include <vector> // vector
|
|
|
|
#include <nlohmann/detail/conversions/to_chars.hpp>
|
|
#include <nlohmann/detail/exceptions.hpp>
|
|
#include <nlohmann/detail/input/string_scan.hpp>
|
|
#include <nlohmann/detail/macro_scope.hpp>
|
|
#include <nlohmann/detail/meta/cpp_future.hpp>
|
|
#include <nlohmann/detail/output/binary_writer.hpp>
|
|
#include <nlohmann/detail/output/output_adapters.hpp>
|
|
#include <nlohmann/detail/string_concat.hpp>
|
|
#include <nlohmann/detail/value_t.hpp>
|
|
|
|
NLOHMANN_JSON_NAMESPACE_BEGIN
|
|
namespace detail
|
|
{
|
|
|
|
///////////////////
|
|
// serialization //
|
|
///////////////////
|
|
|
|
/// how to treat decoding errors
|
|
enum class error_handler_t
|
|
{
|
|
strict, ///< throw a type_error exception in case of invalid UTF-8
|
|
replace, ///< replace invalid UTF-8 sequences with U+FFFD
|
|
ignore ///< ignore invalid UTF-8 sequences
|
|
};
|
|
|
|
template<typename BasicJsonType>
|
|
class 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 binary_char_t = typename BasicJsonType::binary_t::value_type;
|
|
static constexpr std::uint8_t UTF8_ACCEPT = 0;
|
|
static constexpr std::uint8_t UTF8_REJECT = 1;
|
|
|
|
public:
|
|
/*!
|
|
@param[in] s output stream to serialize to
|
|
@param[in] ichar indentation character to use
|
|
@param[in] error_handler_ how to react on decoding errors
|
|
*/
|
|
serializer(output_adapter_t<char> s, const char ichar,
|
|
error_handler_t error_handler_ = error_handler_t::strict)
|
|
: o(std::move(s))
|
|
, loc(std::localeconv())
|
|
, 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)))
|
|
, indent_char(ichar)
|
|
, error_handler(error_handler_)
|
|
{}
|
|
|
|
// deleted because of pointer members
|
|
serializer(const serializer&) = delete;
|
|
serializer& operator=(const serializer&) = delete;
|
|
serializer(serializer&&) = delete;
|
|
serializer& operator=(serializer&&) = delete;
|
|
~serializer() = default;
|
|
|
|
/*!
|
|
@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. Arrays and objects are serialized without recursion,
|
|
however deeply they are nested.
|
|
|
|
- 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
|
|
- binary values are serialized as objects containing the subtype and the
|
|
byte array
|
|
|
|
@param[in] val value to serialize
|
|
@param[in] pretty_print whether the output shall be pretty-printed
|
|
@param[in] ensure_ascii If @a ensure_ascii is true, all non-ASCII characters
|
|
in the output are escaped with `\uXXXX` sequences, and the result consists
|
|
of ASCII characters only.
|
|
@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 std::size_t indent_step,
|
|
const std::size_t current_indent = 0)
|
|
{
|
|
dump_internal(val, pretty_print, ensure_ascii, indent_step, current_indent);
|
|
flush();
|
|
}
|
|
|
|
JSON_PRIVATE_UNLESS_TESTED:
|
|
/*!
|
|
@brief worker for @ref dump
|
|
|
|
Identical in behavior to the historical @ref dump, but writes into the
|
|
serializer's internal @ref write_buffer instead of issuing a virtual call
|
|
per token. The public @ref dump wraps this and flushes the buffer once the
|
|
top-level value has been serialized.
|
|
|
|
Serializing a container descends into its elements, so a value nested deeply
|
|
enough used to exhaust the call stack and terminate the process with no
|
|
exception to catch. The descent is bounded here: once @ref dump_depth_limit
|
|
levels have been entered, @ref dump_iteratively writes out what is left
|
|
without the call stack. A value nested less deeply than that - all but a
|
|
vanishing minority - is written by exactly the code that always wrote it.
|
|
|
|
@sa https://github.com/nlohmann/json/issues/5387
|
|
*/
|
|
void dump_internal(const BasicJsonType& val,
|
|
const bool pretty_print,
|
|
const bool ensure_ascii,
|
|
const std::size_t indent_step,
|
|
const std::size_t current_indent = 0,
|
|
const std::size_t depth = 0)
|
|
{
|
|
switch (val.m_data.m_type)
|
|
{
|
|
case value_t::object:
|
|
{
|
|
if (JSON_HEDLEY_UNLIKELY(depth >= dump_depth_limit()))
|
|
{
|
|
dump_iteratively(val, pretty_print, ensure_ascii, indent_step, current_indent);
|
|
return;
|
|
}
|
|
|
|
if (val.m_data.m_value.object->empty())
|
|
{
|
|
put_literal("{}");
|
|
return;
|
|
}
|
|
|
|
if (pretty_print)
|
|
{
|
|
put_literal("{\n");
|
|
|
|
// variable to hold indentation for recursive calls
|
|
const auto new_indent = next_indent(current_indent, indent_step);
|
|
|
|
// first n-1 elements
|
|
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)
|
|
{
|
|
put_indent(new_indent);
|
|
put_char('\"');
|
|
dump_escaped(i->first, ensure_ascii);
|
|
put_literal("\": ");
|
|
dump_internal(i->second, true, ensure_ascii, indent_step, new_indent, depth + 1);
|
|
put_literal(",\n");
|
|
}
|
|
|
|
// last element
|
|
JSON_ASSERT(i != val.m_data.m_value.object->cend());
|
|
JSON_ASSERT(std::next(i) == val.m_data.m_value.object->cend());
|
|
put_indent(new_indent);
|
|
put_char('\"');
|
|
dump_escaped(i->first, ensure_ascii);
|
|
put_literal("\": ");
|
|
dump_internal(i->second, true, ensure_ascii, indent_step, new_indent, depth + 1);
|
|
|
|
put_char('\n');
|
|
put_indent(current_indent);
|
|
put_char('}');
|
|
}
|
|
else
|
|
{
|
|
put_char('{');
|
|
|
|
// first n-1 elements
|
|
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)
|
|
{
|
|
put_char('\"');
|
|
dump_escaped(i->first, ensure_ascii);
|
|
put_literal("\":");
|
|
dump_internal(i->second, false, ensure_ascii, indent_step, current_indent, depth + 1);
|
|
put_char(',');
|
|
}
|
|
|
|
// last element
|
|
JSON_ASSERT(i != val.m_data.m_value.object->cend());
|
|
JSON_ASSERT(std::next(i) == val.m_data.m_value.object->cend());
|
|
put_char('\"');
|
|
dump_escaped(i->first, ensure_ascii);
|
|
put_literal("\":");
|
|
dump_internal(i->second, false, ensure_ascii, indent_step, current_indent, depth + 1);
|
|
|
|
put_char('}');
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
case value_t::array:
|
|
{
|
|
if (JSON_HEDLEY_UNLIKELY(depth >= dump_depth_limit()))
|
|
{
|
|
dump_iteratively(val, pretty_print, ensure_ascii, indent_step, current_indent);
|
|
return;
|
|
}
|
|
|
|
if (val.m_data.m_value.array->empty())
|
|
{
|
|
put_literal("[]");
|
|
return;
|
|
}
|
|
|
|
if (pretty_print)
|
|
{
|
|
put_literal("[\n");
|
|
|
|
// variable to hold indentation for recursive calls
|
|
const auto new_indent = next_indent(current_indent, indent_step);
|
|
|
|
// first n-1 elements
|
|
for (auto i = val.m_data.m_value.array->cbegin();
|
|
i != val.m_data.m_value.array->cend() - 1; ++i)
|
|
{
|
|
put_indent(new_indent);
|
|
dump_internal(*i, true, ensure_ascii, indent_step, new_indent, depth + 1);
|
|
put_literal(",\n");
|
|
}
|
|
|
|
// last element
|
|
JSON_ASSERT(!val.m_data.m_value.array->empty());
|
|
put_indent(new_indent);
|
|
dump_internal(val.m_data.m_value.array->back(), true, ensure_ascii, indent_step, new_indent, depth + 1);
|
|
|
|
put_char('\n');
|
|
put_indent(current_indent);
|
|
put_char(']');
|
|
}
|
|
else
|
|
{
|
|
put_char('[');
|
|
|
|
// first n-1 elements
|
|
for (auto i = val.m_data.m_value.array->cbegin();
|
|
i != val.m_data.m_value.array->cend() - 1; ++i)
|
|
{
|
|
dump_internal(*i, false, ensure_ascii, indent_step, current_indent, depth + 1);
|
|
put_char(',');
|
|
}
|
|
|
|
// last element
|
|
JSON_ASSERT(!val.m_data.m_value.array->empty());
|
|
dump_internal(val.m_data.m_value.array->back(), false, ensure_ascii, indent_step, current_indent, depth + 1);
|
|
|
|
put_char(']');
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
case value_t::string:
|
|
{
|
|
put_char('\"');
|
|
dump_escaped(*val.m_data.m_value.string, ensure_ascii);
|
|
put_char('\"');
|
|
return;
|
|
}
|
|
|
|
case value_t::binary:
|
|
{
|
|
if (pretty_print)
|
|
{
|
|
put_literal("{\n");
|
|
|
|
// variable to hold indentation for recursive calls
|
|
const auto new_indent = next_indent(current_indent, indent_step);
|
|
|
|
put_indent(new_indent);
|
|
|
|
put_literal("\"bytes\": [");
|
|
|
|
if (!val.m_data.m_value.binary->empty())
|
|
{
|
|
for (auto i = val.m_data.m_value.binary->cbegin();
|
|
i != val.m_data.m_value.binary->cend() - 1; ++i)
|
|
{
|
|
dump_integer(*i);
|
|
put_literal(", ");
|
|
}
|
|
dump_integer(val.m_data.m_value.binary->back());
|
|
}
|
|
|
|
put_literal("],\n");
|
|
put_indent(new_indent);
|
|
|
|
put_literal("\"subtype\": ");
|
|
if (val.m_data.m_value.binary->has_subtype())
|
|
{
|
|
dump_integer(val.m_data.m_value.binary->subtype());
|
|
}
|
|
else
|
|
{
|
|
put_literal("null");
|
|
}
|
|
put_char('\n');
|
|
put_indent(current_indent);
|
|
put_char('}');
|
|
}
|
|
else
|
|
{
|
|
put_literal("{\"bytes\":[");
|
|
|
|
if (!val.m_data.m_value.binary->empty())
|
|
{
|
|
for (auto i = val.m_data.m_value.binary->cbegin();
|
|
i != val.m_data.m_value.binary->cend() - 1; ++i)
|
|
{
|
|
dump_integer(*i);
|
|
put_char(',');
|
|
}
|
|
dump_integer(val.m_data.m_value.binary->back());
|
|
}
|
|
|
|
put_literal("],\"subtype\":");
|
|
if (val.m_data.m_value.binary->has_subtype())
|
|
{
|
|
dump_integer(val.m_data.m_value.binary->subtype());
|
|
put_char('}');
|
|
}
|
|
else
|
|
{
|
|
put_literal("null}");
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
case value_t::boolean:
|
|
{
|
|
if (val.m_data.m_value.boolean)
|
|
{
|
|
put_literal("true");
|
|
}
|
|
else
|
|
{
|
|
put_literal("false");
|
|
}
|
|
return;
|
|
}
|
|
|
|
case value_t::number_integer:
|
|
{
|
|
dump_integer(val.m_data.m_value.number_integer);
|
|
return;
|
|
}
|
|
|
|
case value_t::number_unsigned:
|
|
{
|
|
dump_integer(val.m_data.m_value.number_unsigned);
|
|
return;
|
|
}
|
|
|
|
case value_t::number_float:
|
|
{
|
|
dump_float(val.m_data.m_value.number_float);
|
|
return;
|
|
}
|
|
|
|
case value_t::discarded:
|
|
{
|
|
put_literal("<discarded>");
|
|
return;
|
|
}
|
|
|
|
case value_t::null:
|
|
{
|
|
put_literal("null");
|
|
return;
|
|
}
|
|
|
|
default: // LCOV_EXCL_LINE
|
|
JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE
|
|
}
|
|
}
|
|
|
|
private:
|
|
/// the number of levels @ref dump_internal descends into before it hands
|
|
/// over to @ref dump_iteratively
|
|
static constexpr std::size_t dump_depth_limit()
|
|
{
|
|
return 128;
|
|
}
|
|
|
|
/*!
|
|
@brief write out @a val and everything below it without the call stack
|
|
|
|
Emits the same bytes as @ref dump_internal, keeping the containers it has
|
|
entered on an explicit stack instead of descending into them. Only reached
|
|
for values nested deeper than @ref dump_depth_limit, which is why it is not
|
|
written for speed: walking every value this way measured up to 20% slower on
|
|
object-heavy documents than letting the compiler drive the descent.
|
|
*/
|
|
void dump_iteratively(const BasicJsonType& val,
|
|
const bool pretty_print,
|
|
const bool ensure_ascii,
|
|
const std::size_t indent_step,
|
|
const std::size_t current_indent = 0)
|
|
{
|
|
// Scalars, empty containers and binary values are written by dump_value
|
|
// alone, so nothing is allocated for them: only a container with
|
|
// elements is ever pushed.
|
|
std::vector<dump_frame> stack;
|
|
|
|
dump_value(val, pretty_print, ensure_ascii, indent_step, current_indent, stack);
|
|
|
|
while (!stack.empty())
|
|
{
|
|
dump_frame& frame = stack.back();
|
|
|
|
if (frame.value->m_data.m_type == value_t::object)
|
|
{
|
|
const auto* object = frame.value->m_data.m_value.object;
|
|
|
|
if (frame.object_it == object->cend())
|
|
{
|
|
if (pretty_print)
|
|
{
|
|
put_char('\n');
|
|
put_indent(frame.current_indent);
|
|
}
|
|
|
|
put_char('}');
|
|
stack.pop_back();
|
|
continue;
|
|
}
|
|
|
|
// the separator goes in front of every element but the first,
|
|
// which puts exactly one between each pair and none at the end
|
|
if (frame.object_it != object->cbegin())
|
|
{
|
|
if (pretty_print)
|
|
{
|
|
put_literal(",\n");
|
|
}
|
|
else
|
|
{
|
|
put_char(',');
|
|
}
|
|
}
|
|
|
|
if (pretty_print)
|
|
{
|
|
put_indent(frame.child_indent);
|
|
}
|
|
|
|
put_char('\"');
|
|
dump_escaped(frame.object_it->first, ensure_ascii);
|
|
|
|
if (pretty_print)
|
|
{
|
|
put_literal("\": ");
|
|
}
|
|
else
|
|
{
|
|
put_literal("\":");
|
|
}
|
|
|
|
const BasicJsonType& element = frame.object_it->second;
|
|
++frame.object_it;
|
|
|
|
// read everything needed from the frame before this: entering a
|
|
// container pushes another one and can move them all
|
|
const std::size_t element_indent = frame.child_indent;
|
|
dump_value(element, pretty_print, ensure_ascii, indent_step, element_indent, stack);
|
|
}
|
|
else
|
|
{
|
|
const auto* array = frame.value->m_data.m_value.array;
|
|
|
|
if (frame.array_it == array->cend())
|
|
{
|
|
if (pretty_print)
|
|
{
|
|
put_char('\n');
|
|
put_indent(frame.current_indent);
|
|
}
|
|
|
|
put_char(']');
|
|
stack.pop_back();
|
|
continue;
|
|
}
|
|
|
|
if (frame.array_it != array->cbegin())
|
|
{
|
|
if (pretty_print)
|
|
{
|
|
put_literal(",\n");
|
|
}
|
|
else
|
|
{
|
|
put_char(',');
|
|
}
|
|
}
|
|
|
|
if (pretty_print)
|
|
{
|
|
put_indent(frame.child_indent);
|
|
}
|
|
|
|
const BasicJsonType& element = *frame.array_it;
|
|
++frame.array_it;
|
|
|
|
// see above
|
|
const std::size_t element_indent = frame.child_indent;
|
|
dump_value(element, pretty_print, ensure_ascii, indent_step, element_indent, stack);
|
|
}
|
|
}
|
|
}
|
|
|
|
private:
|
|
/// @brief a container that has been opened but not closed yet
|
|
struct dump_frame
|
|
{
|
|
dump_frame(const BasicJsonType* value_, const std::size_t current_indent_,
|
|
const std::size_t child_indent_) noexcept
|
|
: value(value_)
|
|
, current_indent(current_indent_)
|
|
, child_indent(child_indent_)
|
|
{}
|
|
|
|
/// the object or array being serialized
|
|
const BasicJsonType* value;
|
|
/// the element to serialize next; which of the two is live follows from
|
|
/// the type of @a value. They are kept side by side rather than in a
|
|
/// union, which would need its special members written out by hand, see
|
|
/// detail/iterators/internal_iterator.hpp
|
|
typename BasicJsonType::object_t::const_iterator object_it{};
|
|
typename BasicJsonType::array_t::const_iterator array_it{};
|
|
/// the indentation of the container itself, used by its closing bracket
|
|
std::size_t current_indent;
|
|
/// the indentation of the container's elements
|
|
std::size_t child_indent;
|
|
};
|
|
|
|
/*!
|
|
@brief serialize the value @a val, but not the elements of a container
|
|
|
|
An object or array with elements is opened and pushed onto @a stack for
|
|
@ref dump_internal to walk; everything else - including a binary value,
|
|
which looks like an object but has no elements to descend into - is written
|
|
out here in full.
|
|
*/
|
|
void dump_value(const BasicJsonType& val,
|
|
const bool pretty_print,
|
|
const bool ensure_ascii,
|
|
const std::size_t indent_step,
|
|
const std::size_t current_indent,
|
|
std::vector<dump_frame>& stack)
|
|
{
|
|
switch (val.m_data.m_type)
|
|
{
|
|
case value_t::object:
|
|
{
|
|
if (val.m_data.m_value.object->empty())
|
|
{
|
|
put_literal("{}");
|
|
return;
|
|
}
|
|
|
|
std::size_t child_indent = current_indent;
|
|
|
|
if (pretty_print)
|
|
{
|
|
put_literal("{\n");
|
|
child_indent = next_indent(current_indent, indent_step);
|
|
}
|
|
else
|
|
{
|
|
put_char('{');
|
|
}
|
|
|
|
stack.emplace_back(&val, current_indent, child_indent);
|
|
stack.back().object_it = val.m_data.m_value.object->cbegin();
|
|
return;
|
|
}
|
|
|
|
case value_t::array:
|
|
{
|
|
if (val.m_data.m_value.array->empty())
|
|
{
|
|
put_literal("[]");
|
|
return;
|
|
}
|
|
|
|
std::size_t child_indent = current_indent;
|
|
|
|
if (pretty_print)
|
|
{
|
|
put_literal("[\n");
|
|
child_indent = next_indent(current_indent, indent_step);
|
|
}
|
|
else
|
|
{
|
|
put_char('[');
|
|
}
|
|
|
|
stack.emplace_back(&val, current_indent, child_indent);
|
|
stack.back().array_it = val.m_data.m_value.array->cbegin();
|
|
return;
|
|
}
|
|
|
|
case value_t::string:
|
|
{
|
|
put_char('\"');
|
|
dump_escaped(*val.m_data.m_value.string, ensure_ascii);
|
|
put_char('\"');
|
|
return;
|
|
}
|
|
|
|
case value_t::binary:
|
|
{
|
|
if (pretty_print)
|
|
{
|
|
put_literal("{\n");
|
|
|
|
// variable to hold indentation for the bytes
|
|
const auto new_indent = next_indent(current_indent, indent_step);
|
|
|
|
put_indent(new_indent);
|
|
|
|
put_literal("\"bytes\": [");
|
|
|
|
if (!val.m_data.m_value.binary->empty())
|
|
{
|
|
for (auto i = val.m_data.m_value.binary->cbegin();
|
|
i != val.m_data.m_value.binary->cend() - 1; ++i)
|
|
{
|
|
dump_integer(*i);
|
|
put_literal(", ");
|
|
}
|
|
dump_integer(val.m_data.m_value.binary->back());
|
|
}
|
|
|
|
put_literal("],\n");
|
|
put_indent(new_indent);
|
|
|
|
put_literal("\"subtype\": ");
|
|
if (val.m_data.m_value.binary->has_subtype())
|
|
{
|
|
dump_integer(val.m_data.m_value.binary->subtype());
|
|
}
|
|
else
|
|
{
|
|
put_literal("null");
|
|
}
|
|
put_char('\n');
|
|
put_indent(current_indent);
|
|
put_char('}');
|
|
}
|
|
else
|
|
{
|
|
put_literal("{\"bytes\":[");
|
|
|
|
if (!val.m_data.m_value.binary->empty())
|
|
{
|
|
for (auto i = val.m_data.m_value.binary->cbegin();
|
|
i != val.m_data.m_value.binary->cend() - 1; ++i)
|
|
{
|
|
dump_integer(*i);
|
|
put_char(',');
|
|
}
|
|
dump_integer(val.m_data.m_value.binary->back());
|
|
}
|
|
|
|
put_literal("],\"subtype\":");
|
|
if (val.m_data.m_value.binary->has_subtype())
|
|
{
|
|
dump_integer(val.m_data.m_value.binary->subtype());
|
|
put_char('}');
|
|
}
|
|
else
|
|
{
|
|
put_literal("null}");
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
case value_t::boolean:
|
|
{
|
|
if (val.m_data.m_value.boolean)
|
|
{
|
|
put_literal("true");
|
|
}
|
|
else
|
|
{
|
|
put_literal("false");
|
|
}
|
|
return;
|
|
}
|
|
|
|
case value_t::number_integer:
|
|
{
|
|
dump_integer(val.m_data.m_value.number_integer);
|
|
return;
|
|
}
|
|
|
|
case value_t::number_unsigned:
|
|
{
|
|
dump_integer(val.m_data.m_value.number_unsigned);
|
|
return;
|
|
}
|
|
|
|
case value_t::number_float:
|
|
{
|
|
dump_float(val.m_data.m_value.number_float);
|
|
return;
|
|
}
|
|
|
|
case value_t::discarded:
|
|
{
|
|
put_literal("<discarded>");
|
|
return;
|
|
}
|
|
|
|
case value_t::null:
|
|
{
|
|
put_literal("null");
|
|
return;
|
|
}
|
|
|
|
default: // LCOV_EXCL_LINE
|
|
JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE
|
|
}
|
|
}
|
|
|
|
|
|
/*!
|
|
@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. Far harder to reach
|
|
now that the accumulator is a std::size_t, but still reachable where that is
|
|
32 bits wide.
|
|
*/
|
|
static std::size_t next_indent(const std::size_t current_indent, const std::size_t indent_step)
|
|
{
|
|
const std::size_t new_indent = current_indent + indent_step;
|
|
JSON_ASSERT(new_indent >= current_indent);
|
|
return new_indent;
|
|
}
|
|
|
|
JSON_PRIVATE_UNLESS_TESTED:
|
|
/*!
|
|
@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)
|
|
{
|
|
std::uint32_t codepoint{};
|
|
std::uint8_t state = UTF8_ACCEPT;
|
|
std::size_t bytes = 0; // number of bytes written to string_buffer
|
|
|
|
// number of bytes written at the point of the last valid byte
|
|
std::size_t bytes_after_last_accept = 0;
|
|
std::size_t undumped_chars = 0;
|
|
|
|
for (std::size_t i = 0; i < s.size(); ++i)
|
|
{
|
|
// Fast path: at a character boundary (state == UTF8_ACCEPT),
|
|
// bulk-copy the longest run of bytes that need no escaping using a
|
|
// SWAR scanner shared with the lexer's contiguous path. The scanner
|
|
// stops exactly at the first byte dump_escaped would handle
|
|
// individually, so that byte is left to the byte-at-a-time path
|
|
// below, keeping escaping output and error diagnostics unchanged.
|
|
//
|
|
// - ensure_ascii == false: string_bulk_run() copies ordinary bytes
|
|
// and complete well-formed UTF-8, stopping at a quote, backslash,
|
|
// control character (< 0x20), or ill-formed/truncated sequence.
|
|
// - ensure_ascii == true: only printable ASCII may be copied
|
|
// verbatim; find_ascii_copyable_run() additionally stops at 0x7F
|
|
// and every non-ASCII byte (>= 0x80), which must be \u-escaped.
|
|
if (state == UTF8_ACCEPT)
|
|
{
|
|
const auto* const data = reinterpret_cast<const unsigned char*>(s.data());
|
|
const std::size_t run = ensure_ascii
|
|
? find_ascii_copyable_run(data + i, s.size() - i)
|
|
: string_bulk_run(data + i, s.size() - i);
|
|
if (run != 0)
|
|
{
|
|
// emit any bytes still pending in string_buffer first to
|
|
// preserve output order, then write the run directly
|
|
if (bytes != 0)
|
|
{
|
|
put_buffer(string_buffer, bytes);
|
|
bytes = 0;
|
|
}
|
|
put_string(s, i, i + run);
|
|
bytes_after_last_accept = 0;
|
|
undumped_chars = 0;
|
|
i += run;
|
|
if (i >= s.size())
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
const auto byte = static_cast<std::uint8_t>(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) || (ensure_ascii && (codepoint >= 0x7F)))
|
|
{
|
|
if (codepoint <= 0xFFFF)
|
|
{
|
|
write_u_escape(bytes, static_cast<std::uint16_t>(codepoint));
|
|
}
|
|
else
|
|
{
|
|
write_u_escape(bytes, static_cast<std::uint16_t>(0xD7C0u + (codepoint >> 10u)));
|
|
write_u_escape(bytes, static_cast<std::uint16_t>(0xDC00u + (codepoint & 0x3FFu)));
|
|
}
|
|
}
|
|
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)
|
|
{
|
|
put_buffer(string_buffer, bytes);
|
|
bytes = 0;
|
|
}
|
|
|
|
// remember the byte position of this accept
|
|
bytes_after_last_accept = bytes;
|
|
undumped_chars = 0;
|
|
break;
|
|
}
|
|
|
|
case UTF8_REJECT: // decode found invalid UTF-8 byte
|
|
{
|
|
switch (error_handler)
|
|
{
|
|
case error_handler_t::strict:
|
|
{
|
|
JSON_THROW(type_error::create(316, concat("invalid UTF-8 byte at index ", std::to_string(i), ": 0x", hex_bytes(byte | 0)), nullptr));
|
|
}
|
|
|
|
case error_handler_t::ignore:
|
|
case error_handler_t::replace:
|
|
{
|
|
// in case we saw this character the first time, we
|
|
// would like to read it again, because the byte
|
|
// may be OK for itself, but just not OK for the
|
|
// previous sequence
|
|
if (undumped_chars > 0)
|
|
{
|
|
--i;
|
|
}
|
|
|
|
// reset length buffer to the last accepted index;
|
|
// thus removing/ignoring the invalid characters
|
|
bytes = bytes_after_last_accept;
|
|
|
|
if (error_handler == error_handler_t::replace)
|
|
{
|
|
// add a replacement character
|
|
if (ensure_ascii)
|
|
{
|
|
string_buffer[bytes++] = '\\';
|
|
string_buffer[bytes++] = 'u';
|
|
string_buffer[bytes++] = 'f';
|
|
string_buffer[bytes++] = 'f';
|
|
string_buffer[bytes++] = 'f';
|
|
string_buffer[bytes++] = 'd';
|
|
}
|
|
else
|
|
{
|
|
string_buffer[bytes++] = detail::binary_writer<BasicJsonType, char>::to_char_type('\xEF');
|
|
string_buffer[bytes++] = detail::binary_writer<BasicJsonType, char>::to_char_type('\xBF');
|
|
string_buffer[bytes++] = detail::binary_writer<BasicJsonType, char>::to_char_type('\xBD');
|
|
}
|
|
|
|
// 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)
|
|
{
|
|
put_buffer(string_buffer, bytes);
|
|
bytes = 0;
|
|
}
|
|
|
|
bytes_after_last_accept = bytes;
|
|
}
|
|
|
|
undumped_chars = 0;
|
|
|
|
// continue processing the string
|
|
state = UTF8_ACCEPT;
|
|
break;
|
|
}
|
|
|
|
default: // LCOV_EXCL_LINE
|
|
JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE
|
|
}
|
|
break;
|
|
}
|
|
|
|
default: // decode found yet incomplete multibyte code point
|
|
{
|
|
if (!ensure_ascii)
|
|
{
|
|
// code point will not be escaped - copy byte to buffer
|
|
string_buffer[bytes++] = s[i];
|
|
}
|
|
++undumped_chars;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// we finished processing the string
|
|
if (JSON_HEDLEY_LIKELY(state == UTF8_ACCEPT))
|
|
{
|
|
// write buffer
|
|
if (bytes > 0)
|
|
{
|
|
put_buffer(string_buffer, bytes);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// we finish reading, but do not accept: string was incomplete
|
|
switch (error_handler)
|
|
{
|
|
case error_handler_t::strict:
|
|
{
|
|
JSON_THROW(type_error::create(316, concat("incomplete UTF-8 string; last byte: 0x", hex_bytes(static_cast<std::uint8_t>(s.back() | 0))), nullptr));
|
|
}
|
|
|
|
case error_handler_t::ignore:
|
|
{
|
|
// write all accepted bytes
|
|
put_buffer(string_buffer, bytes_after_last_accept);
|
|
break;
|
|
}
|
|
|
|
case error_handler_t::replace:
|
|
{
|
|
// write all accepted bytes
|
|
put_buffer(string_buffer, bytes_after_last_accept);
|
|
// add a replacement character
|
|
if (ensure_ascii)
|
|
{
|
|
put_literal("\\ufffd");
|
|
}
|
|
else
|
|
{
|
|
put_literal("\xEF\xBF\xBD");
|
|
}
|
|
break;
|
|
}
|
|
|
|
default: // LCOV_EXCL_LINE
|
|
JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE
|
|
}
|
|
}
|
|
}
|
|
|
|
private:
|
|
/*!
|
|
@brief append a single character to the write buffer
|
|
|
|
Structural characters ('{', '"', ',', ...) previously went straight to the
|
|
output adapter, one virtual call each. Buffering them and flushing in bulk
|
|
turns those many indirect calls into a single memcpy plus an occasional
|
|
flush, which dominates the cost of serializing object/array-heavy values.
|
|
*/
|
|
void put_char(char c)
|
|
{
|
|
if (JSON_HEDLEY_UNLIKELY(write_buffer_pos == write_buffer.size()))
|
|
{
|
|
flush();
|
|
}
|
|
write_buffer[write_buffer_pos++] = c;
|
|
}
|
|
|
|
/*!
|
|
@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 is emitted by filling the buffer with
|
|
the indentation character once and flushing that same content repeatedly:
|
|
flushing does not disturb what the buffer holds, so re-filling it between
|
|
flushes would be redundant work.
|
|
*/
|
|
void put_indent(std::size_t indent)
|
|
{
|
|
// closing braces at the outermost level ask for no indentation at all
|
|
if (indent == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
const std::size_t capacity = write_buffer.size();
|
|
|
|
// fill whatever room is left in the buffer; this is the whole job
|
|
// whenever the indentation is narrower than the buffer, which is the
|
|
// case for every sane indent_step
|
|
const std::size_t head = (std::min)(indent, capacity - write_buffer_pos);
|
|
std::memset(write_buffer.data() + write_buffer_pos, indent_char, head);
|
|
write_buffer_pos += head;
|
|
indent -= head;
|
|
|
|
if (JSON_HEDLEY_LIKELY(indent == 0))
|
|
{
|
|
return;
|
|
}
|
|
|
|
// the buffer is full and the remainder spans whole buffer-fulls: flush
|
|
// what is pending, then fill the buffer with the indentation character
|
|
// exactly once and hand the same bytes to the adapter as often as needed
|
|
flush();
|
|
std::memset(write_buffer.data(), indent_char, capacity);
|
|
|
|
while (indent >= capacity)
|
|
{
|
|
write_buffer_pos = capacity;
|
|
flush();
|
|
indent -= capacity;
|
|
}
|
|
|
|
// the buffer still holds indentation characters throughout, so the tail
|
|
// only has to be claimed, not written again
|
|
write_buffer_pos = indent;
|
|
}
|
|
|
|
/*!
|
|
@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]) // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)
|
|
{
|
|
static_assert(N >= 2, "put_literal expects a non-empty string literal");
|
|
// the array bound counts the terminating NUL, which is not written
|
|
constexpr std::size_t length = N - 1;
|
|
static_assert(length < write_buffer_size, "string literal must fit into the write buffer");
|
|
|
|
if (JSON_HEDLEY_UNLIKELY(write_buffer_pos + length > write_buffer.size()))
|
|
{
|
|
flush();
|
|
}
|
|
std::memcpy(write_buffer.data() + write_buffer_pos, s, length);
|
|
write_buffer_pos += length;
|
|
}
|
|
|
|
/*!
|
|
@brief append the characters of @a str in [@a start, @a end)
|
|
|
|
Keeps the pointer arithmetic and the bounds checking inside the function
|
|
rather than at the call site, which is all @ref put_chars could offer.
|
|
*/
|
|
template<typename StringType>
|
|
void put_string(const StringType& str, std::size_t start, std::size_t end)
|
|
{
|
|
JSON_ASSERT(start <= end);
|
|
JSON_ASSERT(end <= str.size());
|
|
put_chars(str.data() + start, end - start);
|
|
}
|
|
|
|
/*!
|
|
@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);
|
|
}
|
|
|
|
/*!
|
|
@brief append @a length characters to the write buffer
|
|
|
|
Runs that do not fit the buffer are written straight through the output
|
|
adapter (after flushing what is pending), so large string/number payloads
|
|
are not copied an extra time.
|
|
|
|
The callers all reach this through @ref put_literal, @ref put_buffer or
|
|
@ref put_string, which each derive the length from something that knows it.
|
|
*/
|
|
JSON_HEDLEY_NON_NULL(2)
|
|
void put_chars(const char* s, std::size_t length)
|
|
{
|
|
if (JSON_HEDLEY_UNLIKELY(length >= write_buffer.size()))
|
|
{
|
|
flush();
|
|
o->write_characters(s, length);
|
|
return;
|
|
}
|
|
if (JSON_HEDLEY_UNLIKELY(write_buffer_pos + length > write_buffer.size()))
|
|
{
|
|
flush();
|
|
}
|
|
std::memcpy(write_buffer.data() + write_buffer_pos, s, length);
|
|
write_buffer_pos += length;
|
|
}
|
|
|
|
JSON_PRIVATE_UNLESS_TESTED:
|
|
/*!
|
|
@brief flush the write buffer to the output adapter
|
|
|
|
Writing zero characters is a well-defined no-op for every output adapter, so
|
|
the buffered length is passed through unconditionally (no empty-guard branch
|
|
to leave uncovered).
|
|
|
|
@note dump_escaped() and dump_integer()/dump_float() write into the internal
|
|
write buffer; callers that invoke them directly (rather than through the
|
|
public dump()) must call flush() before inspecting the output.
|
|
*/
|
|
void flush()
|
|
{
|
|
o->write_characters(write_buffer.data(), write_buffer_pos);
|
|
write_buffer_pos = 0;
|
|
}
|
|
|
|
private:
|
|
/*!
|
|
@brief count digits
|
|
|
|
Count the number of decimal (base 10) digits for an input unsigned integer.
|
|
|
|
@param[in] x unsigned integer number to count its digits
|
|
@return number of decimal digits
|
|
*/
|
|
unsigned int count_digits(number_unsigned_t x) noexcept
|
|
{
|
|
unsigned int n_digits = 1;
|
|
for (;;)
|
|
{
|
|
if (x < 10)
|
|
{
|
|
return n_digits;
|
|
}
|
|
if (x < 100)
|
|
{
|
|
return n_digits + 1;
|
|
}
|
|
if (x < 1000)
|
|
{
|
|
return n_digits + 2;
|
|
}
|
|
if (x < 10000)
|
|
{
|
|
return n_digits + 3;
|
|
}
|
|
x = x / 10000u;
|
|
n_digits += 4;
|
|
}
|
|
}
|
|
|
|
/*!
|
|
* @brief convert a byte to a uppercase hex representation
|
|
* @param[in] byte byte to represent
|
|
* @return representation ("00".."FF")
|
|
*/
|
|
static std::string hex_bytes(std::uint8_t byte)
|
|
{
|
|
std::string result = "FF";
|
|
constexpr const char* nibble_to_hex = "0123456789ABCDEF";
|
|
result[0] = nibble_to_hex[byte / 16];
|
|
result[1] = nibble_to_hex[byte % 16];
|
|
return result;
|
|
}
|
|
|
|
/*!
|
|
* @brief write a lowercase "\uXXXX" escape sequence into @a string_buffer
|
|
*
|
|
* Branch-free replacement for `snprintf(buf, 7, "\\u%04x", codeunit)` in the
|
|
* string escaping hot path. It writes exactly six characters ('\\', 'u' and
|
|
* four hex digits) at position @a pos of @a string_buffer via a nibble
|
|
* lookup table, avoiding the format-string parsing and locale machinery of
|
|
* `snprintf`. Advances @a pos by the number of bytes written (6).
|
|
*
|
|
* @param[in] pos position in @a string_buffer to write at; there must
|
|
* be at least 6 bytes of headroom
|
|
* @param[in] codeunit 16-bit value to encode
|
|
*/
|
|
void write_u_escape(std::size_t& pos, std::uint16_t codeunit) noexcept
|
|
{
|
|
JSON_ASSERT(string_buffer.size() - pos >= 6);
|
|
constexpr const char* nibble_to_hex = "0123456789abcdef";
|
|
string_buffer[pos + 0] = '\\';
|
|
string_buffer[pos + 1] = 'u';
|
|
string_buffer[pos + 2] = nibble_to_hex[(codeunit >> 12u) & 0x0Fu];
|
|
string_buffer[pos + 3] = nibble_to_hex[(codeunit >> 8u) & 0x0Fu];
|
|
string_buffer[pos + 4] = nibble_to_hex[(codeunit >> 4u) & 0x0Fu];
|
|
string_buffer[pos + 5] = nibble_to_hex[codeunit & 0x0Fu];
|
|
pos += 6;
|
|
}
|
|
|
|
// templates to avoid warnings about useless casts
|
|
template <typename NumberType, enable_if_t<std::is_signed<NumberType>::value, int> = 0>
|
|
bool is_negative_number(NumberType x)
|
|
{
|
|
return x < 0;
|
|
}
|
|
|
|
template < typename NumberType, enable_if_t <std::is_unsigned<NumberType>::value, int > = 0 >
|
|
bool is_negative_number(NumberType /*unused*/)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
/*!
|
|
@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 < typename NumberType, detail::enable_if_t <
|
|
std::is_integral<NumberType>::value ||
|
|
std::is_same<NumberType, number_unsigned_t>::value ||
|
|
std::is_same<NumberType, number_integer_t>::value ||
|
|
std::is_same<NumberType, binary_char_t>::value,
|
|
int > = 0 >
|
|
void dump_integer(NumberType x)
|
|
{
|
|
static constexpr std::array<std::array<char, 2>, 100> digits_to_99
|
|
{
|
|
{
|
|
{{'0', '0'}}, {{'0', '1'}}, {{'0', '2'}}, {{'0', '3'}}, {{'0', '4'}}, {{'0', '5'}}, {{'0', '6'}}, {{'0', '7'}}, {{'0', '8'}}, {{'0', '9'}},
|
|
{{'1', '0'}}, {{'1', '1'}}, {{'1', '2'}}, {{'1', '3'}}, {{'1', '4'}}, {{'1', '5'}}, {{'1', '6'}}, {{'1', '7'}}, {{'1', '8'}}, {{'1', '9'}},
|
|
{{'2', '0'}}, {{'2', '1'}}, {{'2', '2'}}, {{'2', '3'}}, {{'2', '4'}}, {{'2', '5'}}, {{'2', '6'}}, {{'2', '7'}}, {{'2', '8'}}, {{'2', '9'}},
|
|
{{'3', '0'}}, {{'3', '1'}}, {{'3', '2'}}, {{'3', '3'}}, {{'3', '4'}}, {{'3', '5'}}, {{'3', '6'}}, {{'3', '7'}}, {{'3', '8'}}, {{'3', '9'}},
|
|
{{'4', '0'}}, {{'4', '1'}}, {{'4', '2'}}, {{'4', '3'}}, {{'4', '4'}}, {{'4', '5'}}, {{'4', '6'}}, {{'4', '7'}}, {{'4', '8'}}, {{'4', '9'}},
|
|
{{'5', '0'}}, {{'5', '1'}}, {{'5', '2'}}, {{'5', '3'}}, {{'5', '4'}}, {{'5', '5'}}, {{'5', '6'}}, {{'5', '7'}}, {{'5', '8'}}, {{'5', '9'}},
|
|
{{'6', '0'}}, {{'6', '1'}}, {{'6', '2'}}, {{'6', '3'}}, {{'6', '4'}}, {{'6', '5'}}, {{'6', '6'}}, {{'6', '7'}}, {{'6', '8'}}, {{'6', '9'}},
|
|
{{'7', '0'}}, {{'7', '1'}}, {{'7', '2'}}, {{'7', '3'}}, {{'7', '4'}}, {{'7', '5'}}, {{'7', '6'}}, {{'7', '7'}}, {{'7', '8'}}, {{'7', '9'}},
|
|
{{'8', '0'}}, {{'8', '1'}}, {{'8', '2'}}, {{'8', '3'}}, {{'8', '4'}}, {{'8', '5'}}, {{'8', '6'}}, {{'8', '7'}}, {{'8', '8'}}, {{'8', '9'}},
|
|
{{'9', '0'}}, {{'9', '1'}}, {{'9', '2'}}, {{'9', '3'}}, {{'9', '4'}}, {{'9', '5'}}, {{'9', '6'}}, {{'9', '7'}}, {{'9', '8'}}, {{'9', '9'}},
|
|
}
|
|
};
|
|
|
|
// special case for "0"
|
|
if (x == 0)
|
|
{
|
|
put_char('0');
|
|
return;
|
|
}
|
|
|
|
// use a pointer to fill the buffer
|
|
auto buffer_ptr = number_buffer.begin(); // NOLINT(llvm-qualified-auto,readability-qualified-auto,cppcoreguidelines-pro-type-vararg,hicpp-vararg)
|
|
|
|
number_unsigned_t abs_value;
|
|
|
|
unsigned int n_chars{};
|
|
|
|
if (is_negative_number(x))
|
|
{
|
|
*buffer_ptr = '-';
|
|
abs_value = remove_sign(static_cast<number_integer_t>(x));
|
|
|
|
// account one more byte for the minus sign
|
|
n_chars = 1 + count_digits(abs_value);
|
|
}
|
|
else
|
|
{
|
|
abs_value = static_cast<number_unsigned_t>(x);
|
|
n_chars = count_digits(abs_value);
|
|
}
|
|
|
|
// spare 1 byte for '\0'
|
|
JSON_ASSERT(n_chars < number_buffer.size() - 1);
|
|
|
|
// jump to the end to generate the string from backward,
|
|
// so we later avoid reversing the result
|
|
buffer_ptr += static_cast<typename decltype(number_buffer)::difference_type>(n_chars);
|
|
|
|
// Fast int2ascii implementation inspired by "Fastware" talk by Andrei Alexandrescu
|
|
// See: https://www.youtube.com/watch?v=o4-CwDo2zpg
|
|
while (abs_value >= 100)
|
|
{
|
|
const auto digits_index = static_cast<unsigned>((abs_value % 100));
|
|
abs_value /= 100;
|
|
*(--buffer_ptr) = digits_to_99[digits_index][1];
|
|
*(--buffer_ptr) = digits_to_99[digits_index][0];
|
|
}
|
|
|
|
if (abs_value >= 10)
|
|
{
|
|
const auto digits_index = static_cast<unsigned>(abs_value);
|
|
*(--buffer_ptr) = digits_to_99[digits_index][1];
|
|
*(--buffer_ptr) = digits_to_99[digits_index][0];
|
|
}
|
|
else
|
|
{
|
|
*(--buffer_ptr) = static_cast<char>('0' + abs_value);
|
|
}
|
|
|
|
put_buffer(number_buffer, n_chars);
|
|
}
|
|
|
|
/*!
|
|
@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 (!std::isfinite(x))
|
|
{
|
|
put_literal("null");
|
|
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 <long double> == <double>.
|
|
static constexpr bool is_ieee_single_or_double
|
|
= (std::numeric_limits<number_float_t>::is_iec559 && std::numeric_limits<number_float_t>::digits == 24 && std::numeric_limits<number_float_t>::max_exponent == 128) ||
|
|
(std::numeric_limits<number_float_t>::is_iec559 && std::numeric_limits<number_float_t>::digits == 53 && std::numeric_limits<number_float_t>::max_exponent == 1024);
|
|
|
|
dump_float(x, std::integral_constant<bool, is_ieee_single_or_double>());
|
|
}
|
|
|
|
void dump_float(number_float_t x, std::true_type /*is_ieee_single_or_double*/)
|
|
{
|
|
auto* begin = number_buffer.data();
|
|
auto* end = ::nlohmann::detail::to_chars(begin, begin + number_buffer.size(), x);
|
|
|
|
put_buffer(number_buffer, static_cast<std::size_t>(end - begin));
|
|
}
|
|
|
|
JSON_HEDLEY_NON_NULL(1)
|
|
static int snprintf_float(char* buf, std::size_t size, int d, double x)
|
|
{
|
|
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg,hicpp-vararg)
|
|
return (std::snprintf)(buf, size, "%.*g", d, x);
|
|
}
|
|
|
|
JSON_HEDLEY_NON_NULL(1)
|
|
static int snprintf_float(char* buf, std::size_t size, int d, long double x)
|
|
{
|
|
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg,hicpp-vararg)
|
|
return (std::snprintf)(buf, size, "%.*Lg", d, x);
|
|
}
|
|
|
|
void dump_float(number_float_t x, std::false_type /*is_ieee_single_or_double*/)
|
|
{
|
|
// get the number of digits for a float -> text -> float round-trip
|
|
static constexpr auto d = std::numeric_limits<number_float_t>::max_digits10;
|
|
|
|
// the actual conversion
|
|
std::ptrdiff_t len = snprintf_float(number_buffer.data(), number_buffer.size(), d, x);
|
|
|
|
// negative value indicates an error
|
|
JSON_ASSERT(len > 0);
|
|
// check if the buffer was large enough
|
|
JSON_ASSERT(static_cast<std::size_t>(len) < number_buffer.size());
|
|
|
|
// erase thousands separators
|
|
if (thousands_sep != '\0')
|
|
{
|
|
// NOLINTNEXTLINE(readability-qualified-auto,llvm-qualified-auto): std::remove returns an iterator, see https://github.com/nlohmann/json/issues/3081
|
|
const auto end = std::remove(number_buffer.begin(), number_buffer.begin() + len, thousands_sep);
|
|
std::fill(end, number_buffer.end(), '\0');
|
|
JSON_ASSERT((end - number_buffer.begin()) <= len);
|
|
len = (end - number_buffer.begin());
|
|
}
|
|
|
|
// convert decimal point to '.'
|
|
if (decimal_point != '\0' && decimal_point != '.')
|
|
{
|
|
// NOLINTNEXTLINE(readability-qualified-auto,llvm-qualified-auto): std::find returns an iterator, see https://github.com/nlohmann/json/issues/3081
|
|
const auto dec_pos = std::find(number_buffer.begin(), number_buffer.end(), decimal_point);
|
|
if (dec_pos != number_buffer.end())
|
|
{
|
|
*dec_pos = '.';
|
|
}
|
|
}
|
|
|
|
put_buffer(number_buffer, static_cast<std::size_t>(len));
|
|
|
|
// determine if we 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 == '.' || c == 'e';
|
|
});
|
|
|
|
if (value_is_int_like)
|
|
{
|
|
put_literal(".0");
|
|
}
|
|
}
|
|
|
|
/*!
|
|
@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 <bjoern@hoehrmann.de>
|
|
@sa http://bjoern.hoehrmann.de/utf-8/decoder/dfa/
|
|
*/
|
|
static std::uint8_t decode(std::uint8_t& state, std::uint32_t& codep, const std::uint8_t byte) noexcept
|
|
{
|
|
static const std::array<std::uint8_t, 400> 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
|
|
}
|
|
};
|
|
|
|
JSON_ASSERT(static_cast<std::size_t>(byte) < utf8d.size());
|
|
const std::uint8_t type = utf8d[byte];
|
|
|
|
codep = (state != UTF8_ACCEPT)
|
|
? (byte & 0x3fu) | (codep << 6u)
|
|
: (0xFFu >> type) & (byte);
|
|
|
|
const std::size_t index = 256u + (static_cast<size_t>(state) * 16u) + static_cast<size_t>(type);
|
|
JSON_ASSERT(index < utf8d.size());
|
|
state = utf8d[index];
|
|
return state;
|
|
}
|
|
|
|
/*
|
|
* Overload to make the compiler happy while it is instantiating
|
|
* dump_integer for number_unsigned_t.
|
|
* Must never be called.
|
|
*/
|
|
number_unsigned_t remove_sign(number_unsigned_t x)
|
|
{
|
|
JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE
|
|
return x; // LCOV_EXCL_LINE
|
|
}
|
|
|
|
/*
|
|
* Helper function for dump_integer
|
|
*
|
|
* This function takes a negative signed integer and returns its absolute
|
|
* value as an unsigned integer. The plus/minus shuffling is necessary as we
|
|
* cannot directly remove the sign of an arbitrary signed integer as the
|
|
* absolute values of INT_MIN and INT_MAX are usually not the same. See
|
|
* #1708 for details.
|
|
*/
|
|
number_unsigned_t remove_sign(number_integer_t x) noexcept
|
|
{
|
|
JSON_ASSERT(x < 0 && x < (std::numeric_limits<number_integer_t>::max)()); // NOLINT(misc-redundant-expression)
|
|
return static_cast<number_unsigned_t>(-(x + 1)) + 1;
|
|
}
|
|
|
|
private:
|
|
/// the output of the serializer
|
|
output_adapter_t<char> o = nullptr;
|
|
|
|
/// a (hopefully) large enough character buffer
|
|
std::array<char, 64> 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<char, 512> string_buffer{{}};
|
|
|
|
/// the indentation character
|
|
const char indent_char;
|
|
|
|
/// error_handler how to react on decoding errors
|
|
const error_handler_t error_handler;
|
|
|
|
/// buffer collecting output before it is flushed to the output adapter, so
|
|
/// that the many small structural writes become few bulk writes
|
|
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
|
|
std::size_t write_buffer_pos = 0;
|
|
};
|
|
|
|
} // namespace detail
|
|
NLOHMANN_JSON_NAMESPACE_END
|