💄 cleanup
- Added comments for the serializer class. - Added test case for resizing of the indentation string. - Using std::none_of to check if “.0” needs to be added to floating-point number.
This commit is contained in:
parent
059f21aada
commit
d69242c6ba
3 changed files with 138 additions and 70 deletions
98
src/json.hpp
98
src/json.hpp
|
@ -6203,6 +6203,9 @@ class basic_json
|
||||||
class serializer
|
class serializer
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
/*!
|
||||||
|
@param[in] s output stream to serialize to
|
||||||
|
*/
|
||||||
serializer(std::ostream& s)
|
serializer(std::ostream& s)
|
||||||
: o(s), loc(std::localeconv()),
|
: o(s), loc(std::localeconv()),
|
||||||
thousands_sep(!loc->thousands_sep ? '\0' : loc->thousands_sep[0]),
|
thousands_sep(!loc->thousands_sep ? '\0' : loc->thousands_sep[0]),
|
||||||
|
@ -6212,10 +6215,10 @@ class basic_json
|
||||||
/*!
|
/*!
|
||||||
@brief internal implementation of the serialization function
|
@brief internal implementation of the serialization function
|
||||||
|
|
||||||
This function is called by the public member function dump and organizes
|
This function is called by the public member function dump and
|
||||||
the serialization internally. The indentation level is propagated as
|
organizes the serialization internally. The indentation level is
|
||||||
additional parameter. In case of arrays and objects, the function is
|
propagated as additional parameter. In case of arrays and objects, the
|
||||||
called recursively. Note that
|
function is called recursively.
|
||||||
|
|
||||||
- strings and object keys are escaped using `escape_string()`
|
- strings and object keys are escaped using `escape_string()`
|
||||||
- integer numbers are converted implicitly via `operator<<`
|
- integer numbers are converted implicitly via `operator<<`
|
||||||
|
@ -6483,15 +6486,14 @@ class basic_json
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@brief escape a string
|
@brief dump escaped string
|
||||||
|
|
||||||
Escape a string by replacing certain special characters by a sequence of
|
Escape a string by replacing certain special characters by a sequence
|
||||||
an escape character (backslash) and another character and other control
|
of an escape character (backslash) and another character and other
|
||||||
characters by a sequence of "\u" followed by a four-digit hex
|
control characters by a sequence of "\u" followed by a four-digit hex
|
||||||
representation.
|
representation. The escaped string is written to output stream @a o.
|
||||||
|
|
||||||
@param[in] s the string to escape
|
@param[in] s the string to escape
|
||||||
@return the escaped string
|
|
||||||
|
|
||||||
@complexity Linear in the length of string @a s.
|
@complexity Linear in the length of string @a s.
|
||||||
*/
|
*/
|
||||||
|
@ -6629,7 +6631,18 @@ class basic_json
|
||||||
o.write(result.c_str(), static_cast<std::streamsize>(result.size()));
|
o.write(result.c_str(), static_cast<std::streamsize>(result.size()));
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename NumberType>
|
/*!
|
||||||
|
@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_same<NumberType, number_unsigned_t>::value or
|
||||||
|
std::is_same<NumberType, number_integer_t>::value, int> = 0>
|
||||||
void dump_integer(NumberType x)
|
void dump_integer(NumberType x)
|
||||||
{
|
{
|
||||||
// special case for "0"
|
// special case for "0"
|
||||||
|
@ -6643,10 +6656,10 @@ class basic_json
|
||||||
size_t i = 0;
|
size_t i = 0;
|
||||||
|
|
||||||
// spare 1 byte for '\0'
|
// spare 1 byte for '\0'
|
||||||
while (x != 0 and i < m_buf.size() - 1)
|
while (x != 0 and i < number_buffer.size() - 1)
|
||||||
{
|
{
|
||||||
const auto digit = std::labs(static_cast<long>(x % 10));
|
const auto digit = std::labs(static_cast<long>(x % 10));
|
||||||
m_buf[i++] = static_cast<char>('0' + digit);
|
number_buffer[i++] = static_cast<char>('0' + digit);
|
||||||
x /= 10;
|
x /= 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6656,14 +6669,22 @@ class basic_json
|
||||||
if (is_negative)
|
if (is_negative)
|
||||||
{
|
{
|
||||||
// make sure there is capacity for the '-'
|
// make sure there is capacity for the '-'
|
||||||
assert(i < m_buf.size() - 2);
|
assert(i < number_buffer.size() - 2);
|
||||||
m_buf[i++] = '-';
|
number_buffer[i++] = '-';
|
||||||
}
|
}
|
||||||
|
|
||||||
std::reverse(m_buf.begin(), m_buf.begin() + i);
|
std::reverse(number_buffer.begin(), number_buffer.begin() + i);
|
||||||
o.write(m_buf.data(), static_cast<std::streamsize>(i));
|
o.write(number_buffer.data(), static_cast<std::streamsize>(i));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief dump a floating-point number
|
||||||
|
|
||||||
|
Dump a given floating-point number to output stream @a o. Works
|
||||||
|
internally with @a number_buffer.
|
||||||
|
|
||||||
|
@param[in] x floating-point number to dump
|
||||||
|
*/
|
||||||
void dump_float(number_float_t x)
|
void dump_float(number_float_t x)
|
||||||
{
|
{
|
||||||
// special case for 0.0 and -0.0
|
// special case for 0.0 and -0.0
|
||||||
|
@ -6684,26 +6705,29 @@ class basic_json
|
||||||
static constexpr auto d = std::numeric_limits<number_float_t>::digits10;
|
static constexpr auto d = std::numeric_limits<number_float_t>::digits10;
|
||||||
|
|
||||||
// the actual conversion
|
// the actual conversion
|
||||||
long written_bytes = snprintf(m_buf.data(), m_buf.size(), "%.*g", d, x);
|
long len = snprintf(number_buffer.data(), number_buffer.size(),
|
||||||
|
"%.*g", d, x);
|
||||||
|
|
||||||
// negative value indicates an error
|
// negative value indicates an error
|
||||||
assert(written_bytes > 0);
|
assert(len > 0);
|
||||||
// check if buffer was large enough
|
// check if buffer was large enough
|
||||||
assert(static_cast<size_t>(written_bytes) < m_buf.size());
|
assert(static_cast<size_t>(len) < number_buffer.size());
|
||||||
|
|
||||||
// erase thousands separator
|
// erase thousands separator
|
||||||
if (thousands_sep != '\0')
|
if (thousands_sep != '\0')
|
||||||
{
|
{
|
||||||
const auto end = std::remove(m_buf.begin(), m_buf.begin() + written_bytes, thousands_sep);
|
const auto end = std::remove(number_buffer.begin(),
|
||||||
std::fill(end, m_buf.end(), '\0');
|
number_buffer.begin() + len,
|
||||||
assert((end - m_buf.begin()) <= written_bytes);
|
thousands_sep);
|
||||||
written_bytes = (end - m_buf.begin());
|
std::fill(end, number_buffer.end(), '\0');
|
||||||
|
assert((end - number_buffer.begin()) <= len);
|
||||||
|
len = (end - number_buffer.begin());
|
||||||
}
|
}
|
||||||
|
|
||||||
// convert decimal point to '.'
|
// convert decimal point to '.'
|
||||||
if (decimal_point != '\0' and decimal_point != '.')
|
if (decimal_point != '\0' and decimal_point != '.')
|
||||||
{
|
{
|
||||||
for (auto& c : m_buf)
|
for (auto& c : number_buffer)
|
||||||
{
|
{
|
||||||
if (c == decimal_point)
|
if (c == decimal_point)
|
||||||
{
|
{
|
||||||
|
@ -6713,16 +6737,15 @@ class basic_json
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// determine if need to append ".0"
|
o.write(number_buffer.data(), static_cast<std::streamsize>(len));
|
||||||
bool value_is_int_like = true;
|
|
||||||
for (size_t i = 0; i < static_cast<size_t>(written_bytes); ++i)
|
|
||||||
{
|
|
||||||
// check if we find non-int character
|
|
||||||
value_is_int_like = value_is_int_like and m_buf[i] != '.' and
|
|
||||||
m_buf[i] != 'e';
|
|
||||||
}
|
|
||||||
|
|
||||||
o.write(m_buf.data(), static_cast<std::streamsize>(written_bytes));
|
// determine if need to append ".0"
|
||||||
|
const bool value_is_int_like = std::none_of(number_buffer.begin(),
|
||||||
|
number_buffer.begin() + len + 1,
|
||||||
|
[](char c)
|
||||||
|
{
|
||||||
|
return c == '.' or c == 'e';
|
||||||
|
});
|
||||||
|
|
||||||
if (value_is_int_like)
|
if (value_is_int_like)
|
||||||
{
|
{
|
||||||
|
@ -6731,15 +6754,20 @@ class basic_json
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
/// the output of the serializer
|
||||||
std::ostream& o;
|
std::ostream& o;
|
||||||
|
|
||||||
/// a (hopefully) large enough character buffer
|
/// a (hopefully) large enough character buffer
|
||||||
std::array < char, 64 > m_buf{{}};
|
std::array<char, 64> number_buffer{{}};
|
||||||
|
|
||||||
|
/// the locale
|
||||||
const std::lconv* loc = nullptr;
|
const std::lconv* loc = nullptr;
|
||||||
|
/// the locale's thousand separator character
|
||||||
const char thousands_sep = '\0';
|
const char thousands_sep = '\0';
|
||||||
|
/// the locale's decimal point character
|
||||||
const char decimal_point = '\0';
|
const char decimal_point = '\0';
|
||||||
|
|
||||||
|
/// the indentation string
|
||||||
string_t indent_string = string_t(512, ' ');
|
string_t indent_string = string_t(512, ' ');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -6203,6 +6203,9 @@ class basic_json
|
||||||
class serializer
|
class serializer
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
/*!
|
||||||
|
@param[in] s output stream to serialize to
|
||||||
|
*/
|
||||||
serializer(std::ostream& s)
|
serializer(std::ostream& s)
|
||||||
: o(s), loc(std::localeconv()),
|
: o(s), loc(std::localeconv()),
|
||||||
thousands_sep(!loc->thousands_sep ? '\0' : loc->thousands_sep[0]),
|
thousands_sep(!loc->thousands_sep ? '\0' : loc->thousands_sep[0]),
|
||||||
|
@ -6212,10 +6215,10 @@ class basic_json
|
||||||
/*!
|
/*!
|
||||||
@brief internal implementation of the serialization function
|
@brief internal implementation of the serialization function
|
||||||
|
|
||||||
This function is called by the public member function dump and organizes
|
This function is called by the public member function dump and
|
||||||
the serialization internally. The indentation level is propagated as
|
organizes the serialization internally. The indentation level is
|
||||||
additional parameter. In case of arrays and objects, the function is
|
propagated as additional parameter. In case of arrays and objects, the
|
||||||
called recursively. Note that
|
function is called recursively.
|
||||||
|
|
||||||
- strings and object keys are escaped using `escape_string()`
|
- strings and object keys are escaped using `escape_string()`
|
||||||
- integer numbers are converted implicitly via `operator<<`
|
- integer numbers are converted implicitly via `operator<<`
|
||||||
|
@ -6483,15 +6486,14 @@ class basic_json
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@brief escape a string
|
@brief dump escaped string
|
||||||
|
|
||||||
Escape a string by replacing certain special characters by a sequence of
|
Escape a string by replacing certain special characters by a sequence
|
||||||
an escape character (backslash) and another character and other control
|
of an escape character (backslash) and another character and other
|
||||||
characters by a sequence of "\u" followed by a four-digit hex
|
control characters by a sequence of "\u" followed by a four-digit hex
|
||||||
representation.
|
representation. The escaped string is written to output stream @a o.
|
||||||
|
|
||||||
@param[in] s the string to escape
|
@param[in] s the string to escape
|
||||||
@return the escaped string
|
|
||||||
|
|
||||||
@complexity Linear in the length of string @a s.
|
@complexity Linear in the length of string @a s.
|
||||||
*/
|
*/
|
||||||
|
@ -6629,7 +6631,18 @@ class basic_json
|
||||||
o.write(result.c_str(), static_cast<std::streamsize>(result.size()));
|
o.write(result.c_str(), static_cast<std::streamsize>(result.size()));
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename NumberType>
|
/*!
|
||||||
|
@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_same<NumberType, number_unsigned_t>::value or
|
||||||
|
std::is_same<NumberType, number_integer_t>::value, int> = 0>
|
||||||
void dump_integer(NumberType x)
|
void dump_integer(NumberType x)
|
||||||
{
|
{
|
||||||
// special case for "0"
|
// special case for "0"
|
||||||
|
@ -6643,10 +6656,10 @@ class basic_json
|
||||||
size_t i = 0;
|
size_t i = 0;
|
||||||
|
|
||||||
// spare 1 byte for '\0'
|
// spare 1 byte for '\0'
|
||||||
while (x != 0 and i < m_buf.size() - 1)
|
while (x != 0 and i < number_buffer.size() - 1)
|
||||||
{
|
{
|
||||||
const auto digit = std::labs(static_cast<long>(x % 10));
|
const auto digit = std::labs(static_cast<long>(x % 10));
|
||||||
m_buf[i++] = static_cast<char>('0' + digit);
|
number_buffer[i++] = static_cast<char>('0' + digit);
|
||||||
x /= 10;
|
x /= 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6656,14 +6669,22 @@ class basic_json
|
||||||
if (is_negative)
|
if (is_negative)
|
||||||
{
|
{
|
||||||
// make sure there is capacity for the '-'
|
// make sure there is capacity for the '-'
|
||||||
assert(i < m_buf.size() - 2);
|
assert(i < number_buffer.size() - 2);
|
||||||
m_buf[i++] = '-';
|
number_buffer[i++] = '-';
|
||||||
}
|
}
|
||||||
|
|
||||||
std::reverse(m_buf.begin(), m_buf.begin() + i);
|
std::reverse(number_buffer.begin(), number_buffer.begin() + i);
|
||||||
o.write(m_buf.data(), static_cast<std::streamsize>(i));
|
o.write(number_buffer.data(), static_cast<std::streamsize>(i));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief dump a floating-point number
|
||||||
|
|
||||||
|
Dump a given floating-point number to output stream @a o. Works
|
||||||
|
internally with @a number_buffer.
|
||||||
|
|
||||||
|
@param[in] x floating-point number to dump
|
||||||
|
*/
|
||||||
void dump_float(number_float_t x)
|
void dump_float(number_float_t x)
|
||||||
{
|
{
|
||||||
// special case for 0.0 and -0.0
|
// special case for 0.0 and -0.0
|
||||||
|
@ -6684,26 +6705,29 @@ class basic_json
|
||||||
static constexpr auto d = std::numeric_limits<number_float_t>::digits10;
|
static constexpr auto d = std::numeric_limits<number_float_t>::digits10;
|
||||||
|
|
||||||
// the actual conversion
|
// the actual conversion
|
||||||
long written_bytes = snprintf(m_buf.data(), m_buf.size(), "%.*g", d, x);
|
long len = snprintf(number_buffer.data(), number_buffer.size(),
|
||||||
|
"%.*g", d, x);
|
||||||
|
|
||||||
// negative value indicates an error
|
// negative value indicates an error
|
||||||
assert(written_bytes > 0);
|
assert(len > 0);
|
||||||
// check if buffer was large enough
|
// check if buffer was large enough
|
||||||
assert(static_cast<size_t>(written_bytes) < m_buf.size());
|
assert(static_cast<size_t>(len) < number_buffer.size());
|
||||||
|
|
||||||
// erase thousands separator
|
// erase thousands separator
|
||||||
if (thousands_sep != '\0')
|
if (thousands_sep != '\0')
|
||||||
{
|
{
|
||||||
const auto end = std::remove(m_buf.begin(), m_buf.begin() + written_bytes, thousands_sep);
|
const auto end = std::remove(number_buffer.begin(),
|
||||||
std::fill(end, m_buf.end(), '\0');
|
number_buffer.begin() + len,
|
||||||
assert((end - m_buf.begin()) <= written_bytes);
|
thousands_sep);
|
||||||
written_bytes = (end - m_buf.begin());
|
std::fill(end, number_buffer.end(), '\0');
|
||||||
|
assert((end - number_buffer.begin()) <= len);
|
||||||
|
len = (end - number_buffer.begin());
|
||||||
}
|
}
|
||||||
|
|
||||||
// convert decimal point to '.'
|
// convert decimal point to '.'
|
||||||
if (decimal_point != '\0' and decimal_point != '.')
|
if (decimal_point != '\0' and decimal_point != '.')
|
||||||
{
|
{
|
||||||
for (auto& c : m_buf)
|
for (auto& c : number_buffer)
|
||||||
{
|
{
|
||||||
if (c == decimal_point)
|
if (c == decimal_point)
|
||||||
{
|
{
|
||||||
|
@ -6713,16 +6737,15 @@ class basic_json
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// determine if need to append ".0"
|
o.write(number_buffer.data(), static_cast<std::streamsize>(len));
|
||||||
bool value_is_int_like = true;
|
|
||||||
for (size_t i = 0; i < static_cast<size_t>(written_bytes); ++i)
|
|
||||||
{
|
|
||||||
// check if we find non-int character
|
|
||||||
value_is_int_like = value_is_int_like and m_buf[i] != '.' and
|
|
||||||
m_buf[i] != 'e';
|
|
||||||
}
|
|
||||||
|
|
||||||
o.write(m_buf.data(), static_cast<std::streamsize>(written_bytes));
|
// determine if need to append ".0"
|
||||||
|
const bool value_is_int_like = std::none_of(number_buffer.begin(),
|
||||||
|
number_buffer.begin() + len + 1,
|
||||||
|
[](char c)
|
||||||
|
{
|
||||||
|
return c == '.' or c == 'e';
|
||||||
|
});
|
||||||
|
|
||||||
if (value_is_int_like)
|
if (value_is_int_like)
|
||||||
{
|
{
|
||||||
|
@ -6731,15 +6754,20 @@ class basic_json
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
/// the output of the serializer
|
||||||
std::ostream& o;
|
std::ostream& o;
|
||||||
|
|
||||||
/// a (hopefully) large enough character buffer
|
/// a (hopefully) large enough character buffer
|
||||||
std::array < char, 64 > m_buf{{}};
|
std::array<char, 64> number_buffer{{}};
|
||||||
|
|
||||||
|
/// the locale
|
||||||
const std::lconv* loc = nullptr;
|
const std::lconv* loc = nullptr;
|
||||||
|
/// the locale's thousand separator character
|
||||||
const char thousands_sep = '\0';
|
const char thousands_sep = '\0';
|
||||||
|
/// the locale's decimal point character
|
||||||
const char decimal_point = '\0';
|
const char decimal_point = '\0';
|
||||||
|
|
||||||
|
/// the indentation string
|
||||||
string_t indent_string = string_t(512, ' ');
|
string_t indent_string = string_t(512, ' ');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -213,6 +213,18 @@ TEST_CASE("object inspection")
|
||||||
"{\n \"array\": [\n 1,\n 2,\n 3,\n 4\n ],\n \"boolean\": false,\n \"null\": null,\n \"number\": 42,\n \"object\": {},\n \"string\": \"Hello world\"\n}");
|
"{\n \"array\": [\n 1,\n 2,\n 3,\n 4\n ],\n \"boolean\": false,\n \"null\": null,\n \"number\": 42,\n \"object\": {},\n \"string\": \"Hello world\"\n}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SECTION("indent=x")
|
||||||
|
{
|
||||||
|
CHECK(j.dump().size() == 94);
|
||||||
|
CHECK(j.dump(1).size() == 127);
|
||||||
|
CHECK(j.dump(2).size() == 142);
|
||||||
|
CHECK(j.dump(512).size() == 7792);
|
||||||
|
|
||||||
|
// important test, because it yields a resize of the indent_string
|
||||||
|
// inside the dump() function
|
||||||
|
CHECK(j.dump(1024).size() == 15472);
|
||||||
|
}
|
||||||
|
|
||||||
SECTION("dump and floating-point numbers")
|
SECTION("dump and floating-point numbers")
|
||||||
{
|
{
|
||||||
auto s = json(42.23).dump();
|
auto s = json(42.23).dump();
|
||||||
|
|
Loading…
Reference in a new issue