Add a dump variant to serialize directly to an ostream, and improve the performance of string escaping
This commit is contained in:
parent
58d7342be7
commit
d07c83deb3
238
src/json.hpp
238
src/json.hpp
@ -789,6 +789,244 @@ class basic_json
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief serialization
|
||||
|
||||
Calculates the extra space required to escape a JSON string
|
||||
*/
|
||||
inline std::size_t extra_space(const string_t& s) const noexcept
|
||||
{
|
||||
std::size_t count = 0;
|
||||
|
||||
for (const auto c : s)
|
||||
{
|
||||
switch (c)
|
||||
{
|
||||
case '"':
|
||||
case '\\':
|
||||
case '\b':
|
||||
case '\f':
|
||||
case '\n':
|
||||
case '\r':
|
||||
case '\t':
|
||||
count += 1;
|
||||
break;
|
||||
default:
|
||||
{
|
||||
if (c >= 0 and c <= 0x1f)
|
||||
{
|
||||
count += 6;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief serialization
|
||||
|
||||
Rapid string escaping alternative
|
||||
|
||||
First, does a linear scan of the input and calculates the additional required
|
||||
storage. Then, if no escaping is necessary, returns the input. Otherwise,
|
||||
it initialized an appropriately sized output string, and iterates through it
|
||||
replacing the necessary non-escape characters during the pass.
|
||||
*/
|
||||
string_t fast_escape_string(const string_t& s) const noexcept
|
||||
{
|
||||
auto space = extra_space(s);
|
||||
if (!space) {
|
||||
return s;
|
||||
}
|
||||
|
||||
// create a result string of necessary size
|
||||
const auto len = space + s.size();
|
||||
string_t result(len, '\\');
|
||||
std::size_t pos = 0;
|
||||
|
||||
for (const auto c : s)
|
||||
{
|
||||
switch (c)
|
||||
{
|
||||
// quotation mark (0x22)
|
||||
case '"':
|
||||
{
|
||||
result[pos + 1] = '"';
|
||||
pos += 2;
|
||||
break;
|
||||
}
|
||||
|
||||
// reverse solidus (0x5c)
|
||||
case '\\':
|
||||
{
|
||||
pos += 2;
|
||||
break;
|
||||
}
|
||||
|
||||
// backspace (0x08)
|
||||
case '\b':
|
||||
{
|
||||
result[pos + 1] = 'b';
|
||||
pos += 2;
|
||||
break;
|
||||
}
|
||||
|
||||
// formfeed (0x0c)
|
||||
case '\f':
|
||||
{
|
||||
result[pos + 1] = 'f';
|
||||
pos += 2;
|
||||
break;
|
||||
}
|
||||
|
||||
// newline (0x0a)
|
||||
case '\n':
|
||||
{
|
||||
result[pos + 1] = 'n';
|
||||
pos += 2;
|
||||
break;
|
||||
}
|
||||
|
||||
// carriage return (0x0d)
|
||||
case '\r':
|
||||
{
|
||||
result[pos + 1] = 'r';
|
||||
pos += 2;
|
||||
break;
|
||||
}
|
||||
|
||||
// horizontal tab (0x09)
|
||||
case '\t':
|
||||
{
|
||||
result[pos + 1] = 't';
|
||||
pos += 2;
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
if (c >= 0 and c <= 0x1f)
|
||||
{
|
||||
result[pos + 1] = 'u';
|
||||
snprintf(&result[pos + 2], 4, "%04x", int(c));
|
||||
pos += 6;
|
||||
result[pos] = (pos == len) ? '\0' : '\\';
|
||||
}
|
||||
else
|
||||
{
|
||||
// all other characters are added as-is
|
||||
result[pos] = c;
|
||||
++pos;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief serialization
|
||||
|
||||
Serialized the JSON value to an ostream
|
||||
*/
|
||||
inline void dump(std::ostream& out) const noexcept
|
||||
{
|
||||
switch (m_type)
|
||||
{
|
||||
case (value_t::object):
|
||||
{
|
||||
if (m_value.object->empty())
|
||||
{
|
||||
out << "{}";
|
||||
return;
|
||||
}
|
||||
|
||||
out << "{";
|
||||
|
||||
for (auto i = m_value.object->cbegin(); i != m_value.object->cend(); ++i)
|
||||
{
|
||||
if (i != m_value.object->cbegin())
|
||||
{
|
||||
out << ",";
|
||||
}
|
||||
out << "\"" << fast_escape_string(i->first) << "\":";
|
||||
i->second.dump(out);
|
||||
}
|
||||
|
||||
out << "}";
|
||||
break;
|
||||
}
|
||||
|
||||
case (value_t::array):
|
||||
{
|
||||
if (m_value.array->empty())
|
||||
{
|
||||
out << "[]";
|
||||
return;
|
||||
}
|
||||
|
||||
out << "[";
|
||||
|
||||
for (auto i = m_value.array->cbegin(); i != m_value.array->cend(); ++i)
|
||||
{
|
||||
if (i != m_value.array->cbegin())
|
||||
{
|
||||
out << ",";
|
||||
}
|
||||
i->dump(out);
|
||||
}
|
||||
|
||||
out << "]";
|
||||
break;
|
||||
}
|
||||
|
||||
case (value_t::string):
|
||||
{
|
||||
out << "\"" << fast_escape_string(*m_value.string) << "\"";
|
||||
break;
|
||||
}
|
||||
|
||||
case (value_t::boolean):
|
||||
{
|
||||
out << (m_value.boolean ? "true" : "false");
|
||||
break;
|
||||
}
|
||||
|
||||
case (value_t::number_integer):
|
||||
{
|
||||
out << std::to_string(m_value.number_integer);
|
||||
break;
|
||||
}
|
||||
|
||||
case (value_t::number_float):
|
||||
{
|
||||
// 15 digits of precision allows round-trip IEEE 754
|
||||
// string->double->string
|
||||
const auto sz = static_cast<unsigned int>(std::snprintf(nullptr, 0, "%.15g", m_value.number_float));
|
||||
std::vector<typename string_t::value_type> buf(sz + 1);
|
||||
std::snprintf(&buf[0], buf.size(), "%.15g", m_value.number_float);
|
||||
out << buf.data();
|
||||
break;
|
||||
}
|
||||
|
||||
case (value_t::discarded):
|
||||
{
|
||||
out << "<discarded>";
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
out << "null";
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// return the type of the object (explicit)
|
||||
inline value_t type() const noexcept
|
||||
{
|
||||
|
||||
Loading…
Reference in New Issue
Block a user