Files
json/tests/src/unit-regression1.cpp
T
Niels Lohmann b38131ec40 Add JSON_RECURSION_DEPTH_LIMIT to set how deep value walks recurse
Copying, comparing, serializing, hashing, merging, diffing, converting and
destroying a value, and the CBOR, MessagePack, UBJSON, BJData and BON8 writers
recurse a fixed number of nesting levels on the call stack and finish deeper
levels with an explicit stack (#5387). The number was hard-coded to 128. That is
too much for small stacks: a 128-level dump() needs about 288 KiB at -O0 with
Clang (2.3 KiB per level; 256 KiB with GCC) and crashed in a thread with a
256 KiB stack, and musl threads have 128 KiB by default. Others may want the
opposite.

The macro JSON_RECURSION_DEPTH_LIMIT, defined before including the library,
sets the number; the default stays 128. The value must be an integer constant
from 0 to 254 (not a floating-point number or bool), otherwise a static_assert
names the macro. 254 is the largest value because the copy constructor and the
comparison operators cannot pass a depth down and count it in a byte per thread
(nesting_depth()). The guard that counts now counts only the levels it enters,
so the count never exceeds the limit. It used to count the level that finds the
limit reached as well, which made the count 255 at limit 254: a copy made by a
custom base class while a copy was going on then wrapped the byte to 0 and
recursed another 254 levels (twice the stack).

0 is allowed: every walk then uses its explicit stack from the first level on
and needs a constant amount of stack (16 KiB or less at
-O0 with Clang and GCC, for a value nested 20,000 levels deep). All uses of the
limit compare a depth with it; none computes limit - 1 or limit + 1, and no
walk has a constant 128 of its own.

The macro changes only stack use and speed, never a result. Like
JSON_NO_THREAD_LOCAL it is not part of the ABI tag and must be the same in all
translation units (ODR); the page of the macro says what happens otherwise.

Supporting a limit of 0 needed one fix: update() without merge_objects reached
the explicit-stack merge at depth 0, which always merges objects. update_members
now takes that path only with merge_objects, which is the only case in which it
recurses at all.

Measurements (macOS arm64, Apple clang 21 and g++ 16, documented in the macro
page with a table per operation and optimization level, and a program to
reproduce them): the stack needed at the default of 128 levels is up to 288 KiB
(dump at -O0, Clang), 224 KiB (copy), and 48 KiB or less at -O2; at 254 levels
up to 576 KiB at -O0 and 112 KiB at -O2. Speed at -O2 relative to the default:
ordinary documents (nested 2 to 10 levels) are 1.1 to 2.4 times as slow with a
limit of 0 or 1 and unchanged (within about 0.1) with 16 or 254; values nested
1,000 or 100,000 levels deep show no difference for any limit. The default
build does the same work as before: the instructions retired for copy and conversion
(the walks whose guard changed; net of loading, canada, citm, twitter, jeopardy
and deep values), ==, dump and hash differ from the base by at most 0.02 % for copy
and 0.71 % in either direction for the others (noise), and the cycles for copy
by at most 0.65 %.

Tests: the existing tests that depended on 128 (comparison, allocator, custom
base class, diagnostics, binary writers, patch, merge_diff, conversions, custom
array type) derive their depths from the limit, and two assumed a limit of at
least 1 (limit - 1 wrapped around). tests/src/unit-recursion_depth_limit.cpp
checks that the macro is honoured (the array type of a value counts the levels
that a copy and a comparison descend) and that all walks return the same results
for values nested 1 to 300 levels deep as arrays, objects and both, against
expected strings and bytes built independently of the library; CMake builds it
with limits 0, 1 and 254 besides the default, and the CI target
ci_test_recursion_depth_limit_0 runs the whole suite with a limit of 0. A test
with a base class that copies the value from its copy constructor covers the
wrap-around at 254. The new test does not compile against the
previous header with -DJSON_RECURSION_DEPTH_LIMIT=1.

Docs: new macro page (range, stack and speed tables, small-stack guidance, ODR),
index, nav, macro overview, "Nesting depth" of the untrusted-input page (which
claimed that no walk could exhaust the stack however deep a value is nested; the
default assumes a stack of a few hundred KiB), the assurance case, and the page
of JSON_NO_THREAD_LOCAL.

No breaking changes: the default is unchanged, and the macro is new.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-11-11 18:32:55 +01:00

1637 lines
60 KiB
C++

// __ _____ _____ _____
// __| | __| | | | JSON for Modern C++ (supporting code)
// | | |__ | | | | | | version 3.12.0
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
//
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
// SPDX-License-Identifier: MIT
#include "doctest_compatibility.h"
// for some reason including this after the json header leads to linker errors with VS 2017...
#include <locale>
// skip tests if JSON_DisableEnumSerialization=ON (#4384)
#if defined(JSON_DISABLE_ENUM_SERIALIZATION) && (JSON_DISABLE_ENUM_SERIALIZATION == 1)
#define SKIP_TESTS_FOR_ENUM_SERIALIZATION
#endif
#define JSON_TESTS_PRIVATE
#include <nlohmann/json.hpp>
using nlohmann::json;
#ifdef JSON_TEST_NO_GLOBAL_UDLS
using namespace nlohmann::literals; // NOLINT(google-build-using-namespace)
#endif
#include <array>
#include <fstream>
#include <sstream>
#include <list>
#include <limits>
#include <cstdio>
#include <utility>
#include "make_test_data_available.hpp"
#include "test_utils.hpp"
#include "fifo_map.hpp"
/////////////////////////////////////////////////////////////////////
// for #972
/////////////////////////////////////////////////////////////////////
template<class K, class V, class dummy_compare, class A>
using my_workaround_fifo_map = nlohmann::fifo_map<K, V, nlohmann::fifo_map_compare<K>, A>;
using my_json = nlohmann::json::with_object_t<my_workaround_fifo_map>;
/////////////////////////////////////////////////////////////////////
// for #977
/////////////////////////////////////////////////////////////////////
namespace ns
{
struct foo
{
int x;
};
template <typename, typename SFINAE = void>
struct foo_serializer;
template<typename T>
struct foo_serializer<T, typename std::enable_if<std::is_same<foo, T>::value>::type>
{
template <typename BasicJsonType>
static void to_json(BasicJsonType& j, const T& value)
{
j = BasicJsonType{{"x", value.x}};
}
template <typename BasicJsonType>
static void from_json(const BasicJsonType& j, T& value) // !!!
{
nlohmann::from_json(j.at("x"), value.x);
}
};
template<typename T>
struct foo_serializer < T, typename std::enable_if < !std::is_same<foo, T>::value >::type >
{
template <typename BasicJsonType>
static void to_json(BasicJsonType& j, const T& value) noexcept // NOLINT(bugprone-exception-escape)
{
::nlohmann::to_json(j, value);
}
template <typename BasicJsonType>
static void from_json(const BasicJsonType& j, T& value) //!!!
{
::nlohmann::from_json(j, value);
}
};
} // namespace ns
using foo_json = nlohmann::json::with_json_serializer_t<ns::foo_serializer>;
/////////////////////////////////////////////////////////////////////
// for #805
/////////////////////////////////////////////////////////////////////
namespace
{
struct nocopy // NOLINT(cppcoreguidelines-special-member-functions,hicpp-special-member-functions)
{
nocopy() = default;
nocopy(const nocopy&) = delete;
nocopy(nocopy&&) = delete;
nocopy& operator=(const nocopy&) = delete;
nocopy& operator=(nocopy&&) = delete;
int val = 0;
friend void to_json(json& j, const nocopy& n)
{
j = {{"val", n.val}};
}
};
} // namespace
TEST_CASE("regression tests 1")
{
SECTION("issue #60 - Double quotation mark is not parsed correctly")
{
SECTION("escape_doublequote")
{
const auto* s = R"(["\"foo\""])";
const json j = json::parse(s);
auto expected = R"(["\"foo\""])"_json;
CHECK(j == expected);
}
}
SECTION("issue #70 - Handle infinity and NaN cases")
{
// previously, NAN/INFINITY created a null value; now, the values are
// properly stored, but are dumped as "null"
SECTION("NAN value")
{
CHECK(json(NAN).dump() == "null");
CHECK(json(json::number_float_t(NAN)).dump() == "null");
}
SECTION("infinity")
{
CHECK(json(INFINITY).dump() == "null");
CHECK(json(json::number_float_t(INFINITY)).dump() == "null");
}
// With 3.0.0, the semantics of this changed: NAN and infinity are
// stored properly inside the JSON value (no exception or conversion
// to null), but are serialized as null.
SECTION("NAN value")
{
json const j1 = NAN;
CHECK(j1.is_number_float());
json::number_float_t const f1{j1};
CHECK(std::isnan(f1));
json const j2 = static_cast<json::number_float_t>(NAN);
CHECK(j2.is_number_float());
json::number_float_t const f2{j2};
CHECK(std::isnan(f2));
}
SECTION("infinity")
{
json const j1 = INFINITY;
CHECK(j1.is_number_float());
json::number_float_t const f1{j1};
CHECK(!std::isfinite(f1));
json const j2 = static_cast<json::number_float_t>(INFINITY);
CHECK(j2.is_number_float());
json::number_float_t const f2{j2};
CHECK(!std::isfinite(f2));
}
}
#ifndef SKIP_TESTS_FOR_ENUM_SERIALIZATION
SECTION("pull request #71 - handle enum type")
{
enum { t = 0, u = 102}; // NOLINT(cppcoreguidelines-use-enum-class)
json j = json::array();
j.push_back(t);
// maybe this is not the place to test this?
const json j2 = u;
auto anon_enum_value = j2.get<decltype(u)>();
CHECK(u == anon_enum_value);
// check if the actual value was stored
CHECK(j2 == 102);
static_assert(std::is_same<decltype(anon_enum_value), decltype(u)>::value, "types must be the same");
j.push_back(json::object(
{
{"game_type", t}
}));
}
#endif
SECTION("issue #76 - dump() / parse() not idempotent")
{
// create JSON object
json fields;
fields["one"] = std::string("one");
fields["two"] = std::string("two three");
fields["three"] = std::string("three \"four\"");
// create another JSON object by deserializing the serialization
std::string const payload = fields.dump();
json parsed_fields = json::parse(payload);
// check individual fields to match both objects
CHECK(parsed_fields["one"] == fields["one"]);
CHECK(parsed_fields["two"] == fields["two"]);
CHECK(parsed_fields["three"] == fields["three"]);
// check individual fields to match original input
CHECK(parsed_fields["one"] == std::string("one"));
CHECK(parsed_fields["two"] == std::string("two three"));
CHECK(parsed_fields["three"] == std::string("three \"four\""));
// check equality of the objects
CHECK(parsed_fields == fields);
// check equality of the serialized objects
CHECK(fields.dump() == parsed_fields.dump());
// check everything in one line
CHECK(fields == json::parse(fields.dump()));
}
SECTION("issue #82 - lexer::get_number return NAN")
{
const auto* const content = R"(
{
"Test":"Test1",
"Number":100,
"Foo":42.42
})";
std::stringstream ss;
ss << content;
json j;
ss >> j;
auto test = j["Test"].get<std::string>();
CHECK(test == "Test1");
const int number{j["Number"]};
CHECK(number == 100);
const float foo{j["Foo"]};
CHECK(static_cast<double>(foo) == Approx(42.42));
}
SECTION("issue #89 - nonstandard integer type")
{
// create JSON class with nonstandard integer number type
using custom_json =
nlohmann::json::with_integers_t<std::int32_t, std::uint32_t>::with_float_t<float>;
custom_json j;
j["int_1"] = 1;
CHECK(j["int_1"] == 1);
// tests for correct handling of non-standard integers that overflow the type selected by the user
// unsigned integer object creation - expected to wrap and still be stored as an integer
j = 4294967296U; // 2^32
CHECK(static_cast<int>(j.type()) == static_cast<int>(custom_json::value_t::number_unsigned));
CHECK(j.get<uint32_t>() == 0); // Wrap
// unsigned integer parsing - expected to overflow and be stored as a float
j = custom_json::parse("4294967296"); // 2^32
CHECK(static_cast<int>(j.type()) == static_cast<int>(custom_json::value_t::number_float));
CHECK(j.get<float>() == 4294967296.0f);
// integer object creation - expected to wrap and still be stored as an integer
j = -2147483649LL; // -2^31-1
CHECK(static_cast<int>(j.type()) == static_cast<int>(custom_json::value_t::number_integer));
CHECK(j.get<int32_t>() == 2147483647); // Wrap
// integer parsing - expected to overflow and be stored as a float with rounding
j = custom_json::parse("-2147483649"); // -2^31
CHECK(static_cast<int>(j.type()) == static_cast<int>(custom_json::value_t::number_float));
CHECK(j.get<float>() == -2147483650.0f);
}
SECTION("issue #93 reverse_iterator operator inheritance problem")
{
{
json a = {1, 2, 3};
json::reverse_iterator rit = a.rbegin();
++rit;
CHECK(*rit == json(2));
CHECK(rit.value() == json(2));
}
{
json a = {1, 2, 3};
json::reverse_iterator const rit = ++a.rbegin();
CHECK(*rit == json(2));
CHECK(rit.value() == json(2));
}
{
json a = {1, 2, 3};
json::reverse_iterator rit = a.rbegin();
++rit;
json b = {0, 0, 0};
std::transform(rit, a.rend(), b.rbegin(), [](json el)
{
return el;
});
CHECK(b == json({0, 1, 2}));
}
{
json a = {1, 2, 3};
json b = {0, 0, 0};
std::transform(++a.rbegin(), a.rend(), b.rbegin(), [](json el)
{
return el;
});
CHECK(b == json({0, 1, 2}));
}
}
SECTION("issue #100 - failed to iterator json object with reverse_iterator")
{
json config =
{
{ "111", 111 },
{ "112", 112 },
{ "113", 113 }
};
std::stringstream ss;
for (auto it = config.begin(); it != config.end(); ++it)
{
ss << it.key() << ": " << it.value() << '\n';
}
for (auto it = config.rbegin(); it != config.rend(); ++it)
{
ss << it.key() << ": " << it.value() << '\n';
}
CHECK(ss.str() == "111: 111\n112: 112\n113: 113\n113: 113\n112: 112\n111: 111\n");
}
SECTION("issue #101 - binary string causes numbers to be dumped as hex")
{
int64_t const number = 10;
std::string const bytes{"\x00" "asdf\n", 6};
json j;
j["int64"] = number;
j["binary string"] = bytes;
// make sure the number is really printed as decimal "10" and not as
// hexadecimal "a"
CHECK(j.dump() == "{\"binary string\":\"\\u0000asdf\\n\",\"int64\":10}");
}
SECTION("issue #111 - subsequent unicode chars")
{
std::string const bytes{0x7, 0x7};
json j;
j["string"] = bytes;
CHECK(j["string"] == "\u0007\u0007");
}
#if JSON_USE_IMPLICIT_CONVERSIONS
SECTION("issue #144 - implicit assignment to std::string fails")
{
json o = {{"name", "value"}};
const std::string s1 = o["name"];
CHECK(s1 == "value");
std::string s2;
s2 = o["name"];
CHECK(s2 == "value");
// improve coverage
o["int"] = 1;
#if JSON_DIAGNOSTICS
CHECK_THROWS_WITH_AS(s2 = o["int"], "[json.exception.type_error.302] (/int) type must be string, but is number", json::type_error);
#else
CHECK_THROWS_WITH_AS(s2 = o["int"], "[json.exception.type_error.302] type must be string, but is number", json::type_error);
#endif
}
#endif
SECTION("issue #146 - character following a surrogate pair is skipped")
{
CHECK(json::parse("\"\\ud80c\\udc60abc\"").get<json::string_t>() == "\xf0\x93\x81\xa0\x61\x62\x63");
}
SECTION("issue #171 - Cannot index by key of type static constexpr const char*")
{
json j;
// Non-const access with key as "char []"
char array_key[] = "Key1"; // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)
CHECK_NOTHROW(j[array_key] = 1);
CHECK(j[array_key] == json(1));
// Non-const access with key as "const char[]"
const char const_array_key[] = "Key2"; // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)
CHECK_NOTHROW(j[const_array_key] = 2);
CHECK(j[const_array_key] == json(2));
// Non-const access with key as "char *"
char _ptr_key[] = "Key3"; // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)
char* ptr_key = &_ptr_key[0]; // NOLINT(cppcoreguidelines-pro-type-vararg,hicpp-vararg)
CHECK_NOTHROW(j[ptr_key] = 3);
CHECK(j[ptr_key] == json(3));
// Non-const access with key as "const char *"
const char* const_ptr_key = "Key4";
CHECK_NOTHROW(j[const_ptr_key] = 4);
CHECK(j[const_ptr_key] == json(4));
// Non-const access with key as "static constexpr const char *"
static constexpr const char* constexpr_ptr_key = "Key5";
CHECK_NOTHROW(j[constexpr_ptr_key] = 5);
CHECK(j[constexpr_ptr_key] == json(5));
const json j_const = j;
// Const access with key as "char []"
CHECK(j_const[array_key] == json(1));
// Const access with key as "const char[]"
CHECK(j_const[const_array_key] == json(2));
// Const access with key as "char *"
CHECK(j_const[ptr_key] == json(3));
// Const access with key as "const char *"
CHECK(j_const[const_ptr_key] == json(4));
// Const access with key as "static constexpr const char *"
CHECK(j_const[constexpr_ptr_key] == json(5));
}
SECTION("issue #186 miloyip/nativejson-benchmark: floating-point parsing")
{
json j;
j = json::parse("-0.0");
CHECK(j.get<double>() == -0.0);
j = json::parse("2.22507385850720113605740979670913197593481954635164564e-308");
CHECK(j.get<double>() == 2.2250738585072009e-308);
j = json::parse("0.999999999999999944488848768742172978818416595458984374");
CHECK(j.get<double>() == 0.99999999999999989);
j = json::parse("1.00000000000000011102230246251565404236316680908203126");
CHECK(j.get<double>() == 1.00000000000000022);
j = json::parse("7205759403792793199999e-5");
CHECK(j.get<double>() == 72057594037927928.0);
j = json::parse("922337203685477529599999e-5");
CHECK(j.get<double>() == 9223372036854774784.0);
j = json::parse("1014120480182583464902367222169599999e-5");
CHECK(j.get<double>() == 10141204801825834086073718800384.0);
j = json::parse("5708990770823839207320493820740630171355185151999e-3");
CHECK(j.get<double>() == 5708990770823838890407843763683279797179383808.0);
// create JSON class with nonstandard float number type
// float
nlohmann::json::with_integers_t<std::int32_t, std::uint32_t>::with_float_t<float> const j_float =
1.23e25f;
CHECK(j_float.get<float>() == 1.23e25f);
// double
nlohmann::json const j_double =
1.23e35;
CHECK(j_double.get<double>() == 1.23e35);
// long double
nlohmann::json::with_float_t<long double> const j_long_double = 1.23e45L;
CHECK(j_long_double.get<long double>() == 1.23e45L);
}
SECTION("issue #228 - double values are serialized with commas as decimal points")
{
json const j1a = 2312.42;
json const j1b = json::parse("2312.42");
json const j2a = 2342e-2;
//issue #230
//json j2b = json::parse("2342e-2");
json const j3a = 10E3;
json const j3b = json::parse("10E3");
json const j3c = json::parse("10e3");
// class to create a locale that would use a comma for decimals
class CommaDecimalSeparator : public std::numpunct<char>
{
protected:
char do_decimal_point() const override
{
return ',';
}
char do_thousands_sep() const override
{
return '.';
}
std::string do_grouping() const override
{
return "\03";
}
};
// change locale to mess with decimal points
auto orig_locale = std::locale::global(std::locale(std::locale(), new CommaDecimalSeparator));
CHECK(j1a.dump() == "2312.42");
CHECK(j1b.dump() == "2312.42");
// check if locale is properly reset
std::stringstream ss;
ss.imbue(std::locale(std::locale(), new CommaDecimalSeparator));
ss << 4712.11;
CHECK(ss.str() == "4.712,11");
ss << j1a;
CHECK(ss.str() == "4.712,112312.42");
ss << 47.11;
CHECK(ss.str() == "4.712,112312.4247,11");
CHECK(j2a.dump() == "23.42");
//issue #230
//CHECK(j2b.dump() == "23.42");
CHECK(j3a.dump() == "10000.0");
CHECK(j3b.dump() == "10000.0");
CHECK(j3c.dump() == "10000.0");
//CHECK(j3b.dump() == "1E04"); // roundtrip error
//CHECK(j3c.dump() == "1e04"); // roundtrip error
std::locale::global(orig_locale);
}
SECTION("issue #378 - locale-independent num-to-str")
{
static_cast<void>(setlocale(LC_NUMERIC, "de_DE.UTF-8"));
// verify that dumped correctly with '.' and no grouping
const json j1 = 12345.67;
CHECK(json(12345.67).dump() == "12345.67");
static_cast<void>(setlocale(LC_NUMERIC, "C"));
}
SECTION("issue #379 - locale-independent str-to-num")
{
static_cast<void>(setlocale(LC_NUMERIC, "de_DE.UTF-8"));
// verify that parsed correctly despite using strtod internally
CHECK(json::parse("3.14").get<double>() == 3.14);
// check a different code path
CHECK(json::parse("1.000000000000000000000000000000000000000000000000000000000000000000000000").get<double>() == 1.0);
}
SECTION("issue #233 - Can't use basic_json::iterator as a base iterator for std::move_iterator")
{
json source = {"a", "b", "c"};
json expected = {"a", "b"};
json dest;
std::copy_n(std::make_move_iterator(source.begin()), 2, std::back_inserter(dest));
CHECK(dest == expected);
}
SECTION("issue #235 - ambiguous overload for 'push_back' and 'operator+='")
{
json data = {{"key", "value"}};
data.push_back({"key2", "value2"});
data += {"key3", "value3"};
CHECK(data == json({{"key", "value"}, {"key2", "value2"}, {"key3", "value3"}}));
}
SECTION("issue #269 - diff generates incorrect patch when removing multiple array elements")
{
json const doc = R"( { "arr1": [1, 2, 3, 4] } )"_json;
json expected = R"( { "arr1": [1, 2] } )"_json;
// check roundtrip
CHECK(doc.patch(json::diff(doc, expected)) == expected);
}
SECTION("issue #283 - value() does not work with _json_pointer types")
{
json j =
{
{"object", {{"key1", 1}, {"key2", 2}}},
};
const int at_integer{j.at("/object/key2"_json_pointer)};
const int val_integer = j.value("/object/key2"_json_pointer, 0);
CHECK(at_integer == val_integer);
}
SECTION("issue #304 - Unused variable warning")
{
// code triggered a "warning: unused variable" warning and is left
// here to avoid the warning in the future
json object;
json const patch = json::array();
object = object.patch(patch);
}
SECTION("issue #306 - Parsing fails without space at end of file")
{
for (const auto* filename :
{
TEST_DATA_DIRECTORY "/regression/broken_file.json",
TEST_DATA_DIRECTORY "/regression/working_file.json"
})
{
CAPTURE(filename)
json j;
std::ifstream f(filename);
CHECK_NOTHROW(f >> j);
}
}
SECTION("issue #310 - make json_benchmarks no longer working in 2.0.4")
{
for (const auto* filename :
{
TEST_DATA_DIRECTORY "/regression/floats.json",
TEST_DATA_DIRECTORY "/regression/signed_ints.json",
TEST_DATA_DIRECTORY "/regression/unsigned_ints.json",
TEST_DATA_DIRECTORY "/regression/small_signed_ints.json"
})
{
CAPTURE(filename)
json j;
std::ifstream f(filename);
CHECK_NOTHROW(f >> j);
}
}
SECTION("issue #323 - add nested object capabilities to pointers")
{
json j;
j["/this/that/2"_json_pointer] = 27;
CHECK(j == json({{"this", {{"that", {nullptr, nullptr, 27}}}}}));
}
SECTION("issue #329 - serialized value not always can be parsed")
{
json _;
CHECK_THROWS_WITH_AS(_ = json::parse("22e2222"), "[json.exception.out_of_range.406] number overflow parsing '22e2222'", json::out_of_range&);
}
SECTION("issue #360 - Loss of precision when serializing <double>")
{
auto check_roundtrip = [](double number)
{
CAPTURE(number)
json j = number;
CHECK(j.is_number_float());
std::stringstream ss;
ss << j;
CHECK_NOTHROW(ss >> j);
CHECK(j.is_number_float());
CHECK(j.get<json::number_float_t>() == number);
};
check_roundtrip(100000000000.1236);
check_roundtrip((std::numeric_limits<json::number_float_t>::max)());
// Some more numbers which fail to roundtrip when serialized with digits10 significand digits (instead of max_digits10)
check_roundtrip(1.541888611948064e-17);
check_roundtrip(5.418771028591015e-16);
check_roundtrip(9.398685592608595e-15);
check_roundtrip(8.826843952762347e-14);
check_roundtrip(8.143291313475335e-13);
check_roundtrip(4.851328172762508e-12);
check_roundtrip(6.677850998084358e-11);
check_roundtrip(3.995398518174525e-10);
check_roundtrip(1.960452605645124e-9);
check_roundtrip(3.551812586302883e-8);
check_roundtrip(2.947988411689261e-7);
check_roundtrip(8.210166748056192e-6);
check_roundtrip(6.104889704266753e-5);
check_roundtrip(0.0008629954631330876);
check_roundtrip(0.004936993881051611);
check_roundtrip(0.08309725102608073);
check_roundtrip(0.5210494268499783);
check_roundtrip(6.382927930939767);
check_roundtrip(59.94947245358671);
check_roundtrip(361.0838651266122);
check_roundtrip(4678.354596181877);
check_roundtrip(61412.17658956043);
check_roundtrip(725696.0799057782);
check_roundtrip(2811732.583399828);
check_roundtrip(30178351.07533605);
check_roundtrip(689684880.3235844);
check_roundtrip(5714887673.555147);
check_roundtrip(84652038821.18808);
check_roundtrip(156510583431.7721);
check_roundtrip(5938450569021.732);
check_roundtrip(83623297654460.33);
check_roundtrip(701466573254773.6);
check_roundtrip(1369013370304513);
check_roundtrip(96963648023094720); // NOLINT(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions)
check_roundtrip(3.478237409280108e+17);
}
SECTION("issue #366 - json::parse on failed stream gets stuck")
{
std::ifstream f("file_not_found.json");
json _;
CHECK_THROWS_WITH_AS(_ = json::parse(f), "[json.exception.parse_error.101] parse error at line 1, column 1: attempting to parse an empty input; check that your input string or stream contains the expected JSON", json::parse_error&);
}
SECTION("issue #367 - calling stream at EOF")
{
std::stringstream ss;
json j;
ss << "123";
CHECK_NOTHROW(ss >> j);
// see https://github.com/nlohmann/json/issues/367#issuecomment-262841893:
// ss is not at EOF; this yielded an error before the fix
// (threw basic_string::append). No, it should just throw
// a parse error because of the EOF.
CHECK_THROWS_WITH_AS(ss >> j, "[json.exception.parse_error.101] parse error at line 1, column 1: attempting to parse an empty input; check that your input string or stream contains the expected JSON", json::parse_error&);
}
SECTION("issue #367 - behavior of operator>> should more closely resemble that of built-in overloads")
{
SECTION("(empty)")
{
std::stringstream ss;
json j;
CHECK_THROWS_WITH_AS(ss >> j, "[json.exception.parse_error.101] parse error at line 1, column 1: attempting to parse an empty input; check that your input string or stream contains the expected JSON", json::parse_error&);
}
SECTION("(whitespace)")
{
std::stringstream ss;
ss << " ";
json j;
CHECK_THROWS_WITH_AS(ss >> j,
"[json.exception.parse_error.101] parse error at line 1, column 4: syntax error while parsing value - unexpected end of input; expected '[', '{', or a literal", json::parse_error&);
}
SECTION("one value")
{
std::stringstream ss;
ss << "111";
json j;
CHECK_NOTHROW(ss >> j);
CHECK(j == 111);
CHECK_THROWS_WITH_AS(ss >> j, "[json.exception.parse_error.101] parse error at line 1, column 1: attempting to parse an empty input; check that your input string or stream contains the expected JSON", json::parse_error&);
}
SECTION("one value + whitespace")
{
std::stringstream ss;
ss << "222 \t\n";
json j;
CHECK_NOTHROW(ss >> j);
CHECK(j == 222);
CHECK_THROWS_WITH_AS(ss >> j,
"[json.exception.parse_error.101] parse error at line 2, column 1: syntax error while parsing value - unexpected end of input; expected '[', '{', or a literal", json::parse_error&);
}
SECTION("whitespace + one value")
{
std::stringstream ss;
ss << "\n\t 333";
json j;
CHECK_NOTHROW(ss >> j);
CHECK(j == 333);
CHECK_THROWS_WITH_AS(ss >> j, "[json.exception.parse_error.101] parse error at line 1, column 1: attempting to parse an empty input; check that your input string or stream contains the expected JSON", json::parse_error&);
}
SECTION("three values")
{
std::stringstream ss;
ss << " 111 \n222\n \n 333";
json j;
CHECK_NOTHROW(ss >> j);
CHECK(j == 111);
CHECK_NOTHROW(ss >> j);
CHECK(j == 222);
CHECK_NOTHROW(ss >> j);
CHECK(j == 333);
CHECK_THROWS_WITH_AS(ss >> j, "[json.exception.parse_error.101] parse error at line 1, column 1: attempting to parse an empty input; check that your input string or stream contains the expected JSON", json::parse_error&);
}
SECTION("literals without whitespace")
{
std::stringstream ss;
ss << "truefalsenull\"\"";
json j;
CHECK_NOTHROW(ss >> j);
CHECK(j == true);
CHECK_NOTHROW(ss >> j);
CHECK(j == false);
CHECK_NOTHROW(ss >> j);
CHECK(j == nullptr);
CHECK_NOTHROW(ss >> j);
CHECK(j == "");
CHECK_THROWS_WITH_AS(ss >> j, "[json.exception.parse_error.101] parse error at line 1, column 1: attempting to parse an empty input; check that your input string or stream contains the expected JSON", json::parse_error&);
}
SECTION("example from #529")
{
std::stringstream ss;
ss << "{\n \"one\" : 1,\n \"two\" : 2\n}\n{\n \"three\" : 3\n}";
json j;
CHECK_NOTHROW(ss >> j);
CHECK(j == json({{"one", 1}, {"two", 2}}));
CHECK_NOTHROW(ss >> j);
CHECK(j == json({{"three", 3}}));
CHECK_THROWS_WITH_AS(ss >> j, "[json.exception.parse_error.101] parse error at line 1, column 1: attempting to parse an empty input; check that your input string or stream contains the expected JSON", json::parse_error&);
}
SECTION("second example from #529")
{
std::string const str = "{\n\"one\" : 1,\n\"two\" : 2\n}\n{\n\"three\" : 3\n}";
{
std::ofstream file("test.json");
file << str;
}
std::ifstream stream("test.json", std::ifstream::in);
json val;
size_t i = 0;
while (stream.peek() != EOF)
{
CAPTURE(i)
CHECK_NOTHROW(stream >> val);
CHECK(i < 2);
if (i == 0)
{
CHECK(val == json({{"one", 1}, {"two", 2}}));
}
if (i == 1)
{
CHECK(val == json({{"three", 3}}));
}
++i;
}
static_cast<void>(std::remove("test.json"));
}
}
SECTION("issue #389 - Integer-overflow (OSS-Fuzz issue 267)")
{
// original test case
json const j1 = json::parse("-9223372036854775808");
CHECK(j1.is_number_integer());
CHECK(j1.get<json::number_integer_t>() == (std::numeric_limits<std::int64_t>::min)());
// edge case (+1; still an integer)
json const j2 = json::parse("-9223372036854775807");
CHECK(j2.is_number_integer());
CHECK(j2.get<json::number_integer_t>() == (std::numeric_limits<std::int64_t>::min)() + 1);
// edge case (-1; overflow -> floats)
json const j3 = json::parse("-9223372036854775809");
CHECK(j3.is_number_float());
}
SECTION("issue #380 - bug in overflow detection when parsing integers")
{
json const j = json::parse("166020696663385964490");
CHECK(j.is_number_float());
CHECK(j.get<json::number_float_t>() == 166020696663385964490.0);
}
SECTION("issue #405 - Heap-buffer-overflow (OSS-Fuzz issue 342)")
{
// original test case
std::vector<uint8_t> const vec {0x65, 0xf5, 0x0a, 0x48, 0x21};
json _;
CHECK_THROWS_WITH_AS(_ = json::from_cbor(vec), "[json.exception.parse_error.110] parse error at byte 6: syntax error while parsing CBOR string: unexpected end of input", json::parse_error&);
}
SECTION("issue #407 - Heap-buffer-overflow (OSS-Fuzz issue 343)")
{
json _;
// original test case: incomplete float64
std::vector<uint8_t> const vec1 {0xcb, 0x8f, 0x0a};
CHECK_THROWS_WITH_AS(_ = json::from_msgpack(vec1), "[json.exception.parse_error.110] parse error at byte 4: syntax error while parsing MessagePack number: unexpected end of input", json::parse_error&);
// related test case: incomplete float32
std::vector<uint8_t> const vec2 {0xca, 0x8f, 0x0a};
CHECK_THROWS_WITH_AS(_ = json::from_msgpack(vec2), "[json.exception.parse_error.110] parse error at byte 4: syntax error while parsing MessagePack number: unexpected end of input", json::parse_error&);
// related test case: incomplete Half-Precision Float (CBOR)
std::vector<uint8_t> const vec3 {0xf9, 0x8f};
CHECK_THROWS_WITH_AS(_ = json::from_cbor(vec3), "[json.exception.parse_error.110] parse error at byte 3: syntax error while parsing CBOR number: unexpected end of input", json::parse_error&);
// related test case: incomplete Single-Precision Float (CBOR)
std::vector<uint8_t> const vec4 {0xfa, 0x8f, 0x0a};
CHECK_THROWS_WITH_AS(_ = json::from_cbor(vec4), "[json.exception.parse_error.110] parse error at byte 4: syntax error while parsing CBOR number: unexpected end of input", json::parse_error&);
// related test case: incomplete Double-Precision Float (CBOR)
std::vector<uint8_t> const vec5 {0xfb, 0x8f, 0x0a};
CHECK_THROWS_WITH_AS(_ = json::from_cbor(vec5), "[json.exception.parse_error.110] parse error at byte 4: syntax error while parsing CBOR number: unexpected end of input", json::parse_error&);
}
SECTION("issue #408 - Heap-buffer-overflow (OSS-Fuzz issue 344)")
{
json _;
// original test case
std::vector<uint8_t> const vec1 {0x87};
CHECK_THROWS_WITH_AS(_ = json::from_msgpack(vec1), "[json.exception.parse_error.110] parse error at byte 2: syntax error while parsing MessagePack string: unexpected end of input", json::parse_error&);
// more test cases for MessagePack
for (auto b :
{
0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, // fixmap
0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, // fixarray
0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf, // fixstr
0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf
})
{
std::vector<uint8_t> const vec(1, static_cast<uint8_t>(b));
CHECK_THROWS_AS(_ = json::from_msgpack(vec), json::parse_error&);
}
// more test cases for CBOR
for (auto b :
{
0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f,
0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, // UTF-8 string
0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f,
0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, // array
0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf,
0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7 // map
})
{
std::vector<uint8_t> const vec(1, static_cast<uint8_t>(b));
CHECK_THROWS_AS(_ = json::from_cbor(vec), json::parse_error&);
}
// special case: empty input
std::vector<uint8_t> const vec2;
CHECK_THROWS_WITH_AS(_ = json::from_cbor(vec2), "[json.exception.parse_error.110] parse error at byte 1: syntax error while parsing CBOR value: unexpected end of input", json::parse_error&);
CHECK_THROWS_WITH_AS(_ = json::from_msgpack(vec2), "[json.exception.parse_error.110] parse error at byte 1: syntax error while parsing MessagePack value: unexpected end of input", json::parse_error&);
}
SECTION("issue #411 - Heap-buffer-overflow (OSS-Fuzz issue 366)")
{
json _;
// original test case: empty UTF-8 string (indefinite length)
std::vector<uint8_t> const vec1 {0x7f};
CHECK_THROWS_WITH_AS(_ = json::from_cbor(vec1), "[json.exception.parse_error.110] parse error at byte 2: syntax error while parsing CBOR string: unexpected end of input", json::parse_error&);
// related test case: empty array (indefinite length)
std::vector<uint8_t> const vec2 {0x9f};
CHECK_THROWS_WITH_AS(_ = json::from_cbor(vec2), "[json.exception.parse_error.110] parse error at byte 2: syntax error while parsing CBOR value: unexpected end of input", json::parse_error&);
// related test case: empty map (indefinite length)
std::vector<uint8_t> const vec3 {0xbf};
CHECK_THROWS_WITH_AS(_ = json::from_cbor(vec3), "[json.exception.parse_error.110] parse error at byte 2: syntax error while parsing CBOR string: unexpected end of input", json::parse_error&);
}
SECTION("issue #412 - Heap-buffer-overflow (OSS-Fuzz issue 367)")
{
// original test case
std::vector<uint8_t> const vec
{
0xab, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98,
0x98, 0x98, 0x98, 0x98, 0x98, 0x00, 0x00, 0x00,
0x60, 0xab, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98,
0x98, 0x98, 0x98, 0x98, 0x98, 0x00, 0x00, 0x00,
0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60,
0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60,
0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60,
0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60,
0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60,
0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60,
0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0xa0, 0x9f,
0x9f, 0x97, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60,
0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60,
0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60,
0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60,
0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60,
0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60
};
json _;
CHECK_THROWS_WITH_AS(_ = json::from_cbor(vec), "[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing CBOR object key: only string keys are supported, but found an array; last byte: 0x98", json::parse_error&);
// related test case: nonempty UTF-8 string (indefinite length)
std::vector<uint8_t> const vec1 {0x7f, 0x61, 0x61};
CHECK_THROWS_WITH_AS(_ = json::from_cbor(vec1), "[json.exception.parse_error.110] parse error at byte 4: syntax error while parsing CBOR string: unexpected end of input", json::parse_error&);
// related test case: nonempty array (indefinite length)
std::vector<uint8_t> const vec2 {0x9f, 0x01};
CHECK_THROWS_WITH_AS(_ = json::from_cbor(vec2), "[json.exception.parse_error.110] parse error at byte 3: syntax error while parsing CBOR value: unexpected end of input", json::parse_error&);
// related test case: nonempty map (indefinite length)
std::vector<uint8_t> const vec3 {0xbf, 0x61, 0x61, 0x01};
CHECK_THROWS_WITH_AS(_ = json::from_cbor(vec3), "[json.exception.parse_error.110] parse error at byte 5: syntax error while parsing CBOR string: unexpected end of input", json::parse_error&);
}
SECTION("issue #414 - compare with literal 0)")
{
#define CHECK_TYPE(v) \
CHECK((json(v) == (v)));\
CHECK(((v) == json(v)));\
CHECK_FALSE((json(v) != (v)));\
CHECK_FALSE(((v) != json(v)));
CHECK_TYPE(nullptr)
CHECK_TYPE(0)
CHECK_TYPE(0u)
CHECK_TYPE(0L)
CHECK_TYPE(0.0)
CHECK_TYPE("") // NOLINT(readability-container-size-empty)
#undef CHECK_TYPE
}
SECTION("issue #416 - Use-of-uninitialized-value (OSS-Fuzz issue 377)")
{
// original test case
std::vector<uint8_t> const vec1
{
0x94, 0xfa, 0xfa, 0xfa, 0xfa, 0xfa, 0xfa, 0xfa,
0x3a, 0x96, 0x96, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4,
0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0x71,
0xb4, 0xb4, 0xfa, 0xfa, 0xfa, 0xfa, 0xfa, 0x3a,
0x96, 0x96, 0xb4, 0xb4, 0xfa, 0x94, 0x94, 0x61,
0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0xfa
};
json _;
CHECK_THROWS_WITH_AS(_ = json::from_cbor(vec1), "[json.exception.parse_error.113] parse error at byte 13: syntax error while parsing CBOR object key: only string keys are supported, but found a map; last byte: 0xB4", json::parse_error&);
// related test case: double-precision
std::vector<uint8_t> const vec2
{
0x94, 0xfa, 0xfa, 0xfa, 0xfa, 0xfa, 0xfa, 0xfa,
0x3a, 0x96, 0x96, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4,
0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0x71,
0xb4, 0xb4, 0xfa, 0xfa, 0xfa, 0xfa, 0xfa, 0x3a,
0x96, 0x96, 0xb4, 0xb4, 0xfa, 0x94, 0x94, 0x61,
0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0xfb
};
CHECK_THROWS_WITH_AS(_ = json::from_cbor(vec2), "[json.exception.parse_error.113] parse error at byte 13: syntax error while parsing CBOR object key: only string keys are supported, but found a map; last byte: 0xB4", json::parse_error&);
}
SECTION("issue #452 - Heap-buffer-overflow (OSS-Fuzz issue 585)")
{
std::vector<uint8_t> const vec = {'-', '0', '1', '2', '2', '7', '4'};
json _;
CHECK_THROWS_AS(_ = json::parse(vec), json::parse_error&);
}
SECTION("issue #454 - doubles are printed as integers")
{
json j = R"({"bool_value":true,"double_value":2.0,"int_value":10,"level1":{"list_value":[3,"hi",false],"tmp":5.0},"string_value":"hello"})"_json;
CHECK(j["double_value"].is_number_float());
}
#if JSON_USE_IMPLICIT_CONVERSIONS
SECTION("issue #464 - VS2017 implicit to std::string conversion fix")
{
const json v = "test";
std::string test;
test = v;
CHECK(v == "test");
}
#endif
SECTION("issue #465 - roundtrip error while parsing 1000000000000000010E5")
{
json const j1 = json::parse("1000000000000000010E5");
const std::string s1 = j1.dump();
json const j2 = json::parse(s1);
const std::string s2 = j2.dump();
CHECK(s1 == s2);
}
#if JSON_USE_IMPLICIT_CONVERSIONS
SECTION("issue #473 - inconsistent behavior in conversion to array type")
{
json const j_array = {1, 2, 3, 4};
json const j_number = 42;
json const j_null = nullptr;
SECTION("std::vector")
{
auto create = [](const json & j)
{
std::vector<int> const v = j;
};
CHECK_NOTHROW(create(j_array));
CHECK_THROWS_WITH_AS(create(j_number), "[json.exception.type_error.302] type must be array, but is number", json::type_error&);
CHECK_THROWS_WITH_AS(create(j_null), "[json.exception.type_error.302] type must be array, but is null", json::type_error&);
}
SECTION("std::list")
{
auto create = [](const json & j)
{
std::list<int> const v = j;
};
CHECK_NOTHROW(create(j_array));
CHECK_THROWS_WITH_AS(create(j_number), "[json.exception.type_error.302] type must be array, but is number", json::type_error&);
CHECK_THROWS_WITH_AS(create(j_null), "[json.exception.type_error.302] type must be array, but is null", json::type_error&);
}
SECTION("std::forward_list")
{
auto create = [](const json & j)
{
std::forward_list<int> const v = j;
};
CHECK_NOTHROW(create(j_array));
CHECK_THROWS_WITH_AS(create(j_number), "[json.exception.type_error.302] type must be array, but is number", json::type_error&);
CHECK_THROWS_WITH_AS(create(j_null), "[json.exception.type_error.302] type must be array, but is null", json::type_error&);
}
}
#endif
SECTION("issue #486 - json::value_t can't be a map's key type in VC++ 2015")
{
// the code below must compile with MSVC
std::map<json::value_t, std::string> jsonTypes ;
jsonTypes[json::value_t::array] = "array";
}
SECTION("issue #494 - conversion from vector<bool> to json fails to build")
{
std::vector<bool> const boolVector = {false, true, false, false};
json j;
j["bool_vector"] = boolVector;
CHECK(j["bool_vector"].dump() == "[false,true,false,false]");
}
SECTION("issue #504 - assertion error (OSS-Fuzz 856)")
{
std::vector<uint8_t> const vec1 = {0xf9, 0xff, 0xff, 0x4a, 0x3a, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01, 0x37, 0x02, 0x38};
json const j1 = json::from_cbor(vec1, false);
// step 2: round trip
std::vector<uint8_t> vec2 = json::to_cbor(j1);
// parse serialization
json const j2 = json::from_cbor(vec2);
// NaN is dumped to "null"
CHECK(j2.is_number_float());
CHECK(std::isnan(j2.get<json::number_float_t>()));
CHECK(j2.dump() == "null");
// check if serializations match
CHECK(json::to_cbor(j2) == vec2);
}
SECTION("issue #512 - use of overloaded operator '<=' is ambiguous")
{
json j;
j["a"] = 5;
// json op scalar
CHECK(j["a"] == 5);
CHECK(j["a"] != 4);
CHECK(j["a"] <= 7);
CHECK(j["a"] < 7);
CHECK(j["a"] >= 3);
CHECK(j["a"] > 3);
CHECK(!(j["a"] <= 4));
CHECK(!(j["a"] < 4));
CHECK(!(j["a"] >= 6));
CHECK(!(j["a"] > 6));
// scalar op json
CHECK(5 == j["a"]);
CHECK(4 != j["a"]);
CHECK(7 >= j["a"]);
CHECK(7 > j["a"]);
CHECK(3 <= j["a"]);
CHECK(3 < j["a"]);
CHECK(!(4 >= j["a"]));
CHECK(!(4 > j["a"]));
CHECK(!(6 <= j["a"]));
CHECK(!(6 < j["a"]));
}
SECTION("issue #575 - heap-buffer-overflow (OSS-Fuzz 1400)")
{
json _;
std::vector<uint8_t> const vec = {'"', '\\', '"', 'X', '"', '"'};
CHECK_THROWS_AS(_ = json::parse(vec), json::parse_error&);
}
#if JSON_USE_IMPLICIT_CONVERSIONS
SECTION("issue #600 - how does one convert a map in Json back to std::map?")
{
SECTION("example 1")
{
// create a map
const std::map<std::string, int> m1 {{"key", 1}};
// create and print a JSON from the map
json const j = m1;
// get the map out of JSON
std::map<std::string, int> m2 = j;
// make sure the roundtrip succeeds
CHECK(m1 == m2);
}
SECTION("example 2")
{
// create a map
const std::map<std::string, std::string> m1 {{"key", "val"}};
// create and print a JSON from the map
json const j = m1;
// get the map out of JSON
std::map<std::string, std::string> m2 = j;
// make sure the roundtrip succeeds
CHECK(m1 == m2);
}
}
#endif
SECTION("issue #602 - BOM not skipped when using json:parse(iterator)")
{
std::string i = "\xef\xbb\xbf{\n \"foo\": true\n}";
json _;
CHECK_NOTHROW(_ = json::parse(i.begin(), i.end()));
}
#if JSON_USE_IMPLICIT_CONVERSIONS
SECTION("issue #702 - conversion from valarray<double> to json fails to build")
{
SECTION("original example")
{
std::valarray<double> const v;
nlohmann::json j;
j["test"] = v;
}
SECTION("full example")
{
std::valarray<double> v = {1.2, 2.3, 3.4, 4.5};
json j = v;
std::valarray<double> vj = j;
CHECK(j == json(vj));
CHECK(v.size() == vj.size());
for (size_t i = 0; i < v.size(); ++i)
{
CHECK(v[i] == vj[i]);
CHECK(v[i] == j[i]);
}
CHECK_THROWS_WITH_AS(json().get<std::valarray<double>>(), "[json.exception.type_error.302] type must be array, but is null", json::type_error&);
}
}
#endif
SECTION("issue #367 - Behavior of operator>> should more closely resemble that of built-in overloads.")
{
SECTION("example 1")
{
std::istringstream i1_2_3( R"({"first": "one" }{"second": "two"}3)" );
json j1;
json j2;
json j3;
i1_2_3 >> j1;
i1_2_3 >> j2;
i1_2_3 >> j3;
auto m1 = j1.get<std::map<std::string, std::string>>();
auto m2 = j2.get<std::map<std::string, std::string>>();
const int i3{j3};
CHECK( m1 == ( std::map<std::string, std::string> {{ "first", "one" }} ));
CHECK( m2 == ( std::map<std::string, std::string> {{ "second", "two" }} ));
CHECK( i3 == 3 );
}
}
SECTION("issue #714 - throw std::ios_base::failure exception when failbit set to true")
{
{
std::ifstream is;
is.exceptions(
is.exceptions()
| std::ios_base::failbit
| std::ios_base::badbit
); // handle different exceptions as 'file not found', 'permission denied'
is.open(TEST_DATA_DIRECTORY "/regression/working_file.json");
json _;
CHECK_NOTHROW(_ = nlohmann::json::parse(is));
}
{
std::ifstream is;
is.exceptions(
is.exceptions()
| std::ios_base::failbit
| std::ios_base::badbit
); // handle different exceptions as 'file not found', 'permission denied'
is.open(TEST_DATA_DIRECTORY "/json_nlohmann_tests/all_unicode.json.cbor",
std::ios_base::in | std::ios_base::binary);
json _;
CHECK_NOTHROW(_ = nlohmann::json::from_cbor(is));
}
}
SECTION("issue #805 - copy constructor is used with std::initializer_list constructor.")
{
nocopy n;
json j;
j = {{"nocopy", n}};
CHECK(j["nocopy"]["val"] == 0);
}
SECTION("issue #838 - incorrect parse error with binary data in keys")
{
std::array<uint8_t, 28> key1 = {{ 103, 92, 117, 48, 48, 48, 55, 92, 114, 215, 126, 214, 95, 92, 34, 174, 40, 71, 38, 174, 40, 71, 38, 223, 134, 247, 127, 0 }};
std::string const key1_str(reinterpret_cast<char*>(key1.data()));
json const j = key1_str;
// dump() is nodiscard; the exception is thrown by dump() itself before it would return
CHECK_THROWS_WITH_AS(utils::ignore_return_value(j.dump()), "[json.exception.type_error.316] invalid UTF-8 byte at index 10: 0x7E", json::type_error&);
}
#if JSON_USE_IMPLICIT_CONVERSIONS
SECTION("issue #843 - converting to array not working")
{
json j;
std::array<int, 4> ar = {{1, 1, 1, 1}};
j = ar;
ar = j;
}
#endif
SECTION("issue #894 - invalid RFC6902 copy operation succeeds")
{
auto model = R"({
"one": {
"two": {
"three": "hello",
"four": 42
}
}
})"_json;
auto p1 = R"([{"op": "move",
"from": "/one/two/three",
"path": "/a/b/c"}])"_json;
#if JSON_DIAGNOSTIC_POSITIONS
CHECK_THROWS_WITH_AS(model.patch(p1),
"[json.exception.out_of_range.403] (bytes 0-158) key 'a' not found", json::out_of_range&);
#else
CHECK_THROWS_WITH_AS(model.patch(p1),
"[json.exception.out_of_range.403] key 'a' not found", json::out_of_range&);
#endif
auto p2 = R"([{"op": "copy",
"from": "/one/two/three",
"path": "/a/b/c"}])"_json;
#if JSON_DIAGNOSTIC_POSITIONS
CHECK_THROWS_WITH_AS(model.patch(p2),
"[json.exception.out_of_range.403] (bytes 0-158) key 'a' not found", json::out_of_range&);
#else
CHECK_THROWS_WITH_AS(model.patch(p2),
"[json.exception.out_of_range.403] key 'a' not found", json::out_of_range&);
#endif
}
SECTION("issue #961 - incorrect parsing of indefinite length CBOR strings")
{
std::vector<uint8_t> const v_cbor =
{
0x7F,
0x64,
'a', 'b', 'c', 'd',
0x63,
'1', '2', '3',
0xFF
};
const json j = json::from_cbor(v_cbor);
CHECK(j == "abcd123");
}
SECTION("issue #962 - Timeout (OSS-Fuzz 6034)")
{
json _;
std::vector<uint8_t> v_ubjson = {'[', '$', 'Z', '#', 'L', 0x78, 0x28, 0x00, 0x68, 0x28, 0x69, 0x69, 0x17};
CHECK_THROWS_AS(_ = json::from_ubjson(v_ubjson), json::out_of_range&);
//CHECK_THROWS_WITH(json::from_ubjson(v_ubjson),
// "[json.exception.out_of_range.408] excessive array size: 8658170730974374167");
v_ubjson[0] = '{';
CHECK_THROWS_AS(_ = json::from_ubjson(v_ubjson), json::out_of_range&);
//CHECK_THROWS_WITH(json::from_ubjson(v_ubjson),
// "[json.exception.out_of_range.408] excessive object size: 8658170730974374167");
}
SECTION("issue #971 - Add a SAX parser - late bug")
{
// a JSON text
const auto* text = R"(
{
"Image": {
"Width": 800,
"Height": 600,
"Title": "View from 15th Floor",
"Thumbnail": {
"Url": "http://www.example.com/image/481989943",
"Height": 125,
"Width": 100
},
"Animated" : false,
"IDs": [116, 943, 234, 38793]
}
}
)";
// define parser callback
json::parser_callback_t const cb = [](int /*depth*/, json::parse_event_t event, json & parsed)
{
// skip object elements with key "Thumbnail"
return !(event == json::parse_event_t::key && parsed == json("Thumbnail"));
};
// parse (with callback) and serialize JSON
const json j_filtered = json::parse(text, cb);
CHECK(j_filtered == R"({"Image":{"Animated":false,"Height":600,"IDs":[116,943,234,38793], "Title":"View from 15th Floor","Width":800}})"_json);
}
SECTION("issue #972 - Segmentation fault on G++ when trying to assign json string literal to custom json type")
{
// this assignment used to crash outright
my_json const foo = R"([1, 2, 3])"_json;
// fifo_map is the adapter the docs recommend for keeping object keys
// in insertion order (see docs/mkdocs/docs/features/object_order.md
// and docs/mkdocs/docs/features/types/template_parameters.md); check
// that recommendation actually holds, including through erase() and
// inserting a new key. The comparator is stateful, so this avoids
// deep copies of "order" (see #1763, #5649).
my_json order = my_json::parse(R"({"z":1,"a":2,"m":{"y":1,"b":2}})");
CHECK(order.dump() == R"({"z":1,"a":2,"m":{"y":1,"b":2}})");
order.erase("z");
CHECK(order.dump() == R"({"a":2,"m":{"y":1,"b":2}})");
order["new_key"] = 3;
CHECK(order.dump() == R"({"a":2,"m":{"y":1,"b":2},"new_key":3})");
}
SECTION("operator[](json_pointer) when emplace() finds a key the lookup does not")
{
// a copied fifo_map restarts its timestamps, so a new key "k" gets
// the timestamp of "x": the lookup does not find "k", but emplace()
// finds "x" and inserts nothing; the pointer then continues with the
// member of "x", as basic_json::operator[] does. The object_t is
// copied explicitly: copying the my_json would build the object
// member by member (with fresh timestamps) when built with
// JSON_NO_THREAD_LOCAL.
const my_json orig = my_json::parse(R"({"x":1})");
const std::array<const char*, 3> pointers = {{"/k/0", "/k/a/b", "/k/-"}};
for (const char* pointer : pointers)
{
CAPTURE(pointer)
my_json j(orig.get_ref<const my_json::object_t&>());
CHECK_THROWS_AS(j[my_json::json_pointer(pointer)], my_json::out_of_range&);
CHECK(j.dump() == R"({"x":1})");
}
}
SECTION("operator[](json_pointer) continues with the member that emplace() found")
{
// see the section above: the lookup does not find "k", but emplace()
// finds "x" and the pointer continues with the member of "x". Here
// the member is a container, so the pointer can go on below it. Only
// the outermost object may be a copy (with restarted timestamps): the
// value of "x" is moved in, so that its own lookups work.
const my_json orig = my_json::parse(R"({"x":null})");
const auto make = [&orig]()
{
my_json::object_t top = orig.get_ref<const my_json::object_t&>();
top.at("x") = my_json::parse(R"({"a":{"b":1},"n":null,"arr":[10,20],"objs":[{"m":5},{"m":6}]})");
return my_json(std::move(top)); // NOLINT(modernize-return-braced-init-list): {top} would be an array
};
SECTION("existing members")
{
// emplace() registers the key "k" in the object, so that every
// pointer needs a new object to reach the member of "x" again
const std::array<std::pair<const char*, const char*>, 4> pointers = {{{"/k/a/b", "1"}, {"/k/arr/1", "20"}, {"/k/n", "null"}, {"/k/objs/1/m", "6"}}};
for (const auto& pointer : pointers)
{
CAPTURE(pointer.first)
my_json j = make();
CHECK(j[my_json::json_pointer(pointer.first)].dump() == pointer.second);
CHECK(j.size() == 1);
CHECK(j["x"].size() == 4);
}
}
SECTION("a missing member of an object")
{
my_json j = make();
#if defined(JSON_NO_THREAD_LOCAL) || JSON_RECURSION_DEPTH_LIMIT < 2
// (set by the user, or by the library for clang with MinGW; or fewer than two
// levels are copied by the object type itself, as for a recursion depth limit
// of 0 or 1: the objects below the outermost one are the second level)
// the objects below the outermost one are not copied by their
// object_t here, but member by member, which keeps their
// timestamps correct: "c" is not mistaken for "b" and is inserted
CHECK(j[my_json::json_pointer("/k/a/c/d")].is_null());
CHECK(j["x"]["a"].dump() == R"({"b":1,"c":{"d":null}})");
#else
// the timestamp of "c" is that of "b" again, so that emplace()
// finds the member "b", a number, which has no member "d"
CHECK_THROWS_AS(j[my_json::json_pointer("/k/a/c/d")], my_json::out_of_range&);
CHECK(j["x"]["a"].dump() == R"({"b":1})");
#endif
}
SECTION("a null value is converted to an object")
{
my_json j = make();
j[my_json::json_pointer("/k/n/c")] = 3;
CHECK(j["x"]["n"].dump() == R"({"c":3})");
}
SECTION("the end of an array")
{
my_json j = make();
j[my_json::json_pointer("/k/arr/-")] = 30;
CHECK(j["x"]["arr"].dump() == "[10,20,30]");
}
SECTION("an index past the end of an array")
{
my_json j = make();
j[my_json::json_pointer("/k/arr/3")] = 40;
CHECK(j["x"]["arr"].dump() == "[10,20,null,40]");
}
}
SECTION("issue #977 - Assigning between different json types")
{
foo_json lj = ns::foo{3};
const ns::foo ff(lj);
CHECK(lj.is_object());
CHECK(lj.size() == 1);
CHECK(lj["x"] == 3);
CHECK(ff.x == 3);
nlohmann::json const nj = lj; // This line works as expected
}
}
#if !defined(JSON_NOEXCEPTION)
TEST_CASE("regression tests, exceptions dependent")
{
SECTION("issue #1340 - eof not set on exhausted input stream")
{
std::stringstream s("{}{}");
json j;
s >> j;
s >> j;
CHECK_THROWS_AS(s >> j, json::parse_error const&);
CHECK(s.eof());
}
}
#endif
/////////////////////////////////////////////////////////////////////
// for #1642
/////////////////////////////////////////////////////////////////////
// the code below fails with Clang on Windows, so we need to exclude it there
#if DOCTEST_CLANG && (defined(WIN32) || defined(_WIN32) || defined(__WIN32) && !defined(__CYGWIN__))
#else
template <typename T> class array {};
template <typename T> class object {};
template <typename T> class string {};
template <typename T> class number_integer {};
template <typename T> class number_unsigned {};
template <typename T> class number_float {};
#endif