💄 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:
Niels Lohmann 2017-02-28 19:20:50 +01:00
parent 059f21aada
commit d69242c6ba
No known key found for this signature in database
GPG key ID: 7F3CEA63AE251B69
3 changed files with 138 additions and 70 deletions

View file

@ -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, ' ');
}; };

View file

@ -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, ' ');
}; };

View file

@ -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();