mirror of
https://github.com/nlohmann/json.git
synced 2026-09-07 00:37:58 +00:00
Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e59b455616 | ||
|
|
e29ac4f043 |
@@ -4,8 +4,8 @@
|
||||
Hello, world!
|
||||
1 2 3 4 5
|
||||
|
||||
string: "Hello, world!"
|
||||
number: {"floating-point":17.23,"integer":42}
|
||||
null: null
|
||||
string: "Hello, world!"
|
||||
boolean: true
|
||||
array: [1,2,3,4,5]
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
Hello, world!
|
||||
1 2 3 4 5
|
||||
|
||||
string: "Hello, world!"
|
||||
number: {"floating-point":17.23,"integer":42}
|
||||
null: null
|
||||
string: "Hello, world!"
|
||||
boolean: true
|
||||
array: [1,2,3,4,5]
|
||||
|
||||
@@ -4,9 +4,9 @@
|
||||
Hello, world!
|
||||
1 2 3 4 5
|
||||
|
||||
string: "Hello, world!"
|
||||
number: {"floating-point":17.23,"integer":42}
|
||||
null: null
|
||||
string: "Hello, world!"
|
||||
boolean: true
|
||||
array: [1,2,3,4,5]
|
||||
[json.exception.type_error.302] type must be boolean, but is string
|
||||
|
||||
@@ -398,6 +398,34 @@ inline void from_json(const BasicJsonType& j, CompatibleArrayType& bin)
|
||||
}
|
||||
}
|
||||
|
||||
template<typename BasicJsonType, typename ConstructibleObjectType>
|
||||
auto from_json_object_impl(const BasicJsonType& j, ConstructibleObjectType& obj, priority_tag<1> /*unused*/)
|
||||
-> decltype(
|
||||
obj.reserve(std::declval<typename ConstructibleObjectType::size_type>()),
|
||||
void())
|
||||
{
|
||||
ConstructibleObjectType ret;
|
||||
const auto* inner_object = j.template get_ptr<const typename BasicJsonType::object_t*>();
|
||||
ret.reserve(inner_object->size());
|
||||
for (const auto& p : *inner_object)
|
||||
{
|
||||
ret.emplace(p.first, p.second.template get<typename ConstructibleObjectType::mapped_type>());
|
||||
}
|
||||
obj = std::move(ret);
|
||||
}
|
||||
|
||||
template<typename BasicJsonType, typename ConstructibleObjectType>
|
||||
inline void from_json_object_impl(const BasicJsonType& j, ConstructibleObjectType& obj, priority_tag<0> /*unused*/)
|
||||
{
|
||||
ConstructibleObjectType ret;
|
||||
const auto* inner_object = j.template get_ptr<const typename BasicJsonType::object_t*>();
|
||||
for (const auto& p : *inner_object)
|
||||
{
|
||||
ret.emplace(p.first, p.second.template get<typename ConstructibleObjectType::mapped_type>());
|
||||
}
|
||||
obj = std::move(ret);
|
||||
}
|
||||
|
||||
template<typename BasicJsonType, typename ConstructibleObjectType,
|
||||
enable_if_t<is_constructible_object_type<BasicJsonType, ConstructibleObjectType>::value, int> = 0>
|
||||
inline void from_json(const BasicJsonType& j, ConstructibleObjectType& obj)
|
||||
@@ -407,13 +435,7 @@ inline void from_json(const BasicJsonType& j, ConstructibleObjectType& obj)
|
||||
JSON_THROW(type_error::create(302, concat("type must be object, but is ", j.type_name()), &j));
|
||||
}
|
||||
|
||||
ConstructibleObjectType ret;
|
||||
const auto* inner_object = j.template get_ptr<const typename BasicJsonType::object_t*>();
|
||||
for (const auto& p : *inner_object)
|
||||
{
|
||||
ret.emplace(p.first, p.second.template get<typename ConstructibleObjectType::mapped_type>());
|
||||
}
|
||||
obj = std::move(ret);
|
||||
from_json_object_impl(j, obj, priority_tag<1> {});
|
||||
}
|
||||
|
||||
// overload for arithmetic types, not chosen for basic_json template arguments
|
||||
|
||||
@@ -71,7 +71,7 @@ class serializer
|
||||
, 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)
|
||||
, indent_string()
|
||||
, indent_string(512, indent_char)
|
||||
, error_handler(error_handler_)
|
||||
{}
|
||||
|
||||
@@ -126,10 +126,6 @@ class serializer
|
||||
|
||||
// variable to hold indentation for recursive calls
|
||||
const auto new_indent = current_indent + indent_step;
|
||||
if (JSON_HEDLEY_UNLIKELY(indent_string.empty()))
|
||||
{
|
||||
indent_string.resize(512, indent_char);
|
||||
}
|
||||
if (JSON_HEDLEY_UNLIKELY(indent_string.size() < new_indent))
|
||||
{
|
||||
indent_string.resize(indent_string.size() * 2, ' ');
|
||||
@@ -203,10 +199,6 @@ class serializer
|
||||
|
||||
// variable to hold indentation for recursive calls
|
||||
const auto new_indent = current_indent + indent_step;
|
||||
if (JSON_HEDLEY_UNLIKELY(indent_string.empty()))
|
||||
{
|
||||
indent_string.resize(512, indent_char);
|
||||
}
|
||||
if (JSON_HEDLEY_UNLIKELY(indent_string.size() < new_indent))
|
||||
{
|
||||
indent_string.resize(indent_string.size() * 2, ' ');
|
||||
@@ -268,10 +260,6 @@ class serializer
|
||||
|
||||
// variable to hold indentation for recursive calls
|
||||
const auto new_indent = current_indent + indent_step;
|
||||
if (JSON_HEDLEY_UNLIKELY(indent_string.empty()))
|
||||
{
|
||||
indent_string.resize(512, indent_char);
|
||||
}
|
||||
if (JSON_HEDLEY_UNLIKELY(indent_string.size() < new_indent))
|
||||
{
|
||||
indent_string.resize(indent_string.size() * 2, ' ');
|
||||
@@ -1022,7 +1010,7 @@ class serializer
|
||||
|
||||
/// the indentation character
|
||||
const char indent_char;
|
||||
/// the indentation string (lazily allocated on first use by a pretty-print branch)
|
||||
/// the indentation string
|
||||
string_t indent_string;
|
||||
|
||||
/// error_handler how to react on decoding errors
|
||||
|
||||
@@ -5693,6 +5693,34 @@ inline void from_json(const BasicJsonType& j, CompatibleArrayType& bin)
|
||||
}
|
||||
}
|
||||
|
||||
template<typename BasicJsonType, typename ConstructibleObjectType>
|
||||
auto from_json_object_impl(const BasicJsonType& j, ConstructibleObjectType& obj, priority_tag<1> /*unused*/)
|
||||
-> decltype(
|
||||
obj.reserve(std::declval<typename ConstructibleObjectType::size_type>()),
|
||||
void())
|
||||
{
|
||||
ConstructibleObjectType ret;
|
||||
const auto* inner_object = j.template get_ptr<const typename BasicJsonType::object_t*>();
|
||||
ret.reserve(inner_object->size());
|
||||
for (const auto& p : *inner_object)
|
||||
{
|
||||
ret.emplace(p.first, p.second.template get<typename ConstructibleObjectType::mapped_type>());
|
||||
}
|
||||
obj = std::move(ret);
|
||||
}
|
||||
|
||||
template<typename BasicJsonType, typename ConstructibleObjectType>
|
||||
inline void from_json_object_impl(const BasicJsonType& j, ConstructibleObjectType& obj, priority_tag<0> /*unused*/)
|
||||
{
|
||||
ConstructibleObjectType ret;
|
||||
const auto* inner_object = j.template get_ptr<const typename BasicJsonType::object_t*>();
|
||||
for (const auto& p : *inner_object)
|
||||
{
|
||||
ret.emplace(p.first, p.second.template get<typename ConstructibleObjectType::mapped_type>());
|
||||
}
|
||||
obj = std::move(ret);
|
||||
}
|
||||
|
||||
template<typename BasicJsonType, typename ConstructibleObjectType,
|
||||
enable_if_t<is_constructible_object_type<BasicJsonType, ConstructibleObjectType>::value, int> = 0>
|
||||
inline void from_json(const BasicJsonType& j, ConstructibleObjectType& obj)
|
||||
@@ -5702,13 +5730,7 @@ inline void from_json(const BasicJsonType& j, ConstructibleObjectType& obj)
|
||||
JSON_THROW(type_error::create(302, concat("type must be object, but is ", j.type_name()), &j));
|
||||
}
|
||||
|
||||
ConstructibleObjectType ret;
|
||||
const auto* inner_object = j.template get_ptr<const typename BasicJsonType::object_t*>();
|
||||
for (const auto& p : *inner_object)
|
||||
{
|
||||
ret.emplace(p.first, p.second.template get<typename ConstructibleObjectType::mapped_type>());
|
||||
}
|
||||
obj = std::move(ret);
|
||||
from_json_object_impl(j, obj, priority_tag<1> {});
|
||||
}
|
||||
|
||||
// overload for arithmetic types, not chosen for basic_json template arguments
|
||||
@@ -20150,7 +20172,7 @@ class serializer
|
||||
, 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)
|
||||
, indent_string()
|
||||
, indent_string(512, indent_char)
|
||||
, error_handler(error_handler_)
|
||||
{}
|
||||
|
||||
@@ -20205,10 +20227,6 @@ class serializer
|
||||
|
||||
// variable to hold indentation for recursive calls
|
||||
const auto new_indent = current_indent + indent_step;
|
||||
if (JSON_HEDLEY_UNLIKELY(indent_string.empty()))
|
||||
{
|
||||
indent_string.resize(512, indent_char);
|
||||
}
|
||||
if (JSON_HEDLEY_UNLIKELY(indent_string.size() < new_indent))
|
||||
{
|
||||
indent_string.resize(indent_string.size() * 2, ' ');
|
||||
@@ -20282,10 +20300,6 @@ class serializer
|
||||
|
||||
// variable to hold indentation for recursive calls
|
||||
const auto new_indent = current_indent + indent_step;
|
||||
if (JSON_HEDLEY_UNLIKELY(indent_string.empty()))
|
||||
{
|
||||
indent_string.resize(512, indent_char);
|
||||
}
|
||||
if (JSON_HEDLEY_UNLIKELY(indent_string.size() < new_indent))
|
||||
{
|
||||
indent_string.resize(indent_string.size() * 2, ' ');
|
||||
@@ -20347,10 +20361,6 @@ class serializer
|
||||
|
||||
// variable to hold indentation for recursive calls
|
||||
const auto new_indent = current_indent + indent_step;
|
||||
if (JSON_HEDLEY_UNLIKELY(indent_string.empty()))
|
||||
{
|
||||
indent_string.resize(512, indent_char);
|
||||
}
|
||||
if (JSON_HEDLEY_UNLIKELY(indent_string.size() < new_indent))
|
||||
{
|
||||
indent_string.resize(indent_string.size() * 2, ' ');
|
||||
@@ -21101,7 +21111,7 @@ class serializer
|
||||
|
||||
/// the indentation character
|
||||
const char indent_char;
|
||||
/// the indentation string (lazily allocated on first use by a pretty-print branch)
|
||||
/// the indentation string
|
||||
string_t indent_string;
|
||||
|
||||
/// error_handler how to react on decoding errors
|
||||
|
||||
@@ -1389,6 +1389,37 @@ TEST_CASE("value conversion")
|
||||
// CHECK(m5["one"] == "eins");
|
||||
}
|
||||
|
||||
SECTION("reserve is called on containers that support it (#5406)")
|
||||
{
|
||||
// build a larger object so that a missing/incorrect reserve()
|
||||
// call would be more likely to corrupt or drop elements
|
||||
json j_large;
|
||||
for (int i = 0; i < 100; ++i)
|
||||
{
|
||||
j_large[std::to_string(i)] = i;
|
||||
}
|
||||
|
||||
SECTION("std::unordered_map (supports reserve)")
|
||||
{
|
||||
const auto m = j_large.get<std::unordered_map<std::string, int>>();
|
||||
CHECK(m.size() == 100);
|
||||
for (int i = 0; i < 100; ++i)
|
||||
{
|
||||
CHECK(m.at(std::to_string(i)) == i);
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("std::map (no reserve, fallback path)")
|
||||
{
|
||||
const auto m = j_large.get<std::map<std::string, int>>();
|
||||
CHECK(m.size() == 100);
|
||||
for (int i = 0; i < 100; ++i)
|
||||
{
|
||||
CHECK(m.at(std::to_string(i)) == i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("std::multimap")
|
||||
{
|
||||
j1.get<std::multimap<std::string, int>>();
|
||||
|
||||
@@ -14,40 +14,6 @@ using nlohmann::json;
|
||||
#include <array>
|
||||
#include <sstream>
|
||||
#include <iomanip>
|
||||
#include <cstdlib>
|
||||
#include <new>
|
||||
|
||||
namespace
|
||||
{
|
||||
// heap allocation counter used by the regression test for issue #5413
|
||||
// (https://github.com/nlohmann/json/issues/5413); disabled (and thus a
|
||||
// no-op besides the counting) unless explicitly toggled on
|
||||
bool count_heap_allocations = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
std::size_t heap_allocations = 0; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
} // namespace
|
||||
|
||||
void* operator new (std::size_t size) // NOLINT(cppcoreguidelines-owning-memory,misc-new-delete-overloads)
|
||||
{
|
||||
if (count_heap_allocations)
|
||||
{
|
||||
++heap_allocations;
|
||||
}
|
||||
if (void* ptr = std::malloc(size)) // NOLINT(cppcoreguidelines-no-malloc,cppcoreguidelines-owning-memory)
|
||||
{
|
||||
return ptr;
|
||||
}
|
||||
throw std::bad_alloc(); // NOLINT(hicpp-exception-baseclass)
|
||||
}
|
||||
|
||||
void operator delete (void* ptr) noexcept // NOLINT(cppcoreguidelines-owning-memory,misc-new-delete-overloads)
|
||||
{
|
||||
std::free(ptr); // NOLINT(cppcoreguidelines-no-malloc,cppcoreguidelines-owning-memory)
|
||||
}
|
||||
|
||||
void operator delete (void* ptr, std::size_t /*size*/) noexcept // NOLINT(cppcoreguidelines-owning-memory,misc-new-delete-overloads)
|
||||
{
|
||||
std::free(ptr); // NOLINT(cppcoreguidelines-no-malloc,cppcoreguidelines-owning-memory)
|
||||
}
|
||||
|
||||
TEST_CASE("serialization")
|
||||
{
|
||||
@@ -416,76 +382,3 @@ TEST_CASE("dump for basic_json with long double number_float_t")
|
||||
check_same(100.0L, 100.0);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("regression test for issue #5413 - lazily allocated indent_string")
|
||||
{
|
||||
// the serializer used to unconditionally allocate a 512-byte
|
||||
// indent_string in its constructor, even though it is only ever read
|
||||
// inside the pretty_print branches of dump(). This wasted a heap
|
||||
// allocation (and its matching deallocation) on every single compact
|
||||
// (i.e. non-pretty, the default) dump() call. indent_string is now
|
||||
// allocated lazily, the first time a pretty-print branch actually
|
||||
// needs it -- so a compact dump() must perform strictly fewer heap
|
||||
// allocations than a pretty dump() of the same value.
|
||||
const json j = {{"level", "info"}, {"msg", "hello world"}, {"id", 12345}};
|
||||
|
||||
// warm up anything unrelated to indentation (e.g., one-time locale
|
||||
// lookups) that might otherwise allocate on first use regardless of
|
||||
// pretty-printing, so it does not skew the counts measured below
|
||||
const auto warmup = j.dump();
|
||||
const auto warmup_pretty = j.dump(4);
|
||||
CHECK(!warmup.empty());
|
||||
CHECK(!warmup_pretty.empty());
|
||||
|
||||
SECTION("compact dump() has a stable, minimal allocation count")
|
||||
{
|
||||
count_heap_allocations = true;
|
||||
|
||||
heap_allocations = 0;
|
||||
const auto compact1 = j.dump();
|
||||
const auto allocs_compact1 = heap_allocations;
|
||||
|
||||
heap_allocations = 0;
|
||||
const auto compact2 = j.dump(-1);
|
||||
const auto allocs_compact2 = heap_allocations;
|
||||
|
||||
count_heap_allocations = false;
|
||||
|
||||
CHECK(compact1 == compact2);
|
||||
// dump() and dump(-1) both take the compact code path and must
|
||||
// never touch indent_string, so they allocate identically often
|
||||
CHECK(allocs_compact1 == allocs_compact2);
|
||||
}
|
||||
|
||||
SECTION("first pretty dump() allocates more than a compact dump()")
|
||||
{
|
||||
// use a tiny value whose compact ({"a":1}, 7 bytes) and pretty
|
||||
// ({"a": 1} with 1-space indent, 11 bytes) serializations both stay
|
||||
// well inside every common std::string small-string-optimization
|
||||
// buffer (>= 15 bytes on libstdc++/MSVC STL, >= 22 on libc++), so
|
||||
// building the result string itself causes no heap allocation
|
||||
// either way -- isolating indent_string as the only thing that can
|
||||
// possibly account for a difference in allocation count
|
||||
const json tiny = {{"a", 1}};
|
||||
|
||||
count_heap_allocations = true;
|
||||
|
||||
heap_allocations = 0;
|
||||
const auto compact = tiny.dump();
|
||||
const auto allocs_compact = heap_allocations;
|
||||
|
||||
heap_allocations = 0;
|
||||
const auto pretty = tiny.dump(1);
|
||||
const auto allocs_pretty = heap_allocations;
|
||||
|
||||
count_heap_allocations = false;
|
||||
|
||||
CHECK(compact == "{\"a\":1}");
|
||||
CHECK(pretty == "{\n \"a\": 1\n}");
|
||||
// a fresh serializer is created per dump() call; the pretty branch
|
||||
// lazily allocates indent_string on its first use, so it must
|
||||
// allocate at least once more than the compact branch, which never
|
||||
// touches indent_string at all
|
||||
CHECK(allocs_pretty > allocs_compact);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user