mirror of
https://github.com/nlohmann/json.git
synced 2026-10-05 06:00:29 +00:00
Merge remote-tracking branch 'origin/json-view/14b-view-float-layout' into HEAD
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
This commit is contained in:
@@ -44,6 +44,10 @@ TEST_CASE("default namespace")
|
||||
expected += "_snul";
|
||||
#endif
|
||||
|
||||
#if JSON_STRICT_BINARY_UTF8
|
||||
expected += "_sbu8";
|
||||
#endif
|
||||
|
||||
expected += "_v" STRINGIZE(NLOHMANN_JSON_VERSION_MAJOR);
|
||||
expected += "_" STRINGIZE(NLOHMANN_JSON_VERSION_MINOR);
|
||||
expected += "_" STRINGIZE(NLOHMANN_JSON_VERSION_PATCH) "::basic_json";
|
||||
|
||||
@@ -45,6 +45,10 @@ TEST_CASE("default namespace without version component")
|
||||
expected += "_snul";
|
||||
#endif
|
||||
|
||||
#if JSON_STRICT_BINARY_UTF8
|
||||
expected += "_sbu8";
|
||||
#endif
|
||||
|
||||
expected += "::basic_json";
|
||||
|
||||
// fallback for Clang
|
||||
|
||||
@@ -10,7 +10,8 @@
|
||||
|
||||
#include <cstdint> // uint8_t
|
||||
#include <cstddef> // size_t
|
||||
#include <fstream> // ifstream, istreambuf_iterator, ios
|
||||
#include <fstream> // ifstream, ios
|
||||
#include <iterator> // istream_iterator
|
||||
#include <vector> // vector
|
||||
|
||||
namespace utils
|
||||
|
||||
@@ -567,7 +567,7 @@ struct allocator_no_forward : std::allocator<T>
|
||||
{
|
||||
allocator_no_forward() = default;
|
||||
template <class U>
|
||||
allocator_no_forward(allocator_no_forward<U> /*unused*/) {}
|
||||
allocator_no_forward(const allocator_no_forward<U>& /*unused*/) {}
|
||||
|
||||
template <class U>
|
||||
struct rebind
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
@@ -423,6 +424,41 @@ TEST_CASE("alternative string type")
|
||||
CHECK(j2.dump() == R"({"/foo/0":"bar","/foo/1":"baz"})");
|
||||
}
|
||||
|
||||
SECTION("conversion between basic_json specializations (#2649)")
|
||||
{
|
||||
// explicit conversions are always possible
|
||||
CHECK(std::is_constructible<nlohmann::json, alt_json>::value);
|
||||
CHECK(std::is_constructible<alt_json, nlohmann::json>::value);
|
||||
CHECK(std::is_constructible<nlohmann::json, nlohmann::ordered_json>::value);
|
||||
CHECK(std::is_constructible<nlohmann::ordered_json, nlohmann::json>::value);
|
||||
|
||||
// specializations with the same string type are implicitly convertible
|
||||
CHECK(std::is_convertible<nlohmann::ordered_json, nlohmann::json>::value);
|
||||
CHECK(std::is_convertible<nlohmann::json, nlohmann::ordered_json>::value);
|
||||
|
||||
// specializations with different string types are only implicitly convertible
|
||||
// if implicit conversions are enabled
|
||||
#if JSON_USE_IMPLICIT_CONVERSIONS
|
||||
CHECK(std::is_convertible<alt_json, nlohmann::json>::value);
|
||||
CHECK(std::is_convertible<nlohmann::json, alt_json>::value);
|
||||
#else
|
||||
CHECK_FALSE(std::is_convertible<alt_json, nlohmann::json>::value);
|
||||
CHECK_FALSE(std::is_convertible<nlohmann::json, alt_json>::value);
|
||||
#endif
|
||||
|
||||
// get<BasicJsonType>() works in either case
|
||||
const nlohmann::json j = {{"foo", 1}, {"bar", true}};
|
||||
CHECK(j.get<nlohmann::ordered_json>() == nlohmann::ordered_json(j));
|
||||
// (only a number is converted here, as objects and strings are affected by #3425)
|
||||
CHECK(nlohmann::json(42).get<alt_json>() == 42);
|
||||
CHECK(alt_json(nlohmann::json(42)) == 42);
|
||||
|
||||
// get_to() also works in either case
|
||||
alt_json a;
|
||||
nlohmann::json(42).get_to(a);
|
||||
CHECK(a == 42);
|
||||
}
|
||||
|
||||
SECTION("strict enum")
|
||||
{
|
||||
// regression test for #5667: NLOHMANN_JSON_SERIALIZE_ENUM_STRICT's from_json
|
||||
|
||||
@@ -0,0 +1,372 @@
|
||||
// __ _____ _____ _____
|
||||
// __| | __| | | | 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"
|
||||
#include "test_utils.hpp"
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
using nlohmann::json;
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
struct ill_formed_case
|
||||
{
|
||||
const char* name;
|
||||
std::string bytes;
|
||||
};
|
||||
|
||||
// RFC 3629 ill-formed sequences used throughout this file, plus one
|
||||
// well-formed sequence for contrast
|
||||
std::vector<ill_formed_case> ill_formed_cases()
|
||||
{
|
||||
return
|
||||
{
|
||||
{"overlong", "\xC0\xAE"},
|
||||
{"lone_0xFF", "\xFF"},
|
||||
{"truncated", "\xE2\x82"},
|
||||
{"surrogate", "\xED\xA0\x80"},
|
||||
};
|
||||
}
|
||||
|
||||
std::string valid_sequence()
|
||||
{
|
||||
return "\xC3\xA9"; // U+00E9, "é"
|
||||
}
|
||||
|
||||
using eh = json::error_handler_t;
|
||||
std::vector<eh> all_handlers()
|
||||
{
|
||||
return {eh::strict, eh::replace, eh::ignore, eh::keep};
|
||||
}
|
||||
|
||||
// what dump()+parse() produces for a sanitizing error_handler; this is the
|
||||
// ground truth every binary writer/reader is checked against
|
||||
std::string dump_and_parse(const std::string& raw, eh error_handler)
|
||||
{
|
||||
return json::parse(json(raw).dump(-1, ' ', false, error_handler)).get<std::string>();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("UTF-8 error_handler for the binary readers and writers")
|
||||
{
|
||||
SECTION("writers: string value")
|
||||
{
|
||||
for (const auto& c : ill_formed_cases())
|
||||
{
|
||||
CAPTURE(c.name)
|
||||
const json jval = c.bytes;
|
||||
|
||||
CHECK_THROWS_AS(json::to_cbor(jval, eh::strict), json::type_error&);
|
||||
CHECK_THROWS_AS(json::to_msgpack(jval, eh::strict), json::type_error&);
|
||||
CHECK_THROWS_AS(json::to_ubjson(jval, false, false, eh::strict), json::type_error&);
|
||||
CHECK_THROWS_AS(json::to_bjdata(jval, false, false, json::bjdata_version_t::draft2, eh::strict), json::type_error&);
|
||||
{
|
||||
json jobj;
|
||||
jobj["k"] = jval;
|
||||
CHECK_THROWS_AS(json::to_bson(jobj, eh::strict), json::type_error&);
|
||||
}
|
||||
|
||||
for (const auto h :
|
||||
{
|
||||
eh::replace, eh::ignore
|
||||
})
|
||||
{
|
||||
CAPTURE(static_cast<int>(h))
|
||||
const std::string expected = dump_and_parse(c.bytes, h);
|
||||
|
||||
CHECK(json::from_cbor(json::to_cbor(jval, h)).get<std::string>() == expected);
|
||||
CHECK(json::from_msgpack(json::to_msgpack(jval, h)).get<std::string>() == expected);
|
||||
CHECK(json::from_ubjson(json::to_ubjson(jval, false, false, h)).get<std::string>() == expected);
|
||||
CHECK(json::from_bjdata(json::to_bjdata(jval, false, false, json::bjdata_version_t::draft2, h)).get<std::string>() == expected);
|
||||
{
|
||||
json jobj;
|
||||
jobj["k"] = jval;
|
||||
const auto bytes = json::to_bson(jobj, h);
|
||||
CHECK(json::from_bson(bytes)["k"].get<std::string>() == expected);
|
||||
}
|
||||
}
|
||||
|
||||
// keep: the writer passes the ill-formed bytes through unchanged,
|
||||
// exactly as every binary writer did before this parameter existed
|
||||
CHECK(json::from_cbor(json::to_cbor(jval, eh::keep)).get<std::string>() == c.bytes);
|
||||
CHECK(json::from_msgpack(json::to_msgpack(jval, eh::keep)).get<std::string>() == c.bytes);
|
||||
CHECK(json::from_ubjson(json::to_ubjson(jval, false, false, eh::keep)).get<std::string>() == c.bytes);
|
||||
CHECK(json::from_bjdata(json::to_bjdata(jval, false, false, json::bjdata_version_t::draft2, eh::keep)).get<std::string>() == c.bytes);
|
||||
{
|
||||
json jobj;
|
||||
jobj["k"] = jval;
|
||||
const auto bytes = json::to_bson(jobj, eh::keep);
|
||||
CHECK(json::from_bson(bytes)["k"].get<std::string>() == c.bytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("writers: object key")
|
||||
{
|
||||
for (const auto& c : ill_formed_cases())
|
||||
{
|
||||
CAPTURE(c.name)
|
||||
json jobj;
|
||||
jobj[c.bytes] = 1;
|
||||
|
||||
CHECK_THROWS_AS(json::to_cbor(jobj, eh::strict), json::type_error&);
|
||||
CHECK_THROWS_AS(json::to_msgpack(jobj, eh::strict), json::type_error&);
|
||||
CHECK_THROWS_AS(json::to_ubjson(jobj, false, false, eh::strict), json::type_error&);
|
||||
CHECK_THROWS_AS(json::to_bjdata(jobj, false, false, json::bjdata_version_t::draft2, eh::strict), json::type_error&);
|
||||
CHECK_THROWS_AS(json::to_bson(jobj, eh::strict), json::type_error&);
|
||||
|
||||
for (const auto h :
|
||||
{
|
||||
eh::replace, eh::ignore
|
||||
})
|
||||
{
|
||||
CAPTURE(static_cast<int>(h))
|
||||
const std::string expected = dump_and_parse(c.bytes, h);
|
||||
|
||||
CHECK(json::from_cbor(json::to_cbor(jobj, h)).begin().key() == expected);
|
||||
CHECK(json::from_msgpack(json::to_msgpack(jobj, h)).begin().key() == expected);
|
||||
CHECK(json::from_ubjson(json::to_ubjson(jobj, false, false, h)).begin().key() == expected);
|
||||
CHECK(json::from_bjdata(json::to_bjdata(jobj, false, false, json::bjdata_version_t::draft2, h)).begin().key() == expected);
|
||||
CHECK(json::from_bson(json::to_bson(jobj, h)).begin().key() == expected);
|
||||
}
|
||||
|
||||
// keep: object keys round-trip unchanged too
|
||||
CHECK(json::from_cbor(json::to_cbor(jobj, eh::keep)).begin().key() == c.bytes);
|
||||
CHECK(json::from_msgpack(json::to_msgpack(jobj, eh::keep)).begin().key() == c.bytes);
|
||||
CHECK(json::from_ubjson(json::to_ubjson(jobj, false, false, eh::keep)).begin().key() == c.bytes);
|
||||
CHECK(json::from_bjdata(json::to_bjdata(jobj, false, false, json::bjdata_version_t::draft2, eh::keep)).begin().key() == c.bytes);
|
||||
CHECK(json::from_bson(json::to_bson(jobj, eh::keep)).begin().key() == c.bytes);
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("readers: string value")
|
||||
{
|
||||
for (const auto& c : ill_formed_cases())
|
||||
{
|
||||
CAPTURE(c.name)
|
||||
|
||||
// bytes produced the lenient (keep) way, as any binary reader
|
||||
// accepted them before this parameter existed
|
||||
const auto cbor_bytes = json::to_cbor(json(c.bytes), eh::keep);
|
||||
const auto msgpack_bytes = json::to_msgpack(json(c.bytes)); // to_msgpack has no error_handler; always pass-through
|
||||
const auto ubjson_bytes = json::to_ubjson(json(c.bytes), false, false, eh::keep);
|
||||
const auto bjdata_bytes = json::to_bjdata(json(c.bytes), false, false, json::bjdata_version_t::draft2, eh::keep);
|
||||
const auto bson_bytes = [&c]
|
||||
{
|
||||
json jobj;
|
||||
jobj["k"] = c.bytes;
|
||||
return json::to_bson(jobj, eh::keep);
|
||||
}();
|
||||
|
||||
// keep (the default): bytes are kept unchanged
|
||||
CHECK(json::from_cbor(cbor_bytes).get<std::string>() == c.bytes);
|
||||
CHECK(json::from_msgpack(msgpack_bytes).get<std::string>() == c.bytes);
|
||||
CHECK(json::from_ubjson(ubjson_bytes).get<std::string>() == c.bytes);
|
||||
CHECK(json::from_bjdata(bjdata_bytes).get<std::string>() == c.bytes);
|
||||
CHECK(json::from_bson(bson_bytes)["k"].get<std::string>() == c.bytes);
|
||||
|
||||
// strict: parse_error.113, discarded (not thrown) when allow_exceptions is false
|
||||
CHECK_THROWS_AS(utils::ignore_return_value(json::from_cbor(cbor_bytes, true, true, json::cbor_tag_handler_t::error, eh::strict)), json::parse_error&);
|
||||
CHECK(json::from_cbor(cbor_bytes, true, false, json::cbor_tag_handler_t::error, eh::strict).is_discarded());
|
||||
CHECK_THROWS_AS(utils::ignore_return_value(json::from_msgpack(msgpack_bytes, true, true, eh::strict)), json::parse_error&);
|
||||
CHECK(json::from_msgpack(msgpack_bytes, true, false, eh::strict).is_discarded());
|
||||
CHECK_THROWS_AS(utils::ignore_return_value(json::from_ubjson(ubjson_bytes, true, true, eh::strict)), json::parse_error&);
|
||||
CHECK(json::from_ubjson(ubjson_bytes, true, false, eh::strict).is_discarded());
|
||||
CHECK_THROWS_AS(utils::ignore_return_value(json::from_bjdata(bjdata_bytes, true, true, eh::strict)), json::parse_error&);
|
||||
CHECK(json::from_bjdata(bjdata_bytes, true, false, eh::strict).is_discarded());
|
||||
CHECK_THROWS_AS(utils::ignore_return_value(json::from_bson(bson_bytes, true, true, eh::strict)), json::parse_error&);
|
||||
CHECK(json::from_bson(bson_bytes, true, false, eh::strict).is_discarded());
|
||||
|
||||
// replace / ignore: match what dump() would have sanitized the same bytes to
|
||||
for (const auto h :
|
||||
{
|
||||
eh::replace, eh::ignore
|
||||
})
|
||||
{
|
||||
CAPTURE(static_cast<int>(h))
|
||||
const std::string expected = dump_and_parse(c.bytes, h);
|
||||
|
||||
CHECK(json::from_cbor(cbor_bytes, true, true, json::cbor_tag_handler_t::error, h).get<std::string>() == expected);
|
||||
CHECK(json::from_msgpack(msgpack_bytes, true, true, h).get<std::string>() == expected);
|
||||
CHECK(json::from_ubjson(ubjson_bytes, true, true, h).get<std::string>() == expected);
|
||||
CHECK(json::from_bjdata(bjdata_bytes, true, true, h).get<std::string>() == expected);
|
||||
CHECK(json::from_bson(bson_bytes, true, true, h)["k"].get<std::string>() == expected);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("readers: object key")
|
||||
{
|
||||
for (const auto& c : ill_formed_cases())
|
||||
{
|
||||
CAPTURE(c.name)
|
||||
|
||||
json jobj;
|
||||
jobj[c.bytes] = 1;
|
||||
const auto cbor_bytes = json::to_cbor(jobj, eh::keep);
|
||||
const auto msgpack_bytes = json::to_msgpack(jobj);
|
||||
const auto ubjson_bytes = json::to_ubjson(jobj, false, false, eh::keep);
|
||||
const auto bjdata_bytes = json::to_bjdata(jobj, false, false, json::bjdata_version_t::draft2, eh::keep);
|
||||
const auto bson_bytes = json::to_bson(jobj, eh::keep);
|
||||
|
||||
CHECK(json::from_cbor(cbor_bytes).begin().key() == c.bytes);
|
||||
CHECK(json::from_msgpack(msgpack_bytes).begin().key() == c.bytes);
|
||||
CHECK(json::from_ubjson(ubjson_bytes).begin().key() == c.bytes);
|
||||
CHECK(json::from_bjdata(bjdata_bytes).begin().key() == c.bytes);
|
||||
CHECK(json::from_bson(bson_bytes).begin().key() == c.bytes);
|
||||
|
||||
CHECK_THROWS_AS(utils::ignore_return_value(json::from_cbor(cbor_bytes, true, true, json::cbor_tag_handler_t::error, eh::strict)), json::parse_error&);
|
||||
CHECK_THROWS_AS(utils::ignore_return_value(json::from_msgpack(msgpack_bytes, true, true, eh::strict)), json::parse_error&);
|
||||
CHECK_THROWS_AS(utils::ignore_return_value(json::from_ubjson(ubjson_bytes, true, true, eh::strict)), json::parse_error&);
|
||||
CHECK_THROWS_AS(utils::ignore_return_value(json::from_bjdata(bjdata_bytes, true, true, eh::strict)), json::parse_error&);
|
||||
CHECK_THROWS_AS(utils::ignore_return_value(json::from_bson(bson_bytes, true, true, eh::strict)), json::parse_error&);
|
||||
|
||||
for (const auto h :
|
||||
{
|
||||
eh::replace, eh::ignore
|
||||
})
|
||||
{
|
||||
CAPTURE(static_cast<int>(h))
|
||||
const std::string expected = dump_and_parse(c.bytes, h);
|
||||
|
||||
CHECK(json::from_cbor(cbor_bytes, true, true, json::cbor_tag_handler_t::error, h).begin().key() == expected);
|
||||
CHECK(json::from_msgpack(msgpack_bytes, true, true, h).begin().key() == expected);
|
||||
CHECK(json::from_ubjson(ubjson_bytes, true, true, h).begin().key() == expected);
|
||||
CHECK(json::from_bjdata(bjdata_bytes, true, true, h).begin().key() == expected);
|
||||
CHECK(json::from_bson(bson_bytes, true, true, h).begin().key() == expected);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("well-formed UTF-8 is unaffected by error_handler")
|
||||
{
|
||||
const json jval = valid_sequence();
|
||||
json jobj;
|
||||
jobj[valid_sequence()] = valid_sequence();
|
||||
|
||||
for (const auto h : all_handlers())
|
||||
{
|
||||
CAPTURE(static_cast<int>(h))
|
||||
|
||||
CHECK(json::from_cbor(json::to_cbor(jval, h)).get<std::string>() == valid_sequence());
|
||||
CHECK(json::from_msgpack(json::to_msgpack(jval, h)).get<std::string>() == valid_sequence());
|
||||
CHECK(json::from_ubjson(json::to_ubjson(jval, false, false, h)).get<std::string>() == valid_sequence());
|
||||
CHECK(json::from_bjdata(json::to_bjdata(jval, false, false, json::bjdata_version_t::draft2, h)).get<std::string>() == valid_sequence());
|
||||
CHECK(json::from_bson(json::to_bson(jobj, h)).begin().key() == valid_sequence());
|
||||
|
||||
CHECK(json::from_cbor(json::to_cbor(jval, eh::keep), true, true, json::cbor_tag_handler_t::error, h).get<std::string>() == valid_sequence());
|
||||
CHECK(json::from_msgpack(json::to_msgpack(jval), true, true, h).get<std::string>() == valid_sequence());
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("dump() with error_handler_t::keep writes raw bytes as is")
|
||||
{
|
||||
for (const auto& c : ill_formed_cases())
|
||||
{
|
||||
CAPTURE(c.name)
|
||||
|
||||
const json jval = c.bytes;
|
||||
const std::string dumped = jval.dump(-1, ' ', false, eh::keep);
|
||||
CHECK(dumped.find(c.bytes) != std::string::npos);
|
||||
|
||||
// even with ensure_ascii, the ill-formed bytes are written as is
|
||||
const std::string dumped_ascii = jval.dump(-1, ' ', true, eh::keep);
|
||||
CHECK(dumped_ascii.find(c.bytes) != std::string::npos);
|
||||
}
|
||||
|
||||
// well-formed characters around an ill-formed sequence are still
|
||||
// escaped as usual under ensure_ascii
|
||||
const json mixed = valid_sequence() + ill_formed_cases()[1].bytes; // "é" + lone 0xFF
|
||||
const std::string dumped_mixed = mixed.dump(-1, ' ', true, eh::keep);
|
||||
CHECK(dumped_mixed.find("\\u00e9") != std::string::npos);
|
||||
CHECK(dumped_mixed.find(ill_formed_cases()[1].bytes) != std::string::npos);
|
||||
|
||||
// the byte that ends an ill-formed sequence is read again, so a quote,
|
||||
// a backslash, or a control character after it is still escaped, and
|
||||
// a well-formed code point after it is escaped under ensure_ascii
|
||||
for (const bool ensure_ascii :
|
||||
{
|
||||
false, true
|
||||
})
|
||||
{
|
||||
CAPTURE(ensure_ascii)
|
||||
CHECK(json("\xC3\"").dump(-1, ' ', ensure_ascii, eh::keep) == "\"\xC3\\\"\"");
|
||||
CHECK(json("\xC3\\").dump(-1, ' ', ensure_ascii, eh::keep) == "\"\xC3\\\\\"");
|
||||
CHECK(json("\xC3\n").dump(-1, ' ', ensure_ascii, eh::keep) == "\"\xC3\\n\"");
|
||||
CHECK(json("\xE2\x82\"").dump(-1, ' ', ensure_ascii, eh::keep) == "\"\xE2\x82\\\"\"");
|
||||
CHECK(json("\xFF\"").dump(-1, ' ', ensure_ascii, eh::keep) == "\"\xFF\\\"\"");
|
||||
CHECK(json("a\xE2\x82").dump(-1, ' ', ensure_ascii, eh::keep) == "\"a\xE2\x82\"");
|
||||
}
|
||||
CHECK(json("\xC3\xC3\xA9").dump(-1, ' ', false, eh::keep) == "\"\xC3\xC3\xA9\"");
|
||||
CHECK(json("\xC3\xC3\xA9").dump(-1, ' ', true, eh::keep) == "\"\xC3\\u00e9\"");
|
||||
}
|
||||
|
||||
SECTION("to_msgpack defaults to keep; to_bon8 is not affected by error_handler")
|
||||
{
|
||||
const json jval = ill_formed_cases()[1].bytes; // lone 0xFF
|
||||
|
||||
// to_msgpack's error_handler defaults to keep, as MessagePack's spec
|
||||
// allows any bytes in a str, so the bytes are passed through
|
||||
CHECK(json::to_msgpack(jval) == json::to_msgpack(jval, eh::keep));
|
||||
CHECK(json::from_msgpack(json::to_msgpack(jval)).get<std::string>() == ill_formed_cases()[1].bytes);
|
||||
|
||||
// the diagnostics context of an ill-formed key is the object
|
||||
json jobj;
|
||||
jobj["\xFF"] = 1;
|
||||
CHECK_THROWS_WITH_AS(json::to_msgpack(jobj, eh::strict), "[json.exception.type_error.316] invalid UTF-8 byte at index 0: 0xFF", json::type_error&);
|
||||
|
||||
// to_bon8 has no error_handler parameter; UTF-8 is structural for
|
||||
// BON8, so it always rejects ill-formed input
|
||||
CHECK_THROWS_AS(json::to_bon8(jval), json::type_error&);
|
||||
}
|
||||
|
||||
SECTION("allow_exceptions=false with error_handler_t::strict discards the value")
|
||||
{
|
||||
const auto bytes = json::to_cbor(json(ill_formed_cases()[0].bytes), eh::keep);
|
||||
const json result = json::from_cbor(bytes, true, false, json::cbor_tag_handler_t::error, eh::strict);
|
||||
CHECK(result.is_discarded());
|
||||
}
|
||||
|
||||
SECTION("default parameters are unchanged")
|
||||
{
|
||||
const json jval = ill_formed_cases()[0].bytes;
|
||||
|
||||
// to_*: the default error_handler is keep, so ill-formed bytes are
|
||||
// written unchanged, exactly as in release 3.12.0 (it is strict only
|
||||
// if JSON_STRICT_BINARY_UTF8 is enabled, see
|
||||
// unit-binary_utf8_strict.cpp)
|
||||
CHECK(json::to_cbor(jval) == json::to_cbor(jval, eh::keep));
|
||||
CHECK(json::to_ubjson(jval) == json::to_ubjson(jval, false, false, eh::keep));
|
||||
CHECK(json::to_bjdata(jval) == json::to_bjdata(jval, false, false, json::bjdata_version_t::draft2, eh::keep));
|
||||
{
|
||||
json jobj;
|
||||
jobj["k"] = jval;
|
||||
CHECK(json::to_bson(jobj) == json::to_bson(jobj, eh::keep));
|
||||
}
|
||||
|
||||
// from_*: the default error_handler is keep, so ill-formed bytes are
|
||||
// still accepted unchanged, exactly as in release 3.12.0
|
||||
const auto cbor_bytes = json::to_cbor(jval, eh::keep);
|
||||
CHECK(json::from_cbor(cbor_bytes).get<std::string>() == ill_formed_cases()[0].bytes);
|
||||
const auto ubjson_bytes = json::to_ubjson(jval, false, false, eh::keep);
|
||||
CHECK(json::from_ubjson(ubjson_bytes).get<std::string>() == ill_formed_cases()[0].bytes);
|
||||
const auto bjdata_bytes = json::to_bjdata(jval, false, false, json::bjdata_version_t::draft2, eh::keep);
|
||||
CHECK(json::from_bjdata(bjdata_bytes).get<std::string>() == ill_formed_cases()[0].bytes);
|
||||
const auto msgpack_bytes = json::to_msgpack(jval);
|
||||
CHECK(json::from_msgpack(msgpack_bytes).get<std::string>() == ill_formed_cases()[0].bytes);
|
||||
json bson_obj;
|
||||
bson_obj["k"] = jval;
|
||||
const auto bson_bytes = json::to_bson(bson_obj, eh::keep);
|
||||
CHECK(json::from_bson(bson_bytes)["k"].get<std::string>() == ill_formed_cases()[0].bytes);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
// __ _____ _____ _____
|
||||
// __| | __| | | | 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"
|
||||
|
||||
// The binary writers check strings and object keys for valid UTF-8 only if
|
||||
// JSON_STRICT_BINARY_UTF8 is enabled (planned to be the default in 4.0.0).
|
||||
// Without it, they write the bytes unchanged, as before version 3.13.0; the
|
||||
// tests for that are next to the other tests of each format.
|
||||
#ifdef JSON_STRICT_BINARY_UTF8
|
||||
#undef JSON_STRICT_BINARY_UTF8
|
||||
#endif
|
||||
|
||||
#define JSON_STRICT_BINARY_UTF8 1
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
using nlohmann::json;
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
TEST_CASE("JSON_STRICT_BINARY_UTF8 (see #5529, #5651)")
|
||||
{
|
||||
SECTION("CBOR")
|
||||
{
|
||||
// a string value with ill-formed UTF-8 is rejected
|
||||
CHECK_THROWS_WITH_AS(json::to_cbor(json("\xFF")), "[json.exception.type_error.316] invalid UTF-8 byte at index 0: 0xFF", json::type_error&);
|
||||
// a truncated multi-byte sequence
|
||||
CHECK_THROWS_WITH_AS(json::to_cbor(json("\xC3")), "[json.exception.type_error.316] invalid UTF-8 byte at index 0: 0xC3", json::type_error&);
|
||||
// an encoded surrogate half (U+D800)
|
||||
CHECK_THROWS_WITH_AS(json::to_cbor(json("\xED\xA0\x80")), "[json.exception.type_error.316] invalid UTF-8 byte at index 0: 0xED", json::type_error&);
|
||||
// an overlong encoding of '.'
|
||||
CHECK_THROWS_WITH_AS(json::to_cbor(json("\xC0\xAF")), "[json.exception.type_error.316] invalid UTF-8 byte at index 0: 0xC0", json::type_error&);
|
||||
|
||||
// an object key with ill-formed UTF-8 is rejected the same way
|
||||
CHECK_THROWS_WITH_AS(json::to_cbor(json{{"\xFF", 1}}), "[json.exception.type_error.316] invalid UTF-8 byte at index 0: 0xFF", json::type_error&);
|
||||
|
||||
// binary values are not text and are unaffected
|
||||
CHECK_NOTHROW(json::to_cbor(json::binary(std::vector<std::uint8_t>({0xFF}))));
|
||||
|
||||
// a value read back from CBOR with ill-formed bytes cannot be written
|
||||
// back either (the reader is lenient regardless of the macro)
|
||||
const json j = json::from_cbor(std::vector<std::uint8_t>({0x62, 0xc0, 0xae}));
|
||||
CHECK_THROWS_WITH_AS(json::to_cbor(j), "[json.exception.type_error.316] invalid UTF-8 byte at index 0: 0xC0", json::type_error&);
|
||||
}
|
||||
|
||||
SECTION("UBJSON")
|
||||
{
|
||||
CHECK_THROWS_WITH_AS(json::to_ubjson(json("\xFF")), "[json.exception.type_error.316] invalid UTF-8 byte at index 0: 0xFF", json::type_error&);
|
||||
// a truncated multi-byte sequence
|
||||
CHECK_THROWS_WITH_AS(json::to_ubjson(json("\xC3")), "[json.exception.type_error.316] invalid UTF-8 byte at index 0: 0xC3", json::type_error&);
|
||||
// an encoded surrogate half (U+D800)
|
||||
CHECK_THROWS_WITH_AS(json::to_ubjson(json("\xED\xA0\x80")), "[json.exception.type_error.316] invalid UTF-8 byte at index 0: 0xED", json::type_error&);
|
||||
// an overlong encoding of '.'
|
||||
CHECK_THROWS_WITH_AS(json::to_ubjson(json("\xC0\xAF")), "[json.exception.type_error.316] invalid UTF-8 byte at index 0: 0xC0", json::type_error&);
|
||||
|
||||
// an object key with ill-formed UTF-8 is rejected the same way
|
||||
CHECK_THROWS_WITH_AS(json::to_ubjson(json{{"\xFF", 1}}), "[json.exception.type_error.316] invalid UTF-8 byte at index 0: 0xFF", json::type_error&);
|
||||
}
|
||||
|
||||
SECTION("BJData")
|
||||
{
|
||||
CHECK_THROWS_WITH_AS(json::to_bjdata(json("\xFF")), "[json.exception.type_error.316] invalid UTF-8 byte at index 0: 0xFF", json::type_error&);
|
||||
// a truncated multi-byte sequence
|
||||
CHECK_THROWS_WITH_AS(json::to_bjdata(json("\xC3")), "[json.exception.type_error.316] invalid UTF-8 byte at index 0: 0xC3", json::type_error&);
|
||||
// an encoded surrogate half (U+D800)
|
||||
CHECK_THROWS_WITH_AS(json::to_bjdata(json("\xED\xA0\x80")), "[json.exception.type_error.316] invalid UTF-8 byte at index 0: 0xED", json::type_error&);
|
||||
// an overlong encoding of '.'
|
||||
CHECK_THROWS_WITH_AS(json::to_bjdata(json("\xC0\xAF")), "[json.exception.type_error.316] invalid UTF-8 byte at index 0: 0xC0", json::type_error&);
|
||||
|
||||
// an object key with ill-formed UTF-8 is rejected the same way
|
||||
CHECK_THROWS_WITH_AS(json::to_bjdata(json{{"\xFF", 1}}), "[json.exception.type_error.316] invalid UTF-8 byte at index 0: 0xFF", json::type_error&);
|
||||
}
|
||||
|
||||
SECTION("BSON")
|
||||
{
|
||||
// to_bson() rejects the same kind of ill-formed string value, before
|
||||
// any bytes reach the output adapter (the BSON document length
|
||||
// prefix must be known up front, so nothing is written incrementally)
|
||||
std::vector<std::uint8_t> out{0x42}; // a sentinel byte the writer must not touch
|
||||
#if JSON_DIAGNOSTICS
|
||||
CHECK_THROWS_WITH_AS(json::to_bson(json {{"s", "\xFF"}}, nlohmann::detail::output_adapter<std::uint8_t>(out)), "[json.exception.type_error.316] (/s) invalid UTF-8 byte at index 0: 0xFF", json::type_error&);
|
||||
#else
|
||||
CHECK_THROWS_WITH_AS(json::to_bson(json {{"s", "\xFF"}}, nlohmann::detail::output_adapter<std::uint8_t>(out)), "[json.exception.type_error.316] invalid UTF-8 byte at index 0: 0xFF", json::type_error&);
|
||||
#endif
|
||||
CHECK(out == std::vector<std::uint8_t> {0x42});
|
||||
|
||||
#if JSON_DIAGNOSTICS
|
||||
CHECK_THROWS_WITH_AS(json::to_bson(json {{"s", "\xFF"}}), "[json.exception.type_error.316] (/s) invalid UTF-8 byte at index 0: 0xFF", json::type_error&);
|
||||
#else
|
||||
CHECK_THROWS_WITH_AS(json::to_bson(json {{"s", "\xFF"}}), "[json.exception.type_error.316] invalid UTF-8 byte at index 0: 0xFF", json::type_error&);
|
||||
#endif
|
||||
// a truncated multi-byte sequence
|
||||
#if JSON_DIAGNOSTICS
|
||||
CHECK_THROWS_WITH_AS(json::to_bson(json {{"s", "\xC3"}}), "[json.exception.type_error.316] (/s) invalid UTF-8 byte at index 0: 0xC3", json::type_error&);
|
||||
#else
|
||||
CHECK_THROWS_WITH_AS(json::to_bson(json {{"s", "\xC3"}}), "[json.exception.type_error.316] invalid UTF-8 byte at index 0: 0xC3", json::type_error&);
|
||||
#endif
|
||||
// an encoded surrogate half (U+D800)
|
||||
#if JSON_DIAGNOSTICS
|
||||
CHECK_THROWS_WITH_AS(json::to_bson(json {{"s", "\xED\xA0\x80"}}), "[json.exception.type_error.316] (/s) invalid UTF-8 byte at index 0: 0xED", json::type_error&);
|
||||
#else
|
||||
CHECK_THROWS_WITH_AS(json::to_bson(json {{"s", "\xED\xA0\x80"}}), "[json.exception.type_error.316] invalid UTF-8 byte at index 0: 0xED", json::type_error&);
|
||||
#endif
|
||||
// an overlong encoding of '.'
|
||||
#if JSON_DIAGNOSTICS
|
||||
CHECK_THROWS_WITH_AS(json::to_bson(json {{"s", "\xC0\xAF"}}), "[json.exception.type_error.316] (/s) invalid UTF-8 byte at index 0: 0xC0", json::type_error&);
|
||||
#else
|
||||
CHECK_THROWS_WITH_AS(json::to_bson(json {{"s", "\xC0\xAF"}}), "[json.exception.type_error.316] invalid UTF-8 byte at index 0: 0xC0", json::type_error&);
|
||||
#endif
|
||||
|
||||
// an object key with ill-formed UTF-8 is rejected as well; unlike
|
||||
// the reader (which never validates element names), the writer
|
||||
// checks both string values and object keys
|
||||
#if JSON_DIAGNOSTICS
|
||||
CHECK_THROWS_WITH_AS(json::to_bson(json {{"\xFF", 1}}), "[json.exception.type_error.316] (/\xFF) invalid UTF-8 byte at index 0: 0xFF", json::type_error&);
|
||||
#else
|
||||
CHECK_THROWS_WITH_AS(json::to_bson(json {{"\xFF", 1}}), "[json.exception.type_error.316] invalid UTF-8 byte at index 0: 0xFF", json::type_error&);
|
||||
#endif
|
||||
}
|
||||
|
||||
SECTION("an explicit error_handler overrides the default")
|
||||
{
|
||||
// the macro only changes the default of the error_handler parameter
|
||||
CHECK(json::to_cbor(json("\xFF"), json::error_handler_t::keep) == std::vector<std::uint8_t>({0x61, 0xff}));
|
||||
CHECK(json::to_ubjson(json("\xFF"), false, false, json::error_handler_t::keep) == std::vector<std::uint8_t>({'S', 'i', 1, 0xff}));
|
||||
CHECK(json::to_bjdata(json("\xFF"), false, false, json::bjdata_version_t::draft2, json::error_handler_t::keep) == std::vector<std::uint8_t>({'S', 'i', 1, 0xff}));
|
||||
CHECK(json::from_bson(json::to_bson(json{{"s", "\xFF"}}, json::error_handler_t::keep)) == json{{"s", "\xFF"}});
|
||||
CHECK(json::to_cbor(json("\xFF"), json::error_handler_t::replace) == std::vector<std::uint8_t>({0x63, 0xef, 0xbf, 0xbd}));
|
||||
}
|
||||
|
||||
SECTION("MessagePack and BON8 are unaffected")
|
||||
{
|
||||
// MessagePack allows any bytes in a str, so to_msgpack() still
|
||||
// defaults to keep (strict only if passed explicitly); BON8 always
|
||||
// checks, because the lead bytes mark where strings end
|
||||
CHECK(json::to_msgpack(json("\xFF")) == std::vector<std::uint8_t>({0xa1, 0xff}));
|
||||
CHECK_THROWS_AS(json::to_msgpack(json("\xFF"), json::error_handler_t::strict), json::type_error&);
|
||||
CHECK_THROWS_AS(json::to_bon8(json("\xFF")), json::type_error&);
|
||||
}
|
||||
}
|
||||
@@ -3906,6 +3906,43 @@ TEST_CASE("Universal Binary JSON Specification Examples 1")
|
||||
CHECK(json::to_bjdata(j) == v);
|
||||
CHECK(json::from_bjdata(v) == j);
|
||||
}
|
||||
|
||||
SECTION("ill-formed UTF-8 (see #5529, #5651)")
|
||||
{
|
||||
// none of the binary format specs requires a decoder to reject
|
||||
// ill-formed UTF-8 in a text string, so a value whose bytes are
|
||||
// not valid UTF-8 (0xC0 0xAE is an overlong encoding of '.')
|
||||
// round-trips byte for byte as a string value; to_bjdata() writes
|
||||
// the bytes unchanged, as before 3.13.0, unless
|
||||
// JSON_STRICT_BINARY_UTF8 is enabled (see
|
||||
// unit-binary_utf8_strict.cpp)
|
||||
const std::vector<uint8_t> v = {'S', 'i', 2, 0xc0, 0xae};
|
||||
json j;
|
||||
CHECK_NOTHROW(j = json::from_bjdata(v));
|
||||
REQUIRE(j.is_string());
|
||||
CHECK(j.get_ref<const json::string_t&>() == std::string("\xc0\xae"));
|
||||
CHECK_THROWS_AS(utils::ignore_return_value(j.dump()), json::type_error&);
|
||||
CHECK(json::from_bjdata(json::to_bjdata(j)) == j);
|
||||
|
||||
// the same bytes as an object key round-trip as well
|
||||
const std::vector<uint8_t> v_key = {'{', 'i', 2, 0xc0, 0xae, 'i', 1, '}'};
|
||||
json j_key;
|
||||
CHECK_NOTHROW(j_key = json::from_bjdata(v_key));
|
||||
REQUIRE(j_key.is_object());
|
||||
CHECK(j_key.contains(std::string("\xc0\xae")));
|
||||
CHECK(json::from_bjdata(json::to_bjdata(j_key)) == j_key);
|
||||
|
||||
CHECK(json::from_bjdata(json::to_bjdata(json("\xFF"))) == json("\xFF"));
|
||||
// a truncated multi-byte sequence
|
||||
CHECK(json::from_bjdata(json::to_bjdata(json("\xC3"))) == json("\xC3"));
|
||||
// an encoded surrogate half (U+D800)
|
||||
CHECK(json::from_bjdata(json::to_bjdata(json("\xED\xA0\x80"))) == json("\xED\xA0\x80"));
|
||||
// an overlong encoding of '.'
|
||||
CHECK(json::from_bjdata(json::to_bjdata(json("\xC0\xAF"))) == json("\xC0\xAF"));
|
||||
|
||||
// an object key with ill-formed UTF-8 is kept the same way
|
||||
CHECK(json::from_bjdata(json::to_bjdata(json{{"\xFF", 1}})) == json{{"\xFF", 1}});
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("Array Type")
|
||||
|
||||
@@ -786,6 +786,7 @@ TEST_CASE("Parse BON8 directly from a file using iterator and sentinel")
|
||||
CHECK((parsed.is_object() || parsed.is_array()));
|
||||
}
|
||||
|
||||
#if !defined(JSON_NOEXCEPTION) // corpus values that do not survive the round trip are skipped by catching the exception
|
||||
TEST_CASE("BON8 round-trip invariants")
|
||||
{
|
||||
// This checks what the parse_bon8_fuzzer driver checks (see
|
||||
@@ -818,6 +819,7 @@ TEST_CASE("BON8 round-trip invariants")
|
||||
CHECK(json::to_bon8(j2) == vec);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
TEST_CASE("BON8 roundtrips" * doctest::skip())
|
||||
{
|
||||
|
||||
@@ -62,6 +62,8 @@ class huge_string_t : public std::string
|
||||
{
|
||||
public:
|
||||
using std::string::string;
|
||||
// inheriting std::string's constructors does not inherit its default constructor
|
||||
huge_string_t() = default;
|
||||
huge_string_t(const std::string& s) : std::string(s) {} // NOLINT(google-explicit-constructor,hicpp-explicit-conversions)
|
||||
|
||||
// returns a copy of @a s whose size() pretends to be huge
|
||||
@@ -154,6 +156,43 @@ TEST_CASE("BSON")
|
||||
#endif
|
||||
}
|
||||
|
||||
SECTION("ill-formed UTF-8 (see #5529, #5651)")
|
||||
{
|
||||
// a BSON document {"s": "\xC0\xAE"} (0xC0 0xAE is an overlong
|
||||
// encoding of '.'); the BSON spec does not require a decoder to
|
||||
// reject ill-formed UTF-8 in a string value, so the reader hands the
|
||||
// bytes back unchanged
|
||||
const std::vector<uint8_t> v =
|
||||
{
|
||||
0x0F, 0x00, 0x00, 0x00, // document length
|
||||
0x02, 's', 0x00, // type 0x02 (string), key "s"
|
||||
0x03, 0x00, 0x00, 0x00, // string length (including null)
|
||||
0xc0, 0xae, 0x00, // string content and its null terminator
|
||||
0x00 // document terminator
|
||||
};
|
||||
json j;
|
||||
CHECK_NOTHROW(j = json::from_bson(v));
|
||||
REQUIRE(j.is_object());
|
||||
REQUIRE(j.contains("s"));
|
||||
CHECK(j["s"].get_ref<const json::string_t&>() == std::string("\xc0\xae"));
|
||||
// dump() still requires valid UTF-8 and throws for such a value
|
||||
CHECK_THROWS_AS(utils::ignore_return_value(j.dump()), json::type_error&);
|
||||
// to_bson() writes the bytes back unchanged, as before 3.13.0,
|
||||
// unless JSON_STRICT_BINARY_UTF8 is enabled (see unit-binary_utf8_strict.cpp)
|
||||
CHECK(json::from_bson(json::to_bson(j)) == j);
|
||||
|
||||
CHECK(json::from_bson(json::to_bson(json{{"s", "\xFF"}})) == json{{"s", "\xFF"}});
|
||||
// a truncated multi-byte sequence
|
||||
CHECK(json::from_bson(json::to_bson(json{{"s", "\xC3"}})) == json{{"s", "\xC3"}});
|
||||
// an encoded surrogate half (U+D800)
|
||||
CHECK(json::from_bson(json::to_bson(json{{"s", "\xED\xA0\x80"}})) == json{{"s", "\xED\xA0\x80"}});
|
||||
// an overlong encoding of '.'
|
||||
CHECK(json::from_bson(json::to_bson(json{{"s", "\xC0\xAF"}})) == json{{"s", "\xC0\xAF"}});
|
||||
|
||||
// an object key with ill-formed UTF-8 is kept as well
|
||||
CHECK(json::from_bson(json::to_bson(json{{"\xFF", 1}})) == json{{"\xFF", 1}});
|
||||
}
|
||||
|
||||
SECTION("lengths exceeding INT32_MAX cannot be serialized to BSON")
|
||||
{
|
||||
// out_of_range.412 is thrown from a single shared helper
|
||||
|
||||
+69
-18
@@ -1801,19 +1801,41 @@ TEST_CASE("CBOR")
|
||||
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0xA1, 0x7C, 0x01})), "[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing CBOR string: expected length specification (0x60-0x7B) or indefinite string type (0x7F); last byte: 0x7C", json::parse_error&);
|
||||
}
|
||||
|
||||
SECTION("invalid UTF-8 in string (see #5529)")
|
||||
SECTION("ill-formed UTF-8 in string (see #5529, #5651)")
|
||||
{
|
||||
// RFC 8949 §3.1 leaves it up to the decoder whether to reject
|
||||
// ill-formed UTF-8 in a text string; this library does not, and
|
||||
// hands the original bytes back unchanged, matching the
|
||||
// MessagePack reader and the behavior before #5185/#5531 (not in
|
||||
// any release)
|
||||
|
||||
// a two-character text string (major type 3) whose bytes are not
|
||||
// valid UTF-8 (0xC0 0xAE is an overlong encoding of '.') must be
|
||||
// rejected at decode time, matching every other kind of
|
||||
// malformed binary input, rather than only failing later when
|
||||
// the resulting value is dumped
|
||||
json _;
|
||||
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0x62, 0xc0, 0xae})), "[json.exception.parse_error.113] parse error at byte 3: syntax error while parsing CBOR string: invalid string: ill-formed UTF-8 byte", json::parse_error&);
|
||||
CHECK(json::from_cbor(std::vector<uint8_t>({0x62, 0xc0, 0xae}), true, false).is_discarded());
|
||||
// valid UTF-8 (0xC0 0xAE is an overlong encoding of '.') round-trips
|
||||
// byte for byte as a string value
|
||||
const std::vector<uint8_t> ill_formed_value = {0x62, 0xc0, 0xae};
|
||||
json j_value;
|
||||
CHECK_NOTHROW(j_value = json::from_cbor(ill_formed_value));
|
||||
REQUIRE(j_value.is_string());
|
||||
CHECK(j_value.get_ref<const json::string_t&>() == std::string("\xc0\xae"));
|
||||
// dump() still requires valid UTF-8 and throws for such a value,
|
||||
// unless an error handler that replaces or ignores the bytes is
|
||||
// passed
|
||||
CHECK_THROWS_AS(utils::ignore_return_value(j_value.dump()), json::type_error&);
|
||||
// to_cbor() writes the bytes back unchanged, as before 3.13.0,
|
||||
// unless JSON_STRICT_BINARY_UTF8 is enabled (see unit-binary_utf8_strict.cpp)
|
||||
CHECK(json::from_cbor(json::to_cbor(j_value)) == j_value);
|
||||
|
||||
// the same bytes as an object key round-trip as well
|
||||
const std::vector<uint8_t> ill_formed_key = {0xa1, 0x62, 0xc0, 0xae, 0x01};
|
||||
json j_key;
|
||||
CHECK_NOTHROW(j_key = json::from_cbor(ill_formed_key));
|
||||
REQUIRE(j_key.is_object());
|
||||
CHECK(j_key.contains(std::string("\xc0\xae")));
|
||||
CHECK(json::from_cbor(json::to_cbor(j_key)) == j_key);
|
||||
|
||||
// a CBOR byte string (major type 2) with the very same bytes is
|
||||
// NOT text and must still be accepted as-is
|
||||
json _;
|
||||
CHECK_NOTHROW(_ = json::from_cbor(std::vector<uint8_t>({0x42, 0xc0, 0xae})));
|
||||
CHECK(_ == json::binary(std::vector<std::uint8_t>({0xc0, 0xae})));
|
||||
|
||||
@@ -1822,17 +1844,47 @@ TEST_CASE("CBOR")
|
||||
CHECK(json::from_cbor(json::to_cbor(j)) == j);
|
||||
}
|
||||
|
||||
SECTION("invalid UTF-8 in indefinite-length string")
|
||||
SECTION("to_cbor keeps ill-formed UTF-8 (see #5651)")
|
||||
{
|
||||
// to_cbor() writes the bytes unchanged, as before 3.13.0, unless
|
||||
// JSON_STRICT_BINARY_UTF8 is enabled (see
|
||||
// unit-binary_utf8_strict.cpp); from_cbor() reads them back as is
|
||||
CHECK(json::from_cbor(json::to_cbor(json("\xFF"))) == json("\xFF"));
|
||||
// a truncated multi-byte sequence
|
||||
CHECK(json::from_cbor(json::to_cbor(json("\xC3"))) == json("\xC3"));
|
||||
// an encoded surrogate half (U+D800)
|
||||
CHECK(json::from_cbor(json::to_cbor(json("\xED\xA0\x80"))) == json("\xED\xA0\x80"));
|
||||
// an overlong encoding of '.'
|
||||
CHECK(json::from_cbor(json::to_cbor(json("\xC0\xAF"))) == json("\xC0\xAF"));
|
||||
|
||||
// an object key with ill-formed UTF-8 is kept the same way
|
||||
CHECK(json::from_cbor(json::to_cbor(json{{"\xFF", 1}})) == json{{"\xFF", 1}});
|
||||
|
||||
// binary values are not text and are unaffected
|
||||
CHECK_NOTHROW(json::to_cbor(json::binary(std::vector<std::uint8_t>({0xFF}))));
|
||||
}
|
||||
|
||||
SECTION("ill-formed UTF-8 in indefinite-length string")
|
||||
{
|
||||
json _;
|
||||
|
||||
// every chunk must be valid UTF-8 on its own (RFC 8949, Section
|
||||
// 3.2.3), so a code point split across two chunks is rejected
|
||||
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0x7f, 0x61, 0xc3, 0x61, 0xa9, 0xff})), "[json.exception.parse_error.113] parse error at byte 3: syntax error while parsing CBOR string: invalid string: ill-formed UTF-8 byte", json::parse_error&);
|
||||
CHECK(json::from_cbor(std::vector<uint8_t>({0x7f, 0x61, 0xc3, 0x61, 0xa9, 0xff}), true, false).is_discarded());
|
||||
// the chunks are concatenated as is, without checking that each
|
||||
// chunk is valid UTF-8 on its own (RFC 8949, Section 3.2.3), so
|
||||
// a code point split across two chunks yields a valid string
|
||||
CHECK_NOTHROW(_ = json::from_cbor(std::vector<uint8_t>({0x7f, 0x61, 0xc3, 0x61, 0xa9, 0xff})));
|
||||
CHECK(_ == "\xc3\xa9");
|
||||
CHECK(_.dump() == "\"\xc3\xa9\"");
|
||||
|
||||
// an ill-formed later chunk is rejected after valid ones
|
||||
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0x7f, 0x62, 0xc3, 0xa9, 0x62, 0xc0, 0xae, 0xff})), "[json.exception.parse_error.113] parse error at byte 7: syntax error while parsing CBOR string: invalid string: ill-formed UTF-8 byte", json::parse_error&);
|
||||
// a truncated code point is kept as is
|
||||
CHECK_NOTHROW(_ = json::from_cbor(std::vector<uint8_t>({0x7f, 0x61, 0xc3, 0xff})));
|
||||
CHECK(_ == "\xc3");
|
||||
CHECK_THROWS_AS(utils::ignore_return_value(_.dump()), json::type_error&);
|
||||
CHECK(json::from_cbor(json::to_cbor(_)) == _);
|
||||
|
||||
// an ill-formed later chunk is kept after valid ones
|
||||
CHECK_NOTHROW(_ = json::from_cbor(std::vector<uint8_t>({0x7f, 0x62, 0xc3, 0xa9, 0x62, 0xc0, 0xae, 0xff})));
|
||||
CHECK(_ == "\xc3\xa9\xc0\xae");
|
||||
CHECK_THROWS_AS(utils::ignore_return_value(_.dump()), json::type_error&);
|
||||
|
||||
// valid multi-byte chunks are accepted
|
||||
CHECK(json::from_cbor(std::vector<uint8_t>({0x7f, 0x62, 0xc3, 0xa9, 0x62, 0xc3, 0xb6, 0xff})) == "\xc3\xa9\xc3\xb6");
|
||||
@@ -1840,9 +1892,6 @@ TEST_CASE("CBOR")
|
||||
|
||||
SECTION("many chunks in indefinite-length string")
|
||||
{
|
||||
// only the newly read chunk is validated, not the whole string
|
||||
// collected so far; validating the latter made this input take
|
||||
// quadratic time (about ten seconds for 100000 chunks)
|
||||
constexpr std::size_t chunks = 100000;
|
||||
std::vector<uint8_t> v{0x7f};
|
||||
for (std::size_t i = 0; i < chunks; ++i)
|
||||
@@ -2341,6 +2390,7 @@ TEST_CASE("issue #5405 - array reserve for definite-length CBOR arrays")
|
||||
}
|
||||
}
|
||||
|
||||
#if !defined(JSON_NOEXCEPTION) // corpus values that do not survive the round trip are skipped by catching the exception
|
||||
TEST_CASE("CBOR round-trip invariants")
|
||||
{
|
||||
// This checks what the parse_cbor_fuzzer driver checks (see
|
||||
@@ -2373,6 +2423,7 @@ TEST_CASE("CBOR round-trip invariants")
|
||||
CHECK(json::to_cbor(j2) == vec);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
TEST_CASE("CBOR roundtrips" * doctest::skip())
|
||||
{
|
||||
|
||||
@@ -592,6 +592,45 @@ TEST_CASE("parser class")
|
||||
|
||||
// parsing from a string literal is unaffected either way
|
||||
CHECK(json::parse("123") == json(123));
|
||||
|
||||
// a NUL byte that ends a // comment ends the input just
|
||||
// like a NUL byte anywhere else (issue #5659); before the
|
||||
// fix, the NUL was consumed as part of the comment, and
|
||||
// scanning continued with whatever followed it
|
||||
{
|
||||
// same as "//c" alone (real end of input after the
|
||||
// comment), rather than continuing with "[1]"
|
||||
std::string s1 = "//c";
|
||||
s1.push_back('\0');
|
||||
s1 += "[1]";
|
||||
json _; // NOLINT(readability-identifier-naming)
|
||||
CHECK_THROWS_WITH_AS(_ = json::parse(s1, nullptr, true, true),
|
||||
"[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&);
|
||||
CHECK_FALSE(json::accept(s1, true, true));
|
||||
}
|
||||
|
||||
{
|
||||
// same as "[1, //c" alone, rather than continuing with " 2]"
|
||||
std::string s2 = "[1, //c";
|
||||
s2.push_back('\0');
|
||||
s2 += " 2]";
|
||||
json _; // NOLINT(readability-identifier-naming)
|
||||
CHECK_THROWS_WITH_AS(_ = json::parse(s2, nullptr, true, true),
|
||||
"[json.exception.parse_error.101] parse error at line 1, column 8: syntax error while parsing value - unexpected end of input; expected '[', '{', or a literal",
|
||||
json::parse_error&);
|
||||
CHECK_FALSE(json::accept(s2, true, true));
|
||||
}
|
||||
|
||||
{
|
||||
// same as "1 //c" alone: the comment (and the NUL that
|
||||
// ends it) is ignored, and "x" is never reached
|
||||
std::string s3 = "1 //c";
|
||||
s3.push_back('\0');
|
||||
s3 += "x";
|
||||
CHECK(json::parse(s3, nullptr, true, true) == json(1));
|
||||
CHECK(json::accept(s3, true, true));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -2645,12 +2684,10 @@ TEST_CASE("diagnostic positions: value lifetime, input adapters, and SAX")
|
||||
|
||||
SECTION("move constructor resets the moved-from value to npos")
|
||||
{
|
||||
// basic_json(basic_json&&) (json.hpp, around line 1951) copies
|
||||
// basic_json(basic_json&&) copies
|
||||
// other's start_position/end_position into *this and then resets
|
||||
// other's to npos (see the cppcheck-suppress[accessForwarded]
|
||||
// annotation there, which flags this reset as worth a second
|
||||
// look). Only the top-level moved-from value is affected; its
|
||||
// (moved-away) children are gone along with it.
|
||||
// other's to npos. Only the top-level moved-from value is
|
||||
// affected; its (moved-away) children are gone along with it.
|
||||
const std::string s = R"({"a":1,"b":[1,2,3]})";
|
||||
json a = json::parse(s);
|
||||
const auto a_start = a.start_pos();
|
||||
@@ -2667,9 +2704,9 @@ TEST_CASE("diagnostic positions: value lifetime, input adapters, and SAX")
|
||||
CHECK(b["b"].end_pos() == nested_end);
|
||||
|
||||
// the moved-from value is reset to a null and reports npos
|
||||
CHECK(a.is_null()); // NOLINT(bugprone-use-after-move,clang-analyzer-cplusplus.Move)
|
||||
CHECK(a.start_pos() == std::string::npos); // NOLINT(bugprone-use-after-move,clang-analyzer-cplusplus.Move)
|
||||
CHECK(a.end_pos() == std::string::npos); // NOLINT(bugprone-use-after-move,clang-analyzer-cplusplus.Move)
|
||||
CHECK(a.is_null()); // NOLINT(bugprone-use-after-move,hicpp-invalid-access-moved,clang-analyzer-cplusplus.Move)
|
||||
CHECK(a.start_pos() == std::string::npos); // NOLINT(bugprone-use-after-move,hicpp-invalid-access-moved,clang-analyzer-cplusplus.Move)
|
||||
CHECK(a.end_pos() == std::string::npos); // NOLINT(bugprone-use-after-move,hicpp-invalid-access-moved,clang-analyzer-cplusplus.Move)
|
||||
}
|
||||
|
||||
SECTION("swap() exchanges positions along with values")
|
||||
|
||||
@@ -750,6 +750,28 @@ TEST_CASE("regression #3868 - heterogeneous comparisons compile under C++20 (P24
|
||||
CHECK_FALSE(j != i);
|
||||
}
|
||||
}
|
||||
|
||||
#if JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON
|
||||
TEST_CASE("regression #5665 - scalar <= discarded and scalar >= discarded in C++20 legacy mode")
|
||||
{
|
||||
// Issue #5665: with a scalar on the left-hand side, <= and >= only had the
|
||||
// candidate rewritten from operator<=>, which does not emulate the legacy
|
||||
// discarded-value behavior. Check that scalar-on-the-left now matches the
|
||||
// other three operand orders.
|
||||
const json discarded(json::value_t::discarded);
|
||||
const json one = 1;
|
||||
|
||||
CHECK(discarded <= 1);
|
||||
CHECK(discarded >= 1);
|
||||
CHECK(one <= discarded);
|
||||
CHECK(one >= discarded);
|
||||
CHECK(1 <= discarded);
|
||||
CHECK(1 >= discarded);
|
||||
CHECK(1.5 <= discarded);
|
||||
CHECK(1.5 >= discarded);
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
namespace
|
||||
@@ -827,6 +849,46 @@ Json nest(Json j, const std::size_t depth)
|
||||
return j;
|
||||
}
|
||||
|
||||
// a std::map comparator with state: case-insensitive, unless constructed
|
||||
// case-sensitive. Used to check that copying an object copies the original's
|
||||
// comparator rather than default-constructing a new one (see #5649).
|
||||
struct key_case_less
|
||||
{
|
||||
key_case_less() = default;
|
||||
explicit key_case_less(const bool cs) noexcept : case_sensitive(cs) {}
|
||||
|
||||
bool operator()(const std::string& a, const std::string& b) const
|
||||
{
|
||||
if (case_sensitive)
|
||||
{
|
||||
return a < b;
|
||||
}
|
||||
return std::lexicographical_compare(a.begin(), a.end(), b.begin(), b.end(),
|
||||
[](unsigned char x, unsigned char y)
|
||||
{
|
||||
return std::tolower(x) < std::tolower(y);
|
||||
});
|
||||
}
|
||||
|
||||
bool case_sensitive = false;
|
||||
};
|
||||
|
||||
template<class Key, class Value, class /*Compare*/, class Allocator>
|
||||
using key_case_map = std::map<Key, Value, key_case_less, Allocator>;
|
||||
using key_case_json = nlohmann::basic_json<key_case_map>;
|
||||
|
||||
// the innermost value of a chain of single-element arrays
|
||||
template<typename Json>
|
||||
const Json& innermost(const Json& j)
|
||||
{
|
||||
const Json* p = &j;
|
||||
while (p->is_array())
|
||||
{
|
||||
p = &(*p)[0];
|
||||
}
|
||||
return *p;
|
||||
}
|
||||
|
||||
// orders keys case-insensitively, so "key" and "KEY" compare equivalent
|
||||
// (neither less than the other) although they are not equal
|
||||
struct case_insensitive_less
|
||||
@@ -891,6 +953,47 @@ TEST_CASE("equality of objects whose entries have no fixed order")
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("copying an object preserves its comparator's state")
|
||||
{
|
||||
// Past the iterative deep copy's nesting bound, an object copy used to be
|
||||
// built with a default-constructed comparator instead of a copy of the
|
||||
// original's. For an object type whose comparator carries state - here, a
|
||||
// std::map that compares keys case-sensitively only when created that way
|
||||
// - this reordered the copy's keys and could even drop entries that the
|
||||
// original's comparator kept distinct (see #5649).
|
||||
key_case_json object = key_case_json::object_t(key_case_less(true)); // case-sensitive
|
||||
object["b"] = 1;
|
||||
object["B"] = 2;
|
||||
object["a"] = 3;
|
||||
REQUIRE(object.dump() == R"({"B":2,"a":3,"b":1})");
|
||||
|
||||
for (const std::size_t depth : std::vector<std::size_t> {0, 127, 128, 200})
|
||||
{
|
||||
CAPTURE(depth)
|
||||
|
||||
key_case_json original = object;
|
||||
for (std::size_t i = 0; i < depth; ++i)
|
||||
{
|
||||
original = key_case_json::array({std::move(original)});
|
||||
}
|
||||
|
||||
{
|
||||
const key_case_json copy = original; // NOLINT(performance-unnecessary-copy-initialization)
|
||||
CHECK(innermost(copy).size() == 3);
|
||||
CHECK(innermost(copy).dump() == R"({"B":2,"a":3,"b":1})");
|
||||
CHECK(copy == original);
|
||||
}
|
||||
|
||||
{
|
||||
key_case_json copy = key_case_json::array();
|
||||
copy = original;
|
||||
CHECK(innermost(copy).size() == 3);
|
||||
CHECK(innermost(copy).dump() == R"({"B":2,"a":3,"b":1})");
|
||||
CHECK(copy == original);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("equality of an object whose comparator treats different keys as equivalent")
|
||||
{
|
||||
// https://github.com/nlohmann/json/issues/5655: past the nesting bound,
|
||||
@@ -998,3 +1101,51 @@ TEST_CASE("containers are compared element by element")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if JSON_HAS_THREE_WAY_COMPARISON
|
||||
// JSON_HAS_CPP_20 (do not remove; see note at top of file)
|
||||
TEST_CASE("operator<=> of binary values with a different subtype does not depend on nesting depth")
|
||||
{
|
||||
// #5654: std::vector<std::uint8_t>::operator<=>, which the binary type's
|
||||
// own operator<=> uses, ignores the subtype that operator== checks. So a
|
||||
// pair of binary values with the same bytes but a different subtype is
|
||||
// unequal, yet <=>-equivalent - the same inconsistency between == and <=>
|
||||
// that a NaN has. Within the nesting bound, an array compares itself
|
||||
// with std::vector's own operator<=>, which treats an equivalent pair as
|
||||
// undecided and lets the next element decide, same as
|
||||
// std::lexicographical_compare_three_way does. Past the bound,
|
||||
// compare_iteratively<true>() takes over and must classify the pair the
|
||||
// same way, or the result of operator<=> - and of <, which C++20 derives
|
||||
// from it - depends on how deeply the values are nested.
|
||||
const json a = json::array({json::binary({1}, 1), 1});
|
||||
const json b = json::array({json::binary({1}, 2), 2});
|
||||
|
||||
// the root inconsistency: unequal, yet <=>-equivalent
|
||||
CHECK_FALSE(a[0] == b[0]);
|
||||
CHECK((a[0] <=> b[0]) == std::partial_ordering::equivalent); // *NOPAD*
|
||||
|
||||
const auto deep = [](const json & j, const std::size_t depth)
|
||||
{
|
||||
json result = j;
|
||||
for (std::size_t i = 0; i < depth; ++i)
|
||||
{
|
||||
result = json::array({std::move(result)});
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
// 127 levels stay within nesting_depth_limit() (128); 128 and 200 do not,
|
||||
// and must still agree with the levels that do
|
||||
for (const std::size_t depth : std::vector<std::size_t> {0, 127, 128, 200})
|
||||
{
|
||||
CAPTURE(depth)
|
||||
const json x = deep(a, depth);
|
||||
const json y = deep(b, depth);
|
||||
CHECK((x <=> y) == std::partial_ordering::less); // *NOPAD*
|
||||
CHECK((y <=> x) == std::partial_ordering::greater); // *NOPAD*
|
||||
CHECK(x < y);
|
||||
CHECK(y > x);
|
||||
CHECK_FALSE(y < x);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -15,11 +15,6 @@
|
||||
|
||||
#include "doctest_compatibility.h"
|
||||
|
||||
// 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;
|
||||
@@ -430,6 +425,37 @@ TEST_CASE("value conversion")
|
||||
CHECK(std::equal(std::begin(nbs[0][0][0]), std::end(nbs[1][1][1]), std::begin(nbs2[0][0][0])));
|
||||
}
|
||||
|
||||
SECTION("built-in arrays: 5D")
|
||||
{
|
||||
// NOLINTBEGIN(misc-const-correctness,cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)
|
||||
const int nbs[1][1][1][2][2] = {{{{{0, 1}, {2, 3}}}}};
|
||||
int nbs2[1][1][1][2][2] = {{{{{0, 0}, {0, 0}}}}};
|
||||
// NOLINTEND(misc-const-correctness,cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)
|
||||
|
||||
const json j2 = nbs;
|
||||
j2.get_to(nbs2);
|
||||
CHECK(std::equal(std::begin(nbs[0][0][0][0]), std::end(nbs[0][0][0][1]), std::begin(nbs2[0][0][0][0])));
|
||||
}
|
||||
|
||||
SECTION("built-in arrays: mismatched shape")
|
||||
{
|
||||
// NOLINTBEGIN(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)
|
||||
int nbs2[2][3] = {{0, 0, 0}, {0, 0, 0}};
|
||||
// NOLINTEND(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)
|
||||
|
||||
SECTION("not an array")
|
||||
{
|
||||
const json j2 = 42;
|
||||
CHECK_THROWS_WITH_AS(j2.get_to(nbs2), "[json.exception.type_error.304] cannot use at() with number", json::type_error&);
|
||||
}
|
||||
|
||||
SECTION("too few elements")
|
||||
{
|
||||
const json j2 = {{0, 1, 2}};
|
||||
CHECK_THROWS_WITH_AS(j2.get_to(nbs2), "[json.exception.out_of_range.401] array index 1 is out of range", json::out_of_range&);
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("std::deque<json>")
|
||||
{
|
||||
std::deque<json> a{"previous", "value"};
|
||||
@@ -1228,759 +1254,8 @@ TEST_CASE("value conversion")
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
SECTION("get a binary value (explicit)")
|
||||
{
|
||||
json::binary_t const n_reference{{1, 2, 3}};
|
||||
json j(n_reference);
|
||||
|
||||
SECTION("binary_t")
|
||||
{
|
||||
json::binary_t const b = j.get<json::binary_t>();
|
||||
CHECK(*json(b).m_data.m_value.binary == *j.m_data.m_value.binary);
|
||||
}
|
||||
|
||||
SECTION("get_binary()")
|
||||
{
|
||||
SECTION("non-const")
|
||||
{
|
||||
auto& b = j.get_binary();
|
||||
CHECK(*json(b).m_data.m_value.binary == *j.m_data.m_value.binary);
|
||||
}
|
||||
|
||||
SECTION("non-const")
|
||||
{
|
||||
const json j_const = j; // NOLINT(performance-unnecessary-copy-initialization)
|
||||
const auto& b = j_const.get_binary();
|
||||
CHECK(*json(b).m_data.m_value.binary == *j.m_data.m_value.binary);
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("exception in case of a non-string type")
|
||||
{
|
||||
json j_null(json::value_t::null);
|
||||
json j_object(json::value_t::object);
|
||||
json j_array(json::value_t::array);
|
||||
json j_string(json::value_t::string);
|
||||
json j_boolean(json::value_t::boolean);
|
||||
const json j_null_const(json::value_t::null);
|
||||
const json j_object_const(json::value_t::object);
|
||||
const json j_array_const(json::value_t::array);
|
||||
const json j_string_const(json::value_t::string);
|
||||
const json j_boolean_const(json::value_t::boolean);
|
||||
|
||||
CHECK_THROWS_WITH_AS(j_null.get<json::binary_t>(),
|
||||
"[json.exception.type_error.302] type must be binary, but is null",
|
||||
json::type_error&);
|
||||
CHECK_THROWS_WITH_AS(j_object.get<json::binary_t>(),
|
||||
"[json.exception.type_error.302] type must be binary, but is object",
|
||||
json::type_error&);
|
||||
CHECK_THROWS_WITH_AS(j_array.get<json::binary_t>(),
|
||||
"[json.exception.type_error.302] type must be binary, but is array",
|
||||
json::type_error&);
|
||||
CHECK_THROWS_WITH_AS(j_string.get<json::binary_t>(),
|
||||
"[json.exception.type_error.302] type must be binary, but is string",
|
||||
json::type_error&);
|
||||
CHECK_THROWS_WITH_AS(j_boolean.get<json::binary_t>(),
|
||||
"[json.exception.type_error.302] type must be binary, but is boolean",
|
||||
json::type_error&);
|
||||
|
||||
CHECK_THROWS_WITH_AS(j_null_const.get<json::binary_t>(),
|
||||
"[json.exception.type_error.302] type must be binary, but is null",
|
||||
json::type_error&);
|
||||
CHECK_THROWS_WITH_AS(j_object_const.get<json::binary_t>(),
|
||||
"[json.exception.type_error.302] type must be binary, but is object",
|
||||
json::type_error&);
|
||||
CHECK_THROWS_WITH_AS(j_array_const.get<json::binary_t>(),
|
||||
"[json.exception.type_error.302] type must be binary, but is array",
|
||||
json::type_error&);
|
||||
CHECK_THROWS_WITH_AS(j_string_const.get<json::binary_t>(),
|
||||
"[json.exception.type_error.302] type must be binary, but is string",
|
||||
json::type_error&);
|
||||
CHECK_THROWS_WITH_AS(j_boolean_const.get<json::binary_t>(),
|
||||
"[json.exception.type_error.302] type must be binary, but is boolean",
|
||||
json::type_error&);
|
||||
|
||||
CHECK_THROWS_WITH_AS(j_null.get_binary(),
|
||||
"[json.exception.type_error.302] type must be binary, but is null",
|
||||
json::type_error&);
|
||||
CHECK_THROWS_WITH_AS(j_object.get_binary(),
|
||||
"[json.exception.type_error.302] type must be binary, but is object",
|
||||
json::type_error&);
|
||||
CHECK_THROWS_WITH_AS(j_array.get_binary(),
|
||||
"[json.exception.type_error.302] type must be binary, but is array",
|
||||
json::type_error&);
|
||||
CHECK_THROWS_WITH_AS(j_string.get_binary(),
|
||||
"[json.exception.type_error.302] type must be binary, but is string",
|
||||
json::type_error&);
|
||||
CHECK_THROWS_WITH_AS(j_boolean.get_binary(),
|
||||
"[json.exception.type_error.302] type must be binary, but is boolean",
|
||||
json::type_error&);
|
||||
|
||||
CHECK_THROWS_WITH_AS(j_null_const.get_binary(),
|
||||
"[json.exception.type_error.302] type must be binary, but is null",
|
||||
json::type_error&);
|
||||
CHECK_THROWS_WITH_AS(j_object_const.get_binary(),
|
||||
"[json.exception.type_error.302] type must be binary, but is object",
|
||||
json::type_error&);
|
||||
CHECK_THROWS_WITH_AS(j_array_const.get_binary(),
|
||||
"[json.exception.type_error.302] type must be binary, but is array",
|
||||
json::type_error&);
|
||||
CHECK_THROWS_WITH_AS(j_string_const.get_binary(),
|
||||
"[json.exception.type_error.302] type must be binary, but is string",
|
||||
json::type_error&);
|
||||
CHECK_THROWS_WITH_AS(j_boolean_const.get_binary(),
|
||||
"[json.exception.type_error.302] type must be binary, but is boolean",
|
||||
json::type_error&);
|
||||
}
|
||||
}
|
||||
|
||||
#if JSON_USE_IMPLICIT_CONVERSIONS
|
||||
SECTION("get a binary value (implicit)")
|
||||
{
|
||||
json::binary_t const n_reference{{1, 2, 3}};
|
||||
json const j(n_reference);
|
||||
|
||||
SECTION("binary_t")
|
||||
{
|
||||
json::binary_t const b = j;
|
||||
CHECK(*json(b).m_data.m_value.binary == *j.m_data.m_value.binary);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifndef SKIP_TESTS_FOR_ENUM_SERIALIZATION
|
||||
SECTION("get an enum")
|
||||
{
|
||||
enum c_enum { value_1, value_2 }; // NOLINT(cppcoreguidelines-use-enum-class)
|
||||
enum class cpp_enum { value_1, value_2 };
|
||||
|
||||
CHECK(json(value_1).get<c_enum>() == value_1);
|
||||
CHECK(json(cpp_enum::value_1).get<cpp_enum>() == cpp_enum::value_1);
|
||||
}
|
||||
|
||||
SECTION("get an enum with underlying type bool (#5671)")
|
||||
{
|
||||
enum class bool_enum : bool { off, on };
|
||||
|
||||
CHECK(json(bool_enum::off).get<bool_enum>() == bool_enum::off);
|
||||
CHECK(json(bool_enum::on).get<bool_enum>() == bool_enum::on);
|
||||
}
|
||||
#endif
|
||||
|
||||
SECTION("more involved conversions")
|
||||
{
|
||||
SECTION("object-like STL containers")
|
||||
{
|
||||
json const j1 = {{"one", 1}, {"two", 2}, {"three", 3}};
|
||||
json const j2 = {{"one", 1u}, {"two", 2u}, {"three", 3u}};
|
||||
json const j3 = {{"one", 1.1}, {"two", 2.2}, {"three", 3.3}};
|
||||
json const j4 = {{"one", true}, {"two", false}, {"three", true}};
|
||||
json const j5 = {{"one", "eins"}, {"two", "zwei"}, {"three", "drei"}};
|
||||
|
||||
SECTION("std::map")
|
||||
{
|
||||
CHECK(j1.get<std::map<std::string, int>>() == (std::map<std::string, int> {{"one", 1}, {"two", 2}, {"three", 3}}));
|
||||
CHECK(j2.get<std::map<std::string, unsigned int>>() == (std::map<std::string, unsigned int> {{"one", 1u}, {"two", 2u}, {"three", 3u}}));
|
||||
CHECK(j3.get<std::map<std::string, double>>() == (std::map<std::string, double> {{"one", 1.1}, {"two", 2.2}, {"three", 3.3}}));
|
||||
CHECK(j4.get<std::map<std::string, bool>>() == (std::map<std::string, bool> {{"one", true}, {"two", false}, {"three", true}}));
|
||||
CHECK(j5.get<std::map<std::string, std::string>>() == (std::map<std::string, std::string> {{"one", "eins"}, {"two", "zwei"}, {"three", "drei"}}));
|
||||
}
|
||||
|
||||
SECTION("std::unordered_map")
|
||||
{
|
||||
CHECK(j1.get<std::unordered_map<std::string, int>>() == (std::unordered_map<std::string, int> {{"one", 1}, {"two", 2}, {"three", 3}}));
|
||||
CHECK(j2.get<std::unordered_map<std::string, unsigned int>>() == (std::unordered_map<std::string, unsigned int> {{"one", 1u}, {"two", 2u}, {"three", 3u}}));
|
||||
CHECK(j3.get<std::unordered_map<std::string, double>>() == (std::unordered_map<std::string, double> {{"one", 1.1}, {"two", 2.2}, {"three", 3.3}}));
|
||||
CHECK(j4.get<std::unordered_map<std::string, bool>>() == (std::unordered_map<std::string, bool> {{"one", true}, {"two", false}, {"three", true}}));
|
||||
const auto m5 = j5.get<std::unordered_map<std::string, std::string>>();
|
||||
CHECK(m5 == (std::unordered_map<std::string, std::string> {{"one", "eins"}, {"two", "zwei"}, {"three", "drei"}}));
|
||||
CHECK(m5.at("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")
|
||||
{
|
||||
CHECK(j1.get<std::multimap<std::string, int>>() == (std::multimap<std::string, int> {{"one", 1}, {"two", 2}, {"three", 3}}));
|
||||
CHECK(j2.get<std::multimap<std::string, unsigned int>>() == (std::multimap<std::string, unsigned int> {{"one", 1u}, {"two", 2u}, {"three", 3u}}));
|
||||
CHECK(j3.get<std::multimap<std::string, double>>() == (std::multimap<std::string, double> {{"one", 1.1}, {"two", 2.2}, {"three", 3.3}}));
|
||||
CHECK(j4.get<std::multimap<std::string, bool>>() == (std::multimap<std::string, bool> {{"one", true}, {"two", false}, {"three", true}}));
|
||||
const auto m5 = j5.get<std::multimap<std::string, std::string>>();
|
||||
CHECK(m5 == (std::multimap<std::string, std::string> {{"one", "eins"}, {"two", "zwei"}, {"three", "drei"}}));
|
||||
CHECK(m5.find("one")->second == "eins");
|
||||
}
|
||||
|
||||
SECTION("std::unordered_multimap")
|
||||
{
|
||||
CHECK(j1.get<std::unordered_multimap<std::string, int>>() == (std::unordered_multimap<std::string, int> {{"one", 1}, {"two", 2}, {"three", 3}}));
|
||||
CHECK(j2.get<std::unordered_multimap<std::string, unsigned int>>() == (std::unordered_multimap<std::string, unsigned int> {{"one", 1u}, {"two", 2u}, {"three", 3u}}));
|
||||
CHECK(j3.get<std::unordered_multimap<std::string, double>>() == (std::unordered_multimap<std::string, double> {{"one", 1.1}, {"two", 2.2}, {"three", 3.3}}));
|
||||
CHECK(j4.get<std::unordered_multimap<std::string, bool>>() == (std::unordered_multimap<std::string, bool> {{"one", true}, {"two", false}, {"three", true}}));
|
||||
const auto m5 = j5.get<std::unordered_multimap<std::string, std::string>>();
|
||||
CHECK(m5 == (std::unordered_multimap<std::string, std::string> {{"one", "eins"}, {"two", "zwei"}, {"three", "drei"}}));
|
||||
CHECK(m5.find("one")->second == "eins");
|
||||
}
|
||||
|
||||
SECTION("exception in case of a non-object type")
|
||||
{
|
||||
CHECK_THROWS_WITH_AS(
|
||||
(json().get<std::map<std::string, int>>()),
|
||||
"[json.exception.type_error.302] type must be object, but is null", json::type_error&);
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("array-like STL containers")
|
||||
{
|
||||
json const j1 = {1, 2, 3, 4};
|
||||
json const j2 = {1u, 2u, 3u, 4u};
|
||||
json const j3 = {1.2, 2.3, 3.4, 4.5};
|
||||
json const j4 = {true, false, true};
|
||||
json const j5 = {"one", "two", "three"};
|
||||
|
||||
SECTION("std::list")
|
||||
{
|
||||
CHECK(j1.get<std::list<int>>() == (std::list<int> {1, 2, 3, 4}));
|
||||
CHECK(j2.get<std::list<unsigned int>>() == (std::list<unsigned int> {1u, 2u, 3u, 4u}));
|
||||
CHECK(j3.get<std::list<double>>() == (std::list<double> {1.2, 2.3, 3.4, 4.5}));
|
||||
CHECK(j4.get<std::list<bool>>() == (std::list<bool> {true, false, true}));
|
||||
CHECK(j5.get<std::list<std::string>>() == (std::list<std::string> {"one", "two", "three"}));
|
||||
}
|
||||
|
||||
SECTION("std::forward_list")
|
||||
{
|
||||
CHECK(j1.get<std::forward_list<int>>() == (std::forward_list<int> {1, 2, 3, 4}));
|
||||
CHECK(j2.get<std::forward_list<unsigned int>>() == (std::forward_list<unsigned int> {1u, 2u, 3u, 4u}));
|
||||
CHECK(j3.get<std::forward_list<double>>() == (std::forward_list<double> {1.2, 2.3, 3.4, 4.5}));
|
||||
CHECK(j4.get<std::forward_list<bool>>() == (std::forward_list<bool> {true, false, true}));
|
||||
CHECK(j5.get<std::forward_list<std::string>>() == (std::forward_list<std::string> {"one", "two", "three"}));
|
||||
}
|
||||
|
||||
SECTION("std::array")
|
||||
{
|
||||
CHECK(j1.get<std::array<int, 4>>() == (std::array<int, 4> {{1, 2, 3, 4}}));
|
||||
// only the first 3 elements of j2 are converted, since the target array is smaller
|
||||
CHECK(j2.get<std::array<unsigned int, 3>>() == (std::array<unsigned int, 3> {{1u, 2u, 3u}}));
|
||||
CHECK(j3.get<std::array<double, 4>>() == (std::array<double, 4> {{1.2, 2.3, 3.4, 4.5}}));
|
||||
CHECK(j4.get<std::array<bool, 3>>() == (std::array<bool, 3> {{true, false, true}}));
|
||||
CHECK(j5.get<std::array<std::string, 3>>() == (std::array<std::string, 3> {{"one", "two", "three"}}));
|
||||
|
||||
SECTION("std::array is larger than JSON")
|
||||
{
|
||||
std::array<int, 6> arr6 = {{1, 2, 3, 4, 5, 6}};
|
||||
CHECK_THROWS_WITH_AS(j1.get_to(arr6), "[json.exception.out_of_range.401] "
|
||||
"array index 4 is out of range", json::out_of_range&);
|
||||
}
|
||||
|
||||
SECTION("std::array is smaller than JSON")
|
||||
{
|
||||
std::array<int, 2> arr2 = {{8, 9}};
|
||||
j1.get_to(arr2);
|
||||
CHECK(arr2[0] == 1);
|
||||
CHECK(arr2[1] == 2);
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("std::valarray")
|
||||
{
|
||||
// valarray has no operator== that returns bool, so compare via a vector copy
|
||||
const auto v1 = j1.get<std::valarray<int>>();
|
||||
CHECK((std::vector<int>(std::begin(v1), std::end(v1)) == std::vector<int> {1, 2, 3, 4}));
|
||||
const auto v2 = j2.get<std::valarray<unsigned int>>();
|
||||
CHECK((std::vector<unsigned int>(std::begin(v2), std::end(v2)) == std::vector<unsigned int> {1u, 2u, 3u, 4u}));
|
||||
const auto v3 = j3.get<std::valarray<double>>();
|
||||
CHECK((std::vector<double>(std::begin(v3), std::end(v3)) == std::vector<double> {1.2, 2.3, 3.4, 4.5}));
|
||||
const auto v4 = j4.get<std::valarray<bool>>();
|
||||
CHECK((std::vector<bool>(std::begin(v4), std::end(v4)) == std::vector<bool> {true, false, true}));
|
||||
const auto v5 = j5.get<std::valarray<std::string>>();
|
||||
CHECK((std::vector<std::string>(std::begin(v5), std::end(v5)) == std::vector<std::string> {"one", "two", "three"}));
|
||||
}
|
||||
|
||||
SECTION("std::vector")
|
||||
{
|
||||
CHECK(j1.get<std::vector<int>>() == (std::vector<int> {1, 2, 3, 4}));
|
||||
CHECK(j2.get<std::vector<unsigned int>>() == (std::vector<unsigned int> {1u, 2u, 3u, 4u}));
|
||||
CHECK(j3.get<std::vector<double>>() == (std::vector<double> {1.2, 2.3, 3.4, 4.5}));
|
||||
CHECK(j4.get<std::vector<bool>>() == (std::vector<bool> {true, false, true}));
|
||||
CHECK(j5.get<std::vector<std::string>>() == (std::vector<std::string> {"one", "two", "three"}));
|
||||
}
|
||||
|
||||
SECTION("std::deque")
|
||||
{
|
||||
CHECK(j1.get<std::deque<int>>() == (std::deque<int> {1, 2, 3, 4}));
|
||||
CHECK(j2.get<std::deque<unsigned int>>() == (std::deque<unsigned int> {1u, 2u, 3u, 4u}));
|
||||
CHECK(j3.get<std::deque<double>>() == (std::deque<double> {1.2, 2.3, 3.4, 4.5}));
|
||||
CHECK(j4.get<std::deque<bool>>() == (std::deque<bool> {true, false, true}));
|
||||
CHECK(j5.get<std::deque<std::string>>() == (std::deque<std::string> {"one", "two", "three"}));
|
||||
}
|
||||
|
||||
SECTION("std::set")
|
||||
{
|
||||
CHECK(j1.get<std::set<int>>() == (std::set<int> {1, 2, 3, 4}));
|
||||
CHECK(j2.get<std::set<unsigned int>>() == (std::set<unsigned int> {1u, 2u, 3u, 4u}));
|
||||
CHECK(j3.get<std::set<double>>() == (std::set<double> {1.2, 2.3, 3.4, 4.5}));
|
||||
CHECK(j4.get<std::set<bool>>() == (std::set<bool> {true, false, true}));
|
||||
CHECK(j5.get<std::set<std::string>>() == (std::set<std::string> {"one", "two", "three"}));
|
||||
}
|
||||
|
||||
SECTION("std::unordered_set")
|
||||
{
|
||||
CHECK(j1.get<std::unordered_set<int>>() == (std::unordered_set<int> {1, 2, 3, 4}));
|
||||
CHECK(j2.get<std::unordered_set<unsigned int>>() == (std::unordered_set<unsigned int> {1u, 2u, 3u, 4u}));
|
||||
CHECK(j3.get<std::unordered_set<double>>() == (std::unordered_set<double> {1.2, 2.3, 3.4, 4.5}));
|
||||
CHECK(j4.get<std::unordered_set<bool>>() == (std::unordered_set<bool> {true, false, true}));
|
||||
CHECK(j5.get<std::unordered_set<std::string>>() == (std::unordered_set<std::string> {"one", "two", "three"}));
|
||||
}
|
||||
|
||||
SECTION("std::map (array of pairs)")
|
||||
{
|
||||
const std::map<int, int> m{{0, 1}, {1, 2}, {2, 3}};
|
||||
json const j6 = m;
|
||||
|
||||
auto m2 = j6.get<std::map<int, int>>();
|
||||
CHECK(m == m2);
|
||||
|
||||
json const j7 = {0, 1, 2, 3};
|
||||
json const j8 = 2;
|
||||
#if JSON_DIAGNOSTICS
|
||||
CHECK_THROWS_WITH_AS((j7.get<std::map<int, int>>()),
|
||||
"[json.exception.type_error.302] (/0) type must be array, "
|
||||
"but is number", json::type_error&);
|
||||
#else
|
||||
CHECK_THROWS_WITH_AS((j7.get<std::map<int, int>>()),
|
||||
"[json.exception.type_error.302] type must be array, "
|
||||
"but is number", json::type_error&);
|
||||
#endif
|
||||
CHECK_THROWS_WITH_AS((j8.get<std::map<int, int>>()),
|
||||
"[json.exception.type_error.302] type must be array, "
|
||||
"but is number", json::type_error&);
|
||||
|
||||
SECTION("superfluous entries")
|
||||
{
|
||||
json const j9 = {{0, 1, 2}, {1, 2, 3}, {2, 3, 4}};
|
||||
m2 = j9.get<std::map<int, int>>();
|
||||
CHECK(m == m2);
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("std::unordered_map (array of pairs)")
|
||||
{
|
||||
const std::unordered_map<int, int> m{{0, 1}, {1, 2}, {2, 3}};
|
||||
json const j6 = m;
|
||||
|
||||
auto m2 = j6.get<std::unordered_map<int, int>>();
|
||||
CHECK(m == m2);
|
||||
|
||||
json const j7 = {0, 1, 2, 3};
|
||||
json const j8 = 2;
|
||||
#if JSON_DIAGNOSTICS
|
||||
CHECK_THROWS_WITH_AS((j7.get<std::unordered_map<int, int>>()),
|
||||
"[json.exception.type_error.302] (/0) type must be array, "
|
||||
"but is number", json::type_error&);
|
||||
#else
|
||||
CHECK_THROWS_WITH_AS((j7.get<std::unordered_map<int, int>>()),
|
||||
"[json.exception.type_error.302] type must be array, "
|
||||
"but is number", json::type_error&);
|
||||
#endif
|
||||
CHECK_THROWS_WITH_AS((j8.get<std::unordered_map<int, int>>()),
|
||||
"[json.exception.type_error.302] type must be array, "
|
||||
"but is number", json::type_error&);
|
||||
|
||||
SECTION("superfluous entries")
|
||||
{
|
||||
json const j9{{0, 1, 2}, {1, 2, 3}, {2, 3, 4}};
|
||||
m2 = j9.get<std::unordered_map<int, int>>();
|
||||
CHECK(m == m2);
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("exception in case of a non-object type")
|
||||
{
|
||||
// does type really must be an array? or it rather must not be null?
|
||||
// that's what I thought when other test like this one broke
|
||||
CHECK_THROWS_WITH_AS(
|
||||
(json().get<std::list<int>>()),
|
||||
"[json.exception.type_error.302] type must be array, but is null", json::type_error&);
|
||||
CHECK_THROWS_WITH_AS(
|
||||
(json().get<std::vector<int>>()),
|
||||
"[json.exception.type_error.302] type must be array, but is null", json::type_error&);
|
||||
CHECK_THROWS_WITH_AS(
|
||||
(json().get<std::vector<json>>()),
|
||||
"[json.exception.type_error.302] type must be array, but is null", json::type_error&);
|
||||
CHECK_THROWS_WITH_AS(
|
||||
(json().get<std::list<json>>()),
|
||||
"[json.exception.type_error.302] type must be array, but is null", json::type_error&);
|
||||
CHECK_THROWS_WITH_AS(
|
||||
(json().get<std::valarray<int>>()),
|
||||
"[json.exception.type_error.302] type must be array, but is null", json::type_error&);
|
||||
CHECK_THROWS_WITH_AS(
|
||||
(json().get<std::map<int, int>>()),
|
||||
"[json.exception.type_error.302] type must be array, but is null", json::type_error&);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum class cards {kreuz, pik, herz, karo};
|
||||
|
||||
// NOLINTNEXTLINE(misc-use-internal-linkage,misc-const-correctness) - false positive
|
||||
NLOHMANN_JSON_SERIALIZE_ENUM(cards,
|
||||
{
|
||||
{cards::kreuz, "kreuz"},
|
||||
{cards::pik, "pik"},
|
||||
{cards::pik, "puk"}, // second entry for cards::puk; will not be used
|
||||
{cards::herz, "herz"},
|
||||
{cards::karo, "karo"}
|
||||
})
|
||||
|
||||
enum TaskState // NOLINT(cert-int09-c,readability-enum-initial-value,cppcoreguidelines-use-enum-class)
|
||||
{
|
||||
TS_STOPPED,
|
||||
TS_RUNNING,
|
||||
TS_COMPLETED,
|
||||
TS_INVALID = -1,
|
||||
};
|
||||
|
||||
// NOLINTNEXTLINE(misc-const-correctness,misc-use-internal-linkage) - false positive
|
||||
NLOHMANN_JSON_SERIALIZE_ENUM(TaskState,
|
||||
{
|
||||
{TS_INVALID, nullptr},
|
||||
{TS_STOPPED, "stopped"},
|
||||
{TS_RUNNING, "running"},
|
||||
{TS_COMPLETED, "completed"},
|
||||
})
|
||||
|
||||
TEST_CASE("JSON to enum mapping")
|
||||
{
|
||||
SECTION("enum class")
|
||||
{
|
||||
// enum -> json
|
||||
CHECK(json(cards::kreuz) == "kreuz");
|
||||
CHECK(json(cards::pik) == "pik");
|
||||
CHECK(json(cards::herz) == "herz");
|
||||
CHECK(json(cards::karo) == "karo");
|
||||
|
||||
// json -> enum
|
||||
CHECK(cards::kreuz == json("kreuz"));
|
||||
CHECK(cards::pik == json("pik"));
|
||||
CHECK(cards::herz == json("herz"));
|
||||
CHECK(cards::karo == json("karo"));
|
||||
|
||||
// invalid json -> first enum
|
||||
CHECK(cards::kreuz == json("what?").get<cards>());
|
||||
}
|
||||
|
||||
SECTION("traditional enum")
|
||||
{
|
||||
// enum -> json
|
||||
CHECK(json(TS_STOPPED) == "stopped");
|
||||
CHECK(json(TS_RUNNING) == "running");
|
||||
CHECK(json(TS_COMPLETED) == "completed");
|
||||
CHECK(json(TS_INVALID) == json());
|
||||
|
||||
// json -> enum
|
||||
CHECK(TS_STOPPED == json("stopped"));
|
||||
CHECK(TS_RUNNING == json("running"));
|
||||
CHECK(TS_COMPLETED == json("completed"));
|
||||
CHECK(TS_INVALID == json());
|
||||
|
||||
// invalid json -> first enum
|
||||
CHECK(TS_INVALID == json("what?").get<TaskState>());
|
||||
}
|
||||
}
|
||||
|
||||
enum class strict_cards {kreuz, pik, herz, karo, andere}; // andere not included in mapping
|
||||
|
||||
// NOLINTNEXTLINE(misc-use-internal-linkage,misc-const-correctness) - false positive
|
||||
NLOHMANN_JSON_SERIALIZE_ENUM_STRICT(strict_cards,
|
||||
{
|
||||
{strict_cards::kreuz, "kreuz"},
|
||||
{strict_cards::pik, "pik"},
|
||||
{strict_cards::pik, "puk"}, // second entry for cards::pik; will not be used
|
||||
{strict_cards::herz, "herz"},
|
||||
{strict_cards::karo, "karo"}
|
||||
})
|
||||
|
||||
enum StrictTaskState // NOLINT(cert-int09-c,readability-enum-initial-value,cppcoreguidelines-use-enum-class)
|
||||
{
|
||||
STRICT_TS_STOPPED,
|
||||
STRICT_TS_RUNNING,
|
||||
STRICT_TS_COMPLETED,
|
||||
STRICT_TS_OTHER, // STRICT_TS_OTHER not in mapping
|
||||
STRICT_TS_INVALID = -1,
|
||||
};
|
||||
|
||||
// NOLINTNEXTLINE(misc-const-correctness,misc-use-internal-linkage) - false positive
|
||||
NLOHMANN_JSON_SERIALIZE_ENUM_STRICT(StrictTaskState,
|
||||
{
|
||||
{STRICT_TS_INVALID, nullptr},
|
||||
{STRICT_TS_STOPPED, "stopped"},
|
||||
{STRICT_TS_RUNNING, "running"},
|
||||
{STRICT_TS_COMPLETED, "completed"},
|
||||
})
|
||||
|
||||
TEST_CASE("Strict JSON to enum mapping")
|
||||
{
|
||||
SECTION("enum class")
|
||||
{
|
||||
// enum -> json
|
||||
CHECK(json(strict_cards::kreuz) == "kreuz");
|
||||
CHECK(json(strict_cards::pik) == "pik");
|
||||
CHECK(json(strict_cards::herz) == "herz");
|
||||
CHECK(json(strict_cards::karo) == "karo");
|
||||
|
||||
// json -> enum
|
||||
CHECK(json("kreuz").get<strict_cards>() == strict_cards::kreuz);
|
||||
CHECK(json("pik").get<strict_cards>() == strict_cards::pik);
|
||||
CHECK(json("herz").get<strict_cards>() == strict_cards::herz);
|
||||
CHECK(json("karo").get<strict_cards>() == strict_cards::karo);
|
||||
|
||||
// invalid json -> exception thrown
|
||||
json _;
|
||||
CHECK_THROWS_WITH_AS(_ = json("what?").get<strict_cards>(), "[json.exception.out_of_range.410] enum value out of range for strict_cards: \"what?\"", json::out_of_range&);
|
||||
|
||||
// conversion of unmapped enum -> exception thrown
|
||||
CHECK_THROWS_WITH_AS(json(strict_cards::andere), "[json.exception.out_of_range.410] enum value out of range for strict_cards", json::out_of_range&);
|
||||
|
||||
// invalid UTF-8 -> out_of_range.410, not the type_error.316 thrown while building the
|
||||
// message (regression test for #5667); such strings can reach get<Enum>() unvalidated,
|
||||
// e.g. from from_cbor()/from_msgpack() (#5529)
|
||||
const json j_invalid_utf8 = "\xFF";
|
||||
CHECK_THROWS_WITH_AS(_ = j_invalid_utf8.get<strict_cards>(), "[json.exception.out_of_range.410] enum value out of range for strict_cards: \"\xEF\xBF\xBD\"", json::out_of_range&);
|
||||
}
|
||||
|
||||
SECTION("traditional enum")
|
||||
{
|
||||
// enum -> json
|
||||
CHECK(json(STRICT_TS_STOPPED) == "stopped");
|
||||
CHECK(json(STRICT_TS_RUNNING) == "running");
|
||||
CHECK(json(STRICT_TS_COMPLETED) == "completed");
|
||||
CHECK(json(STRICT_TS_INVALID) == json());
|
||||
|
||||
// json -> enum
|
||||
CHECK(json("stopped").get<StrictTaskState>() == STRICT_TS_STOPPED);
|
||||
CHECK(json("running").get<StrictTaskState>() == STRICT_TS_RUNNING);
|
||||
CHECK(json("completed").get<StrictTaskState>() == STRICT_TS_COMPLETED);
|
||||
CHECK(json().get<StrictTaskState>() == STRICT_TS_INVALID);
|
||||
|
||||
// invalid json -> exception thrown
|
||||
json _;
|
||||
CHECK_THROWS_WITH_AS(_ = json("what?").get<StrictTaskState>(), "[json.exception.out_of_range.410] enum value out of range for StrictTaskState: \"what?\"", json::out_of_range&);
|
||||
|
||||
// conversion of unmapped enum -> exception thrown
|
||||
CHECK_THROWS_WITH_AS(json(STRICT_TS_OTHER), "[json.exception.out_of_range.410] enum value out of range for StrictTaskState", json::out_of_range&);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#ifdef JSON_HAS_CPP_17
|
||||
#if JSON_HAS_FILESYSTEM || JSON_HAS_EXPERIMENTAL_FILESYSTEM
|
||||
TEST_CASE("std::filesystem::path")
|
||||
{
|
||||
SECTION("ascii")
|
||||
{
|
||||
json const j_string = "Path";
|
||||
auto p = j_string.template get<nlohmann::detail::std_fs::path>();
|
||||
json const j_path = p;
|
||||
|
||||
CHECK(j_path.template get<std::string>() ==
|
||||
j_string.template get<std::string>());
|
||||
}
|
||||
|
||||
SECTION("utf-8")
|
||||
{
|
||||
json const j_string = "P\xc4\x9b\xc5\xa1ina";
|
||||
auto p = j_string.template get<nlohmann::detail::std_fs::path>();
|
||||
json const j_path = p;
|
||||
|
||||
CHECK(j_path.template get<std::string>() ==
|
||||
j_string.template get<std::string>());
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// the ADL to_json overload for std::u8string only exists under the same guard
|
||||
// as std::filesystem::path support (it is otherwise only reached indirectly,
|
||||
// via std::filesystem::path::u8string()) -- mirror both #if conditions from
|
||||
// include/nlohmann/detail/conversions/to_json.hpp exactly
|
||||
#if JSON_HAS_FILESYSTEM || JSON_HAS_EXPERIMENTAL_FILESYSTEM
|
||||
#if defined(__cpp_lib_char8_t)
|
||||
TEST_CASE("std::u8string")
|
||||
{
|
||||
SECTION("ascii")
|
||||
{
|
||||
const std::u8string s = u8"Path";
|
||||
json const j = s;
|
||||
|
||||
CHECK(j.template get<std::string>() == "Path");
|
||||
}
|
||||
|
||||
SECTION("utf-8")
|
||||
{
|
||||
// use \u universal-character-names (rather than raw \x byte escapes
|
||||
// or literal non-ASCII source bytes) to compose the multi-byte UTF-8
|
||||
// encoding -- MSVC treats \x escapes used that way inside a u8
|
||||
// literal as a nonstandard extension (warning C5321), which some of
|
||||
// our CI configs promote to an error; \u is portable and produces
|
||||
// the exact same encoded bytes without depending on the source
|
||||
// file's encoding
|
||||
const std::u8string s = u8"P\u011B\u0161ina";
|
||||
json const j = s;
|
||||
|
||||
CHECK(j.template get<std::string>() == "P\xc4\x9b\xc5\xa1ina");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if !defined(JSON_NOEXCEPTION)
|
||||
namespace
|
||||
{
|
||||
// a type whose to_json reports an error by throwing, used below to check that
|
||||
// converting a std::optional<T> to JSON propagates an exception thrown while
|
||||
// converting its contained value instead of calling std::terminate (#5642)
|
||||
struct throwing_to_json_type {};
|
||||
|
||||
[[noreturn]] void to_json(json& /*unused*/, const throwing_to_json_type& /*unused*/)
|
||||
{
|
||||
throw std::runtime_error("cannot serialize throwing_to_json_type");
|
||||
}
|
||||
} // namespace
|
||||
#endif
|
||||
|
||||
TEST_CASE("std::optional")
|
||||
{
|
||||
SECTION("null")
|
||||
{
|
||||
const json j_null;
|
||||
const std::optional<std::string> opt_null;
|
||||
|
||||
CHECK(json(opt_null) == j_null);
|
||||
CHECK(j_null.get<std::optional<std::string>>() == std::nullopt);
|
||||
|
||||
// Constructing std::optional<T> directly from JSON null throws because
|
||||
// std::optional's own converting constructor is chosen over basic_json's
|
||||
// operator T(). This is a language-level limitation (std::optional<T> is
|
||||
// constructible from T, and T is constructible from basic_json via the
|
||||
// operator); there is no SFINAE path that distinguishes "call from inside
|
||||
// std::optional's constructor" from "direct call". Use get<std::optional<T>>()
|
||||
// or get_to() instead for correct null handling. See #4864 and #5246.
|
||||
CHECK_THROWS_WITH_AS(std::optional<std::string>(j_null),
|
||||
"[json.exception.type_error.302] type must be string, but is null", json::type_error&);
|
||||
CHECK_THROWS_WITH_AS(std::optional<int>(j_null),
|
||||
"[json.exception.type_error.302] type must be number, but is null", json::type_error&);
|
||||
|
||||
// Assignment goes through the same overload resolution as direct
|
||||
// construction, so it throws for the same reason. This relies on
|
||||
// basic_json's implicit conversion operator, so it only applies
|
||||
// when JSON_USE_IMPLICIT_CONVERSIONS is enabled (the default).
|
||||
#if JSON_USE_IMPLICIT_CONVERSIONS
|
||||
std::optional<std::string> opt_assign;
|
||||
CHECK_THROWS_WITH_AS(opt_assign = j_null,
|
||||
"[json.exception.type_error.302] type must be string, but is null", json::type_error&);
|
||||
#endif
|
||||
|
||||
// get_to() is the correct way to obtain std::nullopt from a JSON null.
|
||||
std::optional<std::string> opt_get_to = "placeholder";
|
||||
j_null.get_to(opt_get_to);
|
||||
CHECK(opt_get_to == std::nullopt);
|
||||
}
|
||||
|
||||
SECTION("string")
|
||||
{
|
||||
json j_string = "string";
|
||||
std::optional<std::string> opt_string = "string";
|
||||
|
||||
CHECK(json(opt_string) == j_string);
|
||||
CHECK(std::optional<std::string>(j_string) == opt_string);
|
||||
// false positive: Infer attributes the destruction of the temporaries above to opt_string
|
||||
// @infer-ignore USE_AFTER_DELETE
|
||||
}
|
||||
|
||||
SECTION("bool")
|
||||
{
|
||||
json j_bool = true;
|
||||
std::optional<bool> opt_bool = true;
|
||||
|
||||
CHECK(json(opt_bool) == j_bool);
|
||||
CHECK(std::optional<bool>(j_bool) == opt_bool);
|
||||
}
|
||||
|
||||
SECTION("number")
|
||||
{
|
||||
json j_number = 1;
|
||||
std::optional<int> opt_int = 1;
|
||||
|
||||
CHECK(json(opt_int) == j_number);
|
||||
CHECK(j_number.get<std::optional<int>>() == opt_int);
|
||||
}
|
||||
|
||||
SECTION("array")
|
||||
{
|
||||
json j_array = {1, 2, nullptr};
|
||||
std::vector<std::optional<int>> opt_array = {{1, 2, std::nullopt}};
|
||||
|
||||
CHECK(json(opt_array) == j_array);
|
||||
CHECK(j_array.get<std::vector<std::optional<int>>>() == opt_array);
|
||||
}
|
||||
|
||||
SECTION("object")
|
||||
{
|
||||
json j_object = {{"one", 1}, {"two", 2}, {"zero", nullptr}};
|
||||
std::map<std::string, std::optional<int>> opt_object {{"one", 1}, {"two", 2}, {"zero", std::nullopt}};
|
||||
|
||||
CHECK(json(opt_object) == j_object);
|
||||
CHECK(std::map<std::string, std::optional<int>>(j_object) == opt_object);
|
||||
}
|
||||
|
||||
#if !defined(JSON_NOEXCEPTION)
|
||||
SECTION("exception from contained value's to_json propagates (#5642)")
|
||||
{
|
||||
// to_json(BasicJsonType&, const std::optional<T>&) must not be
|
||||
// noexcept: it calls T's to_json, which may throw (a user-defined
|
||||
// to_json that reports an error, or std::bad_alloc for T =
|
||||
// std::string/vector/json). Before the fix, this called
|
||||
// std::terminate() instead of letting the exception propagate.
|
||||
const std::optional<throwing_to_json_type> opt = throwing_to_json_type{};
|
||||
CHECK_THROWS_WITH_AS(json(opt), "cannot serialize throwing_to_json_type", std::runtime_error&);
|
||||
|
||||
// the conversion is noexcept exactly when converting the contained value is
|
||||
static_assert(!std::is_nothrow_constructible<json, const std::optional<throwing_to_json_type>&>::value);
|
||||
static_assert(std::is_nothrow_constructible<json, const std::optional<int>&>::value);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef JSON_HAS_CPP_17
|
||||
#undef JSON_HAS_CPP_17
|
||||
#endif
|
||||
@@ -0,0 +1,868 @@
|
||||
// __ _____ _____ _____
|
||||
// __| | __| | | | 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
|
||||
|
||||
// cmake/test.cmake selects the C++ standard versions with which to build a
|
||||
// unit test based on the presence of JSON_HAS_CPP_<VERSION> macros.
|
||||
// When using macros that are only defined for particular versions of the standard
|
||||
// (e.g., JSON_HAS_FILESYSTEM for C++17 and up), please mention the corresponding
|
||||
// version macro in a comment close by, like this:
|
||||
// JSON_HAS_CPP_<VERSION> (do not remove; see note at top of file)
|
||||
|
||||
#include "doctest_compatibility.h"
|
||||
|
||||
// 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;
|
||||
|
||||
#include <deque>
|
||||
#include <forward_list>
|
||||
#include <list>
|
||||
#include <set>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <valarray>
|
||||
|
||||
// NLOHMANN_JSON_SERIALIZE_ENUM uses a static std::pair
|
||||
DOCTEST_CLANG_SUPPRESS_WARNING_PUSH
|
||||
DOCTEST_CLANG_SUPPRESS_WARNING("-Wexit-time-destructors")
|
||||
|
||||
#if (defined(__cplusplus) && __cplusplus >= 201703L) || (defined(_HAS_CXX17) && _HAS_CXX17 == 1) // fix for issue #464
|
||||
#define JSON_HAS_CPP_17
|
||||
#define JSON_HAS_CPP_14
|
||||
#elif (defined(__cplusplus) && __cplusplus >= 201402L) || (defined(_HAS_CXX14) && _HAS_CXX14 == 1)
|
||||
#define JSON_HAS_CPP_14
|
||||
#endif
|
||||
|
||||
#ifdef JSON_HAS_CPP_17
|
||||
#if __has_include(<optional>)
|
||||
#include <optional>
|
||||
#elif __has_include(<experimental/optional>)
|
||||
#include <experimental/optional>
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if defined(JSON_HAS_CPP_17)
|
||||
#include <string_view>
|
||||
#endif
|
||||
|
||||
TEST_CASE("value conversion")
|
||||
{
|
||||
SECTION("get a binary value (explicit)")
|
||||
{
|
||||
json::binary_t const n_reference{{1, 2, 3}};
|
||||
json j(n_reference);
|
||||
|
||||
SECTION("binary_t")
|
||||
{
|
||||
json::binary_t const b = j.get<json::binary_t>();
|
||||
CHECK(*json(b).m_data.m_value.binary == *j.m_data.m_value.binary);
|
||||
}
|
||||
|
||||
SECTION("get_binary()")
|
||||
{
|
||||
SECTION("non-const")
|
||||
{
|
||||
auto& b = j.get_binary();
|
||||
CHECK(*json(b).m_data.m_value.binary == *j.m_data.m_value.binary);
|
||||
}
|
||||
|
||||
SECTION("non-const")
|
||||
{
|
||||
const json j_const = j; // NOLINT(performance-unnecessary-copy-initialization)
|
||||
const auto& b = j_const.get_binary();
|
||||
CHECK(*json(b).m_data.m_value.binary == *j.m_data.m_value.binary);
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("exception in case of a non-string type")
|
||||
{
|
||||
json j_null(json::value_t::null);
|
||||
json j_object(json::value_t::object);
|
||||
json j_array(json::value_t::array);
|
||||
json j_string(json::value_t::string);
|
||||
json j_boolean(json::value_t::boolean);
|
||||
const json j_null_const(json::value_t::null);
|
||||
const json j_object_const(json::value_t::object);
|
||||
const json j_array_const(json::value_t::array);
|
||||
const json j_string_const(json::value_t::string);
|
||||
const json j_boolean_const(json::value_t::boolean);
|
||||
|
||||
CHECK_THROWS_WITH_AS(j_null.get<json::binary_t>(),
|
||||
"[json.exception.type_error.302] type must be binary, but is null",
|
||||
json::type_error&);
|
||||
CHECK_THROWS_WITH_AS(j_object.get<json::binary_t>(),
|
||||
"[json.exception.type_error.302] type must be binary, but is object",
|
||||
json::type_error&);
|
||||
CHECK_THROWS_WITH_AS(j_array.get<json::binary_t>(),
|
||||
"[json.exception.type_error.302] type must be binary, but is array",
|
||||
json::type_error&);
|
||||
CHECK_THROWS_WITH_AS(j_string.get<json::binary_t>(),
|
||||
"[json.exception.type_error.302] type must be binary, but is string",
|
||||
json::type_error&);
|
||||
CHECK_THROWS_WITH_AS(j_boolean.get<json::binary_t>(),
|
||||
"[json.exception.type_error.302] type must be binary, but is boolean",
|
||||
json::type_error&);
|
||||
|
||||
CHECK_THROWS_WITH_AS(j_null_const.get<json::binary_t>(),
|
||||
"[json.exception.type_error.302] type must be binary, but is null",
|
||||
json::type_error&);
|
||||
CHECK_THROWS_WITH_AS(j_object_const.get<json::binary_t>(),
|
||||
"[json.exception.type_error.302] type must be binary, but is object",
|
||||
json::type_error&);
|
||||
CHECK_THROWS_WITH_AS(j_array_const.get<json::binary_t>(),
|
||||
"[json.exception.type_error.302] type must be binary, but is array",
|
||||
json::type_error&);
|
||||
CHECK_THROWS_WITH_AS(j_string_const.get<json::binary_t>(),
|
||||
"[json.exception.type_error.302] type must be binary, but is string",
|
||||
json::type_error&);
|
||||
CHECK_THROWS_WITH_AS(j_boolean_const.get<json::binary_t>(),
|
||||
"[json.exception.type_error.302] type must be binary, but is boolean",
|
||||
json::type_error&);
|
||||
|
||||
CHECK_THROWS_WITH_AS(j_null.get_binary(),
|
||||
"[json.exception.type_error.302] type must be binary, but is null",
|
||||
json::type_error&);
|
||||
CHECK_THROWS_WITH_AS(j_object.get_binary(),
|
||||
"[json.exception.type_error.302] type must be binary, but is object",
|
||||
json::type_error&);
|
||||
CHECK_THROWS_WITH_AS(j_array.get_binary(),
|
||||
"[json.exception.type_error.302] type must be binary, but is array",
|
||||
json::type_error&);
|
||||
CHECK_THROWS_WITH_AS(j_string.get_binary(),
|
||||
"[json.exception.type_error.302] type must be binary, but is string",
|
||||
json::type_error&);
|
||||
CHECK_THROWS_WITH_AS(j_boolean.get_binary(),
|
||||
"[json.exception.type_error.302] type must be binary, but is boolean",
|
||||
json::type_error&);
|
||||
|
||||
CHECK_THROWS_WITH_AS(j_null_const.get_binary(),
|
||||
"[json.exception.type_error.302] type must be binary, but is null",
|
||||
json::type_error&);
|
||||
CHECK_THROWS_WITH_AS(j_object_const.get_binary(),
|
||||
"[json.exception.type_error.302] type must be binary, but is object",
|
||||
json::type_error&);
|
||||
CHECK_THROWS_WITH_AS(j_array_const.get_binary(),
|
||||
"[json.exception.type_error.302] type must be binary, but is array",
|
||||
json::type_error&);
|
||||
CHECK_THROWS_WITH_AS(j_string_const.get_binary(),
|
||||
"[json.exception.type_error.302] type must be binary, but is string",
|
||||
json::type_error&);
|
||||
CHECK_THROWS_WITH_AS(j_boolean_const.get_binary(),
|
||||
"[json.exception.type_error.302] type must be binary, but is boolean",
|
||||
json::type_error&);
|
||||
}
|
||||
}
|
||||
|
||||
#if JSON_USE_IMPLICIT_CONVERSIONS
|
||||
SECTION("get a binary value (implicit)")
|
||||
{
|
||||
json::binary_t const n_reference{{1, 2, 3}};
|
||||
json const j(n_reference);
|
||||
|
||||
SECTION("binary_t")
|
||||
{
|
||||
json::binary_t const b = j;
|
||||
CHECK(*json(b).m_data.m_value.binary == *j.m_data.m_value.binary);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifndef SKIP_TESTS_FOR_ENUM_SERIALIZATION
|
||||
SECTION("get an enum")
|
||||
{
|
||||
enum c_enum { value_1, value_2 }; // NOLINT(cppcoreguidelines-use-enum-class)
|
||||
enum class cpp_enum { value_1, value_2 };
|
||||
|
||||
CHECK(json(value_1).get<c_enum>() == value_1);
|
||||
CHECK(json(cpp_enum::value_1).get<cpp_enum>() == cpp_enum::value_1);
|
||||
}
|
||||
|
||||
SECTION("get an enum with underlying type bool (#5671)")
|
||||
{
|
||||
enum class bool_enum : bool { off, on };
|
||||
|
||||
CHECK(json(bool_enum::off).get<bool_enum>() == bool_enum::off);
|
||||
CHECK(json(bool_enum::on).get<bool_enum>() == bool_enum::on);
|
||||
}
|
||||
#endif
|
||||
|
||||
SECTION("more involved conversions")
|
||||
{
|
||||
SECTION("object-like STL containers")
|
||||
{
|
||||
json const j1 = {{"one", 1}, {"two", 2}, {"three", 3}};
|
||||
json const j2 = {{"one", 1u}, {"two", 2u}, {"three", 3u}};
|
||||
json const j3 = {{"one", 1.1}, {"two", 2.2}, {"three", 3.3}};
|
||||
json const j4 = {{"one", true}, {"two", false}, {"three", true}};
|
||||
json const j5 = {{"one", "eins"}, {"two", "zwei"}, {"three", "drei"}};
|
||||
|
||||
SECTION("std::map")
|
||||
{
|
||||
CHECK(j1.get<std::map<std::string, int>>() == (std::map<std::string, int> {{"one", 1}, {"two", 2}, {"three", 3}}));
|
||||
CHECK(j2.get<std::map<std::string, unsigned int>>() == (std::map<std::string, unsigned int> {{"one", 1u}, {"two", 2u}, {"three", 3u}}));
|
||||
CHECK(j3.get<std::map<std::string, double>>() == (std::map<std::string, double> {{"one", 1.1}, {"two", 2.2}, {"three", 3.3}}));
|
||||
CHECK(j4.get<std::map<std::string, bool>>() == (std::map<std::string, bool> {{"one", true}, {"two", false}, {"three", true}}));
|
||||
CHECK(j5.get<std::map<std::string, std::string>>() == (std::map<std::string, std::string> {{"one", "eins"}, {"two", "zwei"}, {"three", "drei"}}));
|
||||
}
|
||||
|
||||
SECTION("std::unordered_map")
|
||||
{
|
||||
CHECK(j1.get<std::unordered_map<std::string, int>>() == (std::unordered_map<std::string, int> {{"one", 1}, {"two", 2}, {"three", 3}}));
|
||||
CHECK(j2.get<std::unordered_map<std::string, unsigned int>>() == (std::unordered_map<std::string, unsigned int> {{"one", 1u}, {"two", 2u}, {"three", 3u}}));
|
||||
CHECK(j3.get<std::unordered_map<std::string, double>>() == (std::unordered_map<std::string, double> {{"one", 1.1}, {"two", 2.2}, {"three", 3.3}}));
|
||||
CHECK(j4.get<std::unordered_map<std::string, bool>>() == (std::unordered_map<std::string, bool> {{"one", true}, {"two", false}, {"three", true}}));
|
||||
const auto m5 = j5.get<std::unordered_map<std::string, std::string>>();
|
||||
CHECK(m5 == (std::unordered_map<std::string, std::string> {{"one", "eins"}, {"two", "zwei"}, {"three", "drei"}}));
|
||||
CHECK(m5.at("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")
|
||||
{
|
||||
CHECK(j1.get<std::multimap<std::string, int>>() == (std::multimap<std::string, int> {{"one", 1}, {"two", 2}, {"three", 3}}));
|
||||
CHECK(j2.get<std::multimap<std::string, unsigned int>>() == (std::multimap<std::string, unsigned int> {{"one", 1u}, {"two", 2u}, {"three", 3u}}));
|
||||
CHECK(j3.get<std::multimap<std::string, double>>() == (std::multimap<std::string, double> {{"one", 1.1}, {"two", 2.2}, {"three", 3.3}}));
|
||||
CHECK(j4.get<std::multimap<std::string, bool>>() == (std::multimap<std::string, bool> {{"one", true}, {"two", false}, {"three", true}}));
|
||||
const auto m5 = j5.get<std::multimap<std::string, std::string>>();
|
||||
CHECK(m5 == (std::multimap<std::string, std::string> {{"one", "eins"}, {"two", "zwei"}, {"three", "drei"}}));
|
||||
CHECK(m5.find("one")->second == "eins");
|
||||
}
|
||||
|
||||
SECTION("std::unordered_multimap")
|
||||
{
|
||||
CHECK(j1.get<std::unordered_multimap<std::string, int>>() == (std::unordered_multimap<std::string, int> {{"one", 1}, {"two", 2}, {"three", 3}}));
|
||||
CHECK(j2.get<std::unordered_multimap<std::string, unsigned int>>() == (std::unordered_multimap<std::string, unsigned int> {{"one", 1u}, {"two", 2u}, {"three", 3u}}));
|
||||
CHECK(j3.get<std::unordered_multimap<std::string, double>>() == (std::unordered_multimap<std::string, double> {{"one", 1.1}, {"two", 2.2}, {"three", 3.3}}));
|
||||
CHECK(j4.get<std::unordered_multimap<std::string, bool>>() == (std::unordered_multimap<std::string, bool> {{"one", true}, {"two", false}, {"three", true}}));
|
||||
const auto m5 = j5.get<std::unordered_multimap<std::string, std::string>>();
|
||||
CHECK(m5 == (std::unordered_multimap<std::string, std::string> {{"one", "eins"}, {"two", "zwei"}, {"three", "drei"}}));
|
||||
CHECK(m5.find("one")->second == "eins");
|
||||
}
|
||||
|
||||
SECTION("exception in case of a non-object type")
|
||||
{
|
||||
CHECK_THROWS_WITH_AS(
|
||||
(json().get<std::map<std::string, int>>()),
|
||||
"[json.exception.type_error.302] type must be object, but is null", json::type_error&);
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("array-like STL containers")
|
||||
{
|
||||
json const j1 = {1, 2, 3, 4};
|
||||
json const j2 = {1u, 2u, 3u, 4u};
|
||||
json const j3 = {1.2, 2.3, 3.4, 4.5};
|
||||
json const j4 = {true, false, true};
|
||||
json const j5 = {"one", "two", "three"};
|
||||
|
||||
SECTION("std::list")
|
||||
{
|
||||
CHECK(j1.get<std::list<int>>() == (std::list<int> {1, 2, 3, 4}));
|
||||
CHECK(j2.get<std::list<unsigned int>>() == (std::list<unsigned int> {1u, 2u, 3u, 4u}));
|
||||
CHECK(j3.get<std::list<double>>() == (std::list<double> {1.2, 2.3, 3.4, 4.5}));
|
||||
CHECK(j4.get<std::list<bool>>() == (std::list<bool> {true, false, true}));
|
||||
CHECK(j5.get<std::list<std::string>>() == (std::list<std::string> {"one", "two", "three"}));
|
||||
}
|
||||
|
||||
SECTION("std::forward_list")
|
||||
{
|
||||
CHECK(j1.get<std::forward_list<int>>() == (std::forward_list<int> {1, 2, 3, 4}));
|
||||
CHECK(j2.get<std::forward_list<unsigned int>>() == (std::forward_list<unsigned int> {1u, 2u, 3u, 4u}));
|
||||
CHECK(j3.get<std::forward_list<double>>() == (std::forward_list<double> {1.2, 2.3, 3.4, 4.5}));
|
||||
CHECK(j4.get<std::forward_list<bool>>() == (std::forward_list<bool> {true, false, true}));
|
||||
CHECK(j5.get<std::forward_list<std::string>>() == (std::forward_list<std::string> {"one", "two", "three"}));
|
||||
}
|
||||
|
||||
SECTION("std::array")
|
||||
{
|
||||
CHECK(j1.get<std::array<int, 4>>() == (std::array<int, 4> {{1, 2, 3, 4}}));
|
||||
// only the first 3 elements of j2 are converted, since the target array is smaller
|
||||
CHECK(j2.get<std::array<unsigned int, 3>>() == (std::array<unsigned int, 3> {{1u, 2u, 3u}}));
|
||||
CHECK(j3.get<std::array<double, 4>>() == (std::array<double, 4> {{1.2, 2.3, 3.4, 4.5}}));
|
||||
CHECK(j4.get<std::array<bool, 3>>() == (std::array<bool, 3> {{true, false, true}}));
|
||||
CHECK(j5.get<std::array<std::string, 3>>() == (std::array<std::string, 3> {{"one", "two", "three"}}));
|
||||
|
||||
SECTION("std::array is larger than JSON")
|
||||
{
|
||||
std::array<int, 6> arr6 = {{1, 2, 3, 4, 5, 6}};
|
||||
CHECK_THROWS_WITH_AS(j1.get_to(arr6), "[json.exception.out_of_range.401] "
|
||||
"array index 4 is out of range", json::out_of_range&);
|
||||
}
|
||||
|
||||
SECTION("std::array is smaller than JSON")
|
||||
{
|
||||
std::array<int, 2> arr2 = {{8, 9}};
|
||||
j1.get_to(arr2);
|
||||
CHECK(arr2[0] == 1);
|
||||
CHECK(arr2[1] == 2);
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("std::valarray")
|
||||
{
|
||||
// valarray has no operator== that returns bool, so compare via a vector copy
|
||||
const auto v1 = j1.get<std::valarray<int>>();
|
||||
CHECK((std::vector<int>(std::begin(v1), std::end(v1)) == std::vector<int> {1, 2, 3, 4}));
|
||||
const auto v2 = j2.get<std::valarray<unsigned int>>();
|
||||
CHECK((std::vector<unsigned int>(std::begin(v2), std::end(v2)) == std::vector<unsigned int> {1u, 2u, 3u, 4u}));
|
||||
const auto v3 = j3.get<std::valarray<double>>();
|
||||
CHECK((std::vector<double>(std::begin(v3), std::end(v3)) == std::vector<double> {1.2, 2.3, 3.4, 4.5}));
|
||||
const auto v4 = j4.get<std::valarray<bool>>();
|
||||
CHECK((std::vector<bool>(std::begin(v4), std::end(v4)) == std::vector<bool> {true, false, true}));
|
||||
const auto v5 = j5.get<std::valarray<std::string>>();
|
||||
CHECK((std::vector<std::string>(std::begin(v5), std::end(v5)) == std::vector<std::string> {"one", "two", "three"}));
|
||||
}
|
||||
|
||||
SECTION("std::vector")
|
||||
{
|
||||
CHECK(j1.get<std::vector<int>>() == (std::vector<int> {1, 2, 3, 4}));
|
||||
CHECK(j2.get<std::vector<unsigned int>>() == (std::vector<unsigned int> {1u, 2u, 3u, 4u}));
|
||||
CHECK(j3.get<std::vector<double>>() == (std::vector<double> {1.2, 2.3, 3.4, 4.5}));
|
||||
CHECK(j4.get<std::vector<bool>>() == (std::vector<bool> {true, false, true}));
|
||||
CHECK(j5.get<std::vector<std::string>>() == (std::vector<std::string> {"one", "two", "three"}));
|
||||
}
|
||||
|
||||
SECTION("std::deque")
|
||||
{
|
||||
CHECK(j1.get<std::deque<int>>() == (std::deque<int> {1, 2, 3, 4}));
|
||||
CHECK(j2.get<std::deque<unsigned int>>() == (std::deque<unsigned int> {1u, 2u, 3u, 4u}));
|
||||
CHECK(j3.get<std::deque<double>>() == (std::deque<double> {1.2, 2.3, 3.4, 4.5}));
|
||||
CHECK(j4.get<std::deque<bool>>() == (std::deque<bool> {true, false, true}));
|
||||
CHECK(j5.get<std::deque<std::string>>() == (std::deque<std::string> {"one", "two", "three"}));
|
||||
}
|
||||
|
||||
SECTION("std::set")
|
||||
{
|
||||
CHECK(j1.get<std::set<int>>() == (std::set<int> {1, 2, 3, 4}));
|
||||
CHECK(j2.get<std::set<unsigned int>>() == (std::set<unsigned int> {1u, 2u, 3u, 4u}));
|
||||
CHECK(j3.get<std::set<double>>() == (std::set<double> {1.2, 2.3, 3.4, 4.5}));
|
||||
CHECK(j4.get<std::set<bool>>() == (std::set<bool> {true, false, true}));
|
||||
CHECK(j5.get<std::set<std::string>>() == (std::set<std::string> {"one", "two", "three"}));
|
||||
}
|
||||
|
||||
SECTION("std::unordered_set")
|
||||
{
|
||||
CHECK(j1.get<std::unordered_set<int>>() == (std::unordered_set<int> {1, 2, 3, 4}));
|
||||
CHECK(j2.get<std::unordered_set<unsigned int>>() == (std::unordered_set<unsigned int> {1u, 2u, 3u, 4u}));
|
||||
CHECK(j3.get<std::unordered_set<double>>() == (std::unordered_set<double> {1.2, 2.3, 3.4, 4.5}));
|
||||
CHECK(j4.get<std::unordered_set<bool>>() == (std::unordered_set<bool> {true, false, true}));
|
||||
CHECK(j5.get<std::unordered_set<std::string>>() == (std::unordered_set<std::string> {"one", "two", "three"}));
|
||||
}
|
||||
|
||||
SECTION("std::map (array of pairs)")
|
||||
{
|
||||
const std::map<int, int> m{{0, 1}, {1, 2}, {2, 3}};
|
||||
json const j6 = m;
|
||||
|
||||
auto m2 = j6.get<std::map<int, int>>();
|
||||
CHECK(m == m2);
|
||||
|
||||
json const j7 = {0, 1, 2, 3};
|
||||
json const j8 = 2;
|
||||
#if JSON_DIAGNOSTICS
|
||||
CHECK_THROWS_WITH_AS((j7.get<std::map<int, int>>()),
|
||||
"[json.exception.type_error.302] (/0) type must be array, "
|
||||
"but is number", json::type_error&);
|
||||
#else
|
||||
CHECK_THROWS_WITH_AS((j7.get<std::map<int, int>>()),
|
||||
"[json.exception.type_error.302] type must be array, "
|
||||
"but is number", json::type_error&);
|
||||
#endif
|
||||
CHECK_THROWS_WITH_AS((j8.get<std::map<int, int>>()),
|
||||
"[json.exception.type_error.302] type must be array, "
|
||||
"but is number", json::type_error&);
|
||||
|
||||
SECTION("superfluous entries")
|
||||
{
|
||||
json const j9 = {{0, 1, 2}, {1, 2, 3}, {2, 3, 4}};
|
||||
m2 = j9.get<std::map<int, int>>();
|
||||
CHECK(m == m2);
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("std::unordered_map (array of pairs)")
|
||||
{
|
||||
const std::unordered_map<int, int> m{{0, 1}, {1, 2}, {2, 3}};
|
||||
json const j6 = m;
|
||||
|
||||
auto m2 = j6.get<std::unordered_map<int, int>>();
|
||||
CHECK(m == m2);
|
||||
|
||||
json const j7 = {0, 1, 2, 3};
|
||||
json const j8 = 2;
|
||||
#if JSON_DIAGNOSTICS
|
||||
CHECK_THROWS_WITH_AS((j7.get<std::unordered_map<int, int>>()),
|
||||
"[json.exception.type_error.302] (/0) type must be array, "
|
||||
"but is number", json::type_error&);
|
||||
#else
|
||||
CHECK_THROWS_WITH_AS((j7.get<std::unordered_map<int, int>>()),
|
||||
"[json.exception.type_error.302] type must be array, "
|
||||
"but is number", json::type_error&);
|
||||
#endif
|
||||
CHECK_THROWS_WITH_AS((j8.get<std::unordered_map<int, int>>()),
|
||||
"[json.exception.type_error.302] type must be array, "
|
||||
"but is number", json::type_error&);
|
||||
|
||||
SECTION("superfluous entries")
|
||||
{
|
||||
json const j9{{0, 1, 2}, {1, 2, 3}, {2, 3, 4}};
|
||||
m2 = j9.get<std::unordered_map<int, int>>();
|
||||
CHECK(m == m2);
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("exception in case of a non-object type")
|
||||
{
|
||||
// does type really must be an array? or it rather must not be null?
|
||||
// that's what I thought when other test like this one broke
|
||||
CHECK_THROWS_WITH_AS(
|
||||
(json().get<std::list<int>>()),
|
||||
"[json.exception.type_error.302] type must be array, but is null", json::type_error&);
|
||||
CHECK_THROWS_WITH_AS(
|
||||
(json().get<std::vector<int>>()),
|
||||
"[json.exception.type_error.302] type must be array, but is null", json::type_error&);
|
||||
CHECK_THROWS_WITH_AS(
|
||||
(json().get<std::vector<json>>()),
|
||||
"[json.exception.type_error.302] type must be array, but is null", json::type_error&);
|
||||
CHECK_THROWS_WITH_AS(
|
||||
(json().get<std::list<json>>()),
|
||||
"[json.exception.type_error.302] type must be array, but is null", json::type_error&);
|
||||
CHECK_THROWS_WITH_AS(
|
||||
(json().get<std::valarray<int>>()),
|
||||
"[json.exception.type_error.302] type must be array, but is null", json::type_error&);
|
||||
CHECK_THROWS_WITH_AS(
|
||||
(json().get<std::map<int, int>>()),
|
||||
"[json.exception.type_error.302] type must be array, but is null", json::type_error&);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum class cards {kreuz, pik, herz, karo};
|
||||
|
||||
// NOLINTNEXTLINE(misc-use-internal-linkage,misc-const-correctness) - false positive
|
||||
NLOHMANN_JSON_SERIALIZE_ENUM(cards,
|
||||
{
|
||||
{cards::kreuz, "kreuz"},
|
||||
{cards::pik, "pik"},
|
||||
{cards::pik, "puk"}, // second entry for cards::puk; will not be used
|
||||
{cards::herz, "herz"},
|
||||
{cards::karo, "karo"}
|
||||
})
|
||||
|
||||
enum TaskState // NOLINT(cert-int09-c,readability-enum-initial-value,cppcoreguidelines-use-enum-class)
|
||||
{
|
||||
TS_STOPPED,
|
||||
TS_RUNNING,
|
||||
TS_COMPLETED,
|
||||
TS_INVALID = -1,
|
||||
};
|
||||
|
||||
// NOLINTNEXTLINE(misc-const-correctness,misc-use-internal-linkage) - false positive
|
||||
NLOHMANN_JSON_SERIALIZE_ENUM(TaskState,
|
||||
{
|
||||
{TS_INVALID, nullptr},
|
||||
{TS_STOPPED, "stopped"},
|
||||
{TS_RUNNING, "running"},
|
||||
{TS_COMPLETED, "completed"},
|
||||
})
|
||||
|
||||
TEST_CASE("JSON to enum mapping")
|
||||
{
|
||||
SECTION("enum class")
|
||||
{
|
||||
// enum -> json
|
||||
CHECK(json(cards::kreuz) == "kreuz");
|
||||
CHECK(json(cards::pik) == "pik");
|
||||
CHECK(json(cards::herz) == "herz");
|
||||
CHECK(json(cards::karo) == "karo");
|
||||
|
||||
// json -> enum
|
||||
CHECK(cards::kreuz == json("kreuz"));
|
||||
CHECK(cards::pik == json("pik"));
|
||||
CHECK(cards::herz == json("herz"));
|
||||
CHECK(cards::karo == json("karo"));
|
||||
|
||||
// invalid json -> first enum
|
||||
CHECK(cards::kreuz == json("what?").get<cards>());
|
||||
}
|
||||
|
||||
SECTION("traditional enum")
|
||||
{
|
||||
// enum -> json
|
||||
CHECK(json(TS_STOPPED) == "stopped");
|
||||
CHECK(json(TS_RUNNING) == "running");
|
||||
CHECK(json(TS_COMPLETED) == "completed");
|
||||
CHECK(json(TS_INVALID) == json());
|
||||
|
||||
// json -> enum
|
||||
CHECK(TS_STOPPED == json("stopped"));
|
||||
CHECK(TS_RUNNING == json("running"));
|
||||
CHECK(TS_COMPLETED == json("completed"));
|
||||
CHECK(TS_INVALID == json());
|
||||
|
||||
// invalid json -> first enum
|
||||
CHECK(TS_INVALID == json("what?").get<TaskState>());
|
||||
}
|
||||
}
|
||||
|
||||
enum class strict_cards {kreuz, pik, herz, karo, andere}; // andere not included in mapping
|
||||
|
||||
// NOLINTNEXTLINE(misc-use-internal-linkage,misc-const-correctness) - false positive
|
||||
NLOHMANN_JSON_SERIALIZE_ENUM_STRICT(strict_cards,
|
||||
{
|
||||
{strict_cards::kreuz, "kreuz"},
|
||||
{strict_cards::pik, "pik"},
|
||||
{strict_cards::pik, "puk"}, // second entry for cards::pik; will not be used
|
||||
{strict_cards::herz, "herz"},
|
||||
{strict_cards::karo, "karo"}
|
||||
})
|
||||
|
||||
enum StrictTaskState // NOLINT(cert-int09-c,readability-enum-initial-value,cppcoreguidelines-use-enum-class)
|
||||
{
|
||||
STRICT_TS_STOPPED,
|
||||
STRICT_TS_RUNNING,
|
||||
STRICT_TS_COMPLETED,
|
||||
STRICT_TS_OTHER, // STRICT_TS_OTHER not in mapping
|
||||
STRICT_TS_INVALID = -1,
|
||||
};
|
||||
|
||||
// NOLINTNEXTLINE(misc-const-correctness,misc-use-internal-linkage) - false positive
|
||||
NLOHMANN_JSON_SERIALIZE_ENUM_STRICT(StrictTaskState,
|
||||
{
|
||||
{STRICT_TS_INVALID, nullptr},
|
||||
{STRICT_TS_STOPPED, "stopped"},
|
||||
{STRICT_TS_RUNNING, "running"},
|
||||
{STRICT_TS_COMPLETED, "completed"},
|
||||
})
|
||||
|
||||
// regression test for #5708 item 2: NLOHMANN_JSON_SERIALIZE_ENUM_STRICT must not rely on
|
||||
// unqualified lookup of a helper name that a user's own namespace may also declare
|
||||
namespace ns_with_colliding_name
|
||||
{
|
||||
// NOLINTNEXTLINE(misc-use-internal-linkage) - used to shadow the library's internal helper name
|
||||
inline void templated_json_throw(int /*unused*/) {}
|
||||
|
||||
enum class colliding_enum { a, b };
|
||||
|
||||
// NOLINTNEXTLINE(misc-use-internal-linkage,misc-const-correctness,cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) - false positive
|
||||
NLOHMANN_JSON_SERIALIZE_ENUM_STRICT(colliding_enum,
|
||||
{
|
||||
{colliding_enum::a, "a"},
|
||||
{colliding_enum::b, "b"}
|
||||
})
|
||||
} // namespace ns_with_colliding_name
|
||||
|
||||
TEST_CASE("NLOHMANN_JSON_SERIALIZE_ENUM_STRICT in a namespace with a colliding name")
|
||||
{
|
||||
using ns_with_colliding_name::colliding_enum;
|
||||
|
||||
CHECK(json(colliding_enum::a) == "a");
|
||||
CHECK(colliding_enum::b == json("b"));
|
||||
|
||||
json _;
|
||||
CHECK_THROWS_WITH_AS(_ = json("nope").get<colliding_enum>(), "[json.exception.out_of_range.410] enum value out of range for colliding_enum: \"nope\"", json::out_of_range&);
|
||||
}
|
||||
|
||||
TEST_CASE("Strict JSON to enum mapping")
|
||||
{
|
||||
SECTION("enum class")
|
||||
{
|
||||
// enum -> json
|
||||
CHECK(json(strict_cards::kreuz) == "kreuz");
|
||||
CHECK(json(strict_cards::pik) == "pik");
|
||||
CHECK(json(strict_cards::herz) == "herz");
|
||||
CHECK(json(strict_cards::karo) == "karo");
|
||||
|
||||
// json -> enum
|
||||
CHECK(json("kreuz").get<strict_cards>() == strict_cards::kreuz);
|
||||
CHECK(json("pik").get<strict_cards>() == strict_cards::pik);
|
||||
CHECK(json("herz").get<strict_cards>() == strict_cards::herz);
|
||||
CHECK(json("karo").get<strict_cards>() == strict_cards::karo);
|
||||
|
||||
// comparison of enum and json
|
||||
CHECK(strict_cards::kreuz == json("kreuz"));
|
||||
CHECK(strict_cards::pik == json("pik"));
|
||||
CHECK(strict_cards::herz == json("herz"));
|
||||
CHECK(strict_cards::karo == json("karo"));
|
||||
|
||||
// invalid json -> exception thrown
|
||||
json _;
|
||||
CHECK_THROWS_WITH_AS(_ = json("what?").get<strict_cards>(), "[json.exception.out_of_range.410] enum value out of range for strict_cards: \"what?\"", json::out_of_range&);
|
||||
|
||||
// conversion of unmapped enum -> exception thrown
|
||||
CHECK_THROWS_WITH_AS(json(strict_cards::andere), "[json.exception.out_of_range.410] enum value out of range for strict_cards", json::out_of_range&);
|
||||
|
||||
// comparing an unmapped enum with json throws the same exception
|
||||
// (the scalar comparison operators used to be noexcept, so this
|
||||
// called std::terminate)
|
||||
CHECK_THROWS_WITH_AS(static_cast<void>(strict_cards::andere == json("andere")), "[json.exception.out_of_range.410] enum value out of range for strict_cards", json::out_of_range&);
|
||||
CHECK_THROWS_WITH_AS(static_cast<void>(json("andere") != strict_cards::andere), "[json.exception.out_of_range.410] enum value out of range for strict_cards", json::out_of_range&);
|
||||
|
||||
// invalid UTF-8 -> out_of_range.410, not the type_error.316 thrown while building the
|
||||
// message (regression test for #5667); such strings can reach get<Enum>() unvalidated,
|
||||
// e.g. from from_cbor()/from_msgpack() (#5529)
|
||||
const json j_invalid_utf8 = "\xFF";
|
||||
CHECK_THROWS_WITH_AS(_ = j_invalid_utf8.get<strict_cards>(), "[json.exception.out_of_range.410] enum value out of range for strict_cards: \"\xEF\xBF\xBD\"", json::out_of_range&);
|
||||
}
|
||||
|
||||
SECTION("traditional enum")
|
||||
{
|
||||
// enum -> json
|
||||
CHECK(json(STRICT_TS_STOPPED) == "stopped");
|
||||
CHECK(json(STRICT_TS_RUNNING) == "running");
|
||||
CHECK(json(STRICT_TS_COMPLETED) == "completed");
|
||||
CHECK(json(STRICT_TS_INVALID) == json());
|
||||
|
||||
// json -> enum
|
||||
CHECK(json("stopped").get<StrictTaskState>() == STRICT_TS_STOPPED);
|
||||
CHECK(json("running").get<StrictTaskState>() == STRICT_TS_RUNNING);
|
||||
CHECK(json("completed").get<StrictTaskState>() == STRICT_TS_COMPLETED);
|
||||
CHECK(json().get<StrictTaskState>() == STRICT_TS_INVALID);
|
||||
|
||||
// comparison of enum and json
|
||||
CHECK(STRICT_TS_STOPPED == json("stopped"));
|
||||
CHECK(STRICT_TS_RUNNING == json("running"));
|
||||
CHECK(STRICT_TS_COMPLETED == json("completed"));
|
||||
CHECK(STRICT_TS_INVALID == json());
|
||||
|
||||
// invalid json -> exception thrown
|
||||
json _;
|
||||
CHECK_THROWS_WITH_AS(_ = json("what?").get<StrictTaskState>(), "[json.exception.out_of_range.410] enum value out of range for StrictTaskState: \"what?\"", json::out_of_range&);
|
||||
|
||||
// conversion of unmapped enum -> exception thrown
|
||||
CHECK_THROWS_WITH_AS(json(STRICT_TS_OTHER), "[json.exception.out_of_range.410] enum value out of range for StrictTaskState", json::out_of_range&);
|
||||
|
||||
// comparing an unmapped enum with json throws the same exception
|
||||
CHECK_THROWS_WITH_AS(static_cast<void>(STRICT_TS_OTHER < json("x")), "[json.exception.out_of_range.410] enum value out of range for StrictTaskState", json::out_of_range&);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#ifdef JSON_HAS_CPP_17
|
||||
#if JSON_HAS_FILESYSTEM || JSON_HAS_EXPERIMENTAL_FILESYSTEM
|
||||
TEST_CASE("std::filesystem::path")
|
||||
{
|
||||
SECTION("ascii")
|
||||
{
|
||||
json const j_string = "Path";
|
||||
auto p = j_string.template get<nlohmann::detail::std_fs::path>();
|
||||
json const j_path = p;
|
||||
|
||||
CHECK(j_path.template get<std::string>() ==
|
||||
j_string.template get<std::string>());
|
||||
}
|
||||
|
||||
SECTION("utf-8")
|
||||
{
|
||||
json const j_string = "P\xc4\x9b\xc5\xa1ina";
|
||||
auto p = j_string.template get<nlohmann::detail::std_fs::path>();
|
||||
json const j_path = p;
|
||||
|
||||
CHECK(j_path.template get<std::string>() ==
|
||||
j_string.template get<std::string>());
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// the ADL to_json overload for std::u8string only exists under the same guard
|
||||
// as std::filesystem::path support (it is otherwise only reached indirectly,
|
||||
// via std::filesystem::path::u8string()) -- mirror both #if conditions from
|
||||
// include/nlohmann/detail/conversions/to_json.hpp exactly
|
||||
#if JSON_HAS_FILESYSTEM || JSON_HAS_EXPERIMENTAL_FILESYSTEM
|
||||
#if defined(__cpp_lib_char8_t)
|
||||
TEST_CASE("std::u8string")
|
||||
{
|
||||
SECTION("ascii")
|
||||
{
|
||||
const std::u8string s = u8"Path";
|
||||
json const j = s;
|
||||
|
||||
CHECK(j.template get<std::string>() == "Path");
|
||||
}
|
||||
|
||||
SECTION("utf-8")
|
||||
{
|
||||
// use \u universal-character-names (rather than raw \x byte escapes
|
||||
// or literal non-ASCII source bytes) to compose the multi-byte UTF-8
|
||||
// encoding -- MSVC treats \x escapes used that way inside a u8
|
||||
// literal as a nonstandard extension (warning C5321), which some of
|
||||
// our CI configs promote to an error; \u is portable and produces
|
||||
// the exact same encoded bytes without depending on the source
|
||||
// file's encoding
|
||||
const std::u8string s = u8"P\u011B\u0161ina";
|
||||
json const j = s;
|
||||
|
||||
CHECK(j.template get<std::string>() == "P\xc4\x9b\xc5\xa1ina");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if !defined(JSON_NOEXCEPTION)
|
||||
namespace
|
||||
{
|
||||
// a type whose to_json reports an error by throwing, used below to check that
|
||||
// converting a std::optional<T> to JSON propagates an exception thrown while
|
||||
// converting its contained value instead of calling std::terminate (#5642)
|
||||
struct throwing_to_json_type {};
|
||||
|
||||
[[noreturn]] void to_json(json& /*unused*/, const throwing_to_json_type& /*unused*/)
|
||||
{
|
||||
throw std::runtime_error("cannot serialize throwing_to_json_type");
|
||||
}
|
||||
} // namespace
|
||||
#endif
|
||||
|
||||
TEST_CASE("std::optional")
|
||||
{
|
||||
SECTION("null")
|
||||
{
|
||||
const json j_null;
|
||||
const std::optional<std::string> opt_null;
|
||||
|
||||
CHECK(json(opt_null) == j_null);
|
||||
CHECK(j_null.get<std::optional<std::string>>() == std::nullopt);
|
||||
|
||||
// Constructing std::optional<T> directly from JSON null throws because
|
||||
// std::optional's own converting constructor is chosen over basic_json's
|
||||
// operator T(). This is a language-level limitation (std::optional<T> is
|
||||
// constructible from T, and T is constructible from basic_json via the
|
||||
// operator); there is no SFINAE path that distinguishes "call from inside
|
||||
// std::optional's constructor" from "direct call". Use get<std::optional<T>>()
|
||||
// or get_to() instead for correct null handling. See #4864 and #5246.
|
||||
CHECK_THROWS_WITH_AS(std::optional<std::string>(j_null),
|
||||
"[json.exception.type_error.302] type must be string, but is null", json::type_error&);
|
||||
CHECK_THROWS_WITH_AS(std::optional<int>(j_null),
|
||||
"[json.exception.type_error.302] type must be number, but is null", json::type_error&);
|
||||
|
||||
// Assignment goes through the same overload resolution as direct
|
||||
// construction, so it throws for the same reason. This relies on
|
||||
// basic_json's implicit conversion operator, so it only applies
|
||||
// when JSON_USE_IMPLICIT_CONVERSIONS is enabled (the default).
|
||||
#if JSON_USE_IMPLICIT_CONVERSIONS
|
||||
std::optional<std::string> opt_assign;
|
||||
CHECK_THROWS_WITH_AS(opt_assign = j_null,
|
||||
"[json.exception.type_error.302] type must be string, but is null", json::type_error&);
|
||||
#endif
|
||||
|
||||
// get_to() is the correct way to obtain std::nullopt from a JSON null.
|
||||
std::optional<std::string> opt_get_to = "placeholder";
|
||||
j_null.get_to(opt_get_to);
|
||||
CHECK(opt_get_to == std::nullopt);
|
||||
}
|
||||
|
||||
SECTION("string")
|
||||
{
|
||||
json j_string = "string";
|
||||
std::optional<std::string> opt_string = "string";
|
||||
|
||||
CHECK(json(opt_string) == j_string);
|
||||
CHECK(std::optional<std::string>(j_string) == opt_string);
|
||||
// false positive: Infer attributes the destruction of the temporaries above to opt_string
|
||||
// @infer-ignore USE_AFTER_DELETE
|
||||
}
|
||||
|
||||
SECTION("bool")
|
||||
{
|
||||
json j_bool = true;
|
||||
std::optional<bool> opt_bool = true;
|
||||
|
||||
CHECK(json(opt_bool) == j_bool);
|
||||
CHECK(std::optional<bool>(j_bool) == opt_bool);
|
||||
}
|
||||
|
||||
SECTION("number")
|
||||
{
|
||||
json j_number = 1;
|
||||
std::optional<int> opt_int = 1;
|
||||
|
||||
CHECK(json(opt_int) == j_number);
|
||||
CHECK(j_number.get<std::optional<int>>() == opt_int);
|
||||
}
|
||||
|
||||
SECTION("array")
|
||||
{
|
||||
json j_array = {1, 2, nullptr};
|
||||
std::vector<std::optional<int>> opt_array = {{1, 2, std::nullopt}};
|
||||
|
||||
CHECK(json(opt_array) == j_array);
|
||||
CHECK(j_array.get<std::vector<std::optional<int>>>() == opt_array);
|
||||
}
|
||||
|
||||
SECTION("object")
|
||||
{
|
||||
json j_object = {{"one", 1}, {"two", 2}, {"zero", nullptr}};
|
||||
std::map<std::string, std::optional<int>> opt_object {{"one", 1}, {"two", 2}, {"zero", std::nullopt}};
|
||||
|
||||
CHECK(json(opt_object) == j_object);
|
||||
CHECK(std::map<std::string, std::optional<int>>(j_object) == opt_object);
|
||||
}
|
||||
|
||||
#if !defined(JSON_NOEXCEPTION)
|
||||
SECTION("exception from contained value's to_json propagates (#5642)")
|
||||
{
|
||||
// to_json(BasicJsonType&, const std::optional<T>&) must not be
|
||||
// noexcept: it calls T's to_json, which may throw (a user-defined
|
||||
// to_json that reports an error, or std::bad_alloc for T =
|
||||
// std::string/vector/json). Before the fix, this called
|
||||
// std::terminate() instead of letting the exception propagate.
|
||||
const std::optional<throwing_to_json_type> opt = throwing_to_json_type{};
|
||||
CHECK_THROWS_WITH_AS(json(opt), "cannot serialize throwing_to_json_type", std::runtime_error&);
|
||||
|
||||
// the conversion is noexcept exactly when converting the contained value is
|
||||
static_assert(!std::is_nothrow_constructible<json, const std::optional<throwing_to_json_type>&>::value);
|
||||
static_assert(std::is_nothrow_constructible<json, const std::optional<int>&>::value);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef JSON_HAS_CPP_17
|
||||
#undef JSON_HAS_CPP_17
|
||||
#endif
|
||||
|
||||
#ifdef JSON_HAS_CPP_14
|
||||
#undef JSON_HAS_CPP_14
|
||||
#endif
|
||||
DOCTEST_CLANG_SUPPRESS_WARNING_POP
|
||||
@@ -10,6 +10,8 @@
|
||||
#include <set>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "doctest_compatibility.h"
|
||||
@@ -405,3 +407,141 @@ TEST_CASE("JSON Visit Node")
|
||||
);
|
||||
CHECK(expected.empty());
|
||||
}
|
||||
|
||||
// Test accessing members of a custom base class that are hidden by members of nlohmann::basic_json
|
||||
class base_class_with_hidden_members
|
||||
{
|
||||
public:
|
||||
const char* type_name() const noexcept // NOLINT(readability-convert-member-functions-to-static)
|
||||
{
|
||||
return "custom type_name";
|
||||
}
|
||||
|
||||
std::size_t size() const noexcept
|
||||
{
|
||||
return m_size;
|
||||
}
|
||||
|
||||
std::size_t m_size = 42;
|
||||
};
|
||||
|
||||
using json_with_hidden_base_members =
|
||||
nlohmann::basic_json <
|
||||
std::map,
|
||||
std::vector,
|
||||
std::string,
|
||||
bool,
|
||||
std::int64_t,
|
||||
std::uint64_t,
|
||||
double,
|
||||
std::allocator,
|
||||
nlohmann::adl_serializer,
|
||||
std::vector<std::uint8_t>,
|
||||
base_class_with_hidden_members
|
||||
>;
|
||||
|
||||
TEST_CASE("JSON Node as_base_class")
|
||||
{
|
||||
using json = json_with_hidden_base_members;
|
||||
|
||||
static_assert(std::is_same<decltype(std::declval<json&>().as_base_class()), json::json_base_class_t&>::value, "");
|
||||
static_assert(std::is_same<decltype(std::declval<const json&>().as_base_class()), const json::json_base_class_t&>::value, "");
|
||||
static_assert(noexcept(std::declval<json&>().as_base_class()), "");
|
||||
static_assert(noexcept(std::declval<const json&>().as_base_class()), "");
|
||||
|
||||
SECTION("non-const")
|
||||
{
|
||||
json j = {1, 2, 3};
|
||||
|
||||
CHECK(std::string(j.type_name()) == "array");
|
||||
CHECK(j.size() == 3);
|
||||
CHECK(std::string(j.as_base_class().type_name()) == "custom type_name");
|
||||
CHECK(j.as_base_class().size() == 42);
|
||||
CHECK(&j.as_base_class() == &static_cast<json::json_base_class_t&>(j));
|
||||
|
||||
j.as_base_class().m_size = 7;
|
||||
CHECK(j.as_base_class().size() == 7);
|
||||
CHECK(j.size() == 3);
|
||||
}
|
||||
|
||||
SECTION("const")
|
||||
{
|
||||
const json j = {1, 2, 3};
|
||||
|
||||
CHECK(std::string(j.type_name()) == "array");
|
||||
CHECK(j.size() == 3);
|
||||
CHECK(std::string(j.as_base_class().type_name()) == "custom type_name");
|
||||
CHECK(j.as_base_class().size() == 42);
|
||||
CHECK(&j.as_base_class() == &static_cast<const json::json_base_class_t&>(j));
|
||||
}
|
||||
}
|
||||
|
||||
// A custom base class with a const member: copy-constructible (initializing a
|
||||
// const member works fine), but not copy-/move-assignable (assigning one does
|
||||
// not). Used to check that copy construction never requires more than that.
|
||||
struct const_member_base
|
||||
{
|
||||
const int id = 7; // NOLINT(misc-non-private-member-variables-in-classes)
|
||||
};
|
||||
|
||||
using json_with_const_base = nlohmann::basic_json <
|
||||
std::map,
|
||||
std::vector,
|
||||
std::string,
|
||||
bool,
|
||||
std::int64_t,
|
||||
std::uint64_t,
|
||||
double,
|
||||
std::allocator,
|
||||
nlohmann::adl_serializer,
|
||||
std::vector<std::uint8_t>,
|
||||
const_member_base
|
||||
>;
|
||||
|
||||
// build an array nested @a depth levels deep, with the innermost value 1;
|
||||
// every level is constructed (never assigned), since const_member_base does
|
||||
// not support assignment
|
||||
static json_with_const_base make_nested_array(std::size_t depth)
|
||||
{
|
||||
if (depth == 0)
|
||||
{
|
||||
return json_with_const_base(1); // NOLINT(modernize-return-braced-init-list): {1} would be an array
|
||||
}
|
||||
return json_with_const_base::array({make_nested_array(depth - 1)});
|
||||
}
|
||||
|
||||
TEST_CASE("Regression test for issue #5674 - copy construction must not require an assignable base class")
|
||||
{
|
||||
SECTION("depth 0")
|
||||
{
|
||||
// as in the original bug report: copy construction only, no assignment
|
||||
const json_with_const_base j = {1, 2};
|
||||
const json_with_const_base copy = j; // NOLINT(performance-unnecessary-copy-initialization)
|
||||
|
||||
CHECK(copy.size() == 2);
|
||||
CHECK(copy.id == 7);
|
||||
}
|
||||
|
||||
SECTION("nested deeper than the copy constructor's descent bound")
|
||||
{
|
||||
// beyond nesting_depth_limit() (128) levels, the copy constructor
|
||||
// copies without the call stack (copy_iteratively / copy_array_level),
|
||||
// which used to assign the base class of every element it created
|
||||
const std::size_t depth = 300;
|
||||
|
||||
const json_with_const_base j = make_nested_array(depth);
|
||||
const json_with_const_base copy = j; // NOLINT(performance-unnecessary-copy-initialization)
|
||||
|
||||
const json_with_const_base* c = ©
|
||||
for (std::size_t level = 0; level <= depth; ++level)
|
||||
{
|
||||
CAPTURE(level)
|
||||
REQUIRE(c->id == 7);
|
||||
if (level < depth)
|
||||
{
|
||||
c = &c->at(0);
|
||||
}
|
||||
}
|
||||
CHECK(*c == 1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,9 @@ DOCTEST_GCC_SUPPRESS_WARNING("-Wnoexcept")
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
using json = nlohmann::json;
|
||||
#ifdef JSON_TEST_NO_GLOBAL_UDLS
|
||||
using namespace nlohmann::literals; // NOLINT(google-build-using-namespace)
|
||||
#endif
|
||||
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
// for #2824
|
||||
@@ -47,6 +50,21 @@ TEST_CASE("Tests with disabled exceptions")
|
||||
delete sax_no_exception::error_string; // NOLINT(cppcoreguidelines-owning-memory)
|
||||
}
|
||||
|
||||
SECTION("issue #5672 - value(json_pointer, default) must not abort for array tokens that are not a valid index")
|
||||
{
|
||||
const json j = {1, 2, 3};
|
||||
|
||||
// a syntactically valid index that is out of range for this array
|
||||
CHECK(j.value("/7"_json_pointer, 42) == 42);
|
||||
// a reference token that is not a number at all
|
||||
CHECK(j.value("/1a"_json_pointer, 42) == 42);
|
||||
// the empty reference token (JSON pointer "/")
|
||||
CHECK(j.value("/"_json_pointer, 42) == 42);
|
||||
// an index whose magnitude does not fit into size_type
|
||||
CHECK(j.value("/99999999999999999999999"_json_pointer, 42) == 42);
|
||||
CHECK(j.value("/18446744073709551615"_json_pointer, 42) == 42);
|
||||
}
|
||||
|
||||
SECTION("growing an ordered_json object")
|
||||
{
|
||||
auto j = nlohmann::ordered_json::object();
|
||||
|
||||
@@ -516,6 +516,21 @@ TEST_CASE_TEMPLATE("element access 2", Json, nlohmann::json, nlohmann::ordered_j
|
||||
CHECK(j_array.value("/-"_json_pointer, 42) == 42);
|
||||
CHECK(j_array_const.value("/-"_json_pointer, 42) == 42);
|
||||
|
||||
// Test an index with a non-digit after a valid leading digit; this is
|
||||
// out_of_range (not parse_error) and must not throw (see #5672)
|
||||
CHECK(j_array.value("/1a"_json_pointer, 42) == 42);
|
||||
CHECK(j_array_const.value("/1a"_json_pointer, 42) == 42);
|
||||
|
||||
// Test the empty reference token (JSON pointer "/"); see #5672
|
||||
CHECK(j_array.value("/"_json_pointer, 42) == 42);
|
||||
CHECK(j_array_const.value("/"_json_pointer, 42) == 42);
|
||||
|
||||
// Test an index whose magnitude does not fit into size_type (see #5672)
|
||||
CHECK(j_array.value("/99999999999999999999999"_json_pointer, 42) == 42);
|
||||
CHECK(j_array_const.value("/99999999999999999999999"_json_pointer, 42) == 42);
|
||||
CHECK(j_array.value("/18446744073709551615"_json_pointer, 42) == 42);
|
||||
CHECK(j_array_const.value("/18446744073709551615"_json_pointer, 42) == 42);
|
||||
|
||||
#if !defined(JSON_NOEXCEPTION)
|
||||
// Test malformed index (non-numeric) throws parse_error
|
||||
CHECK_THROWS_WITH_AS(j_array.value("/foo"_json_pointer, 1), "[json.exception.parse_error.109] parse error: array index 'foo' is not a number", typename Json::parse_error&);
|
||||
@@ -1948,8 +1963,8 @@ TEST_CASE("operator[] with user-defined std::string_view-convertible types")
|
||||
};
|
||||
|
||||
json j = {{"foo", "from_class"}, {"bar", "from_struct"}};
|
||||
TestClass foo_obj;
|
||||
TestStruct bar_obj;
|
||||
const TestClass foo_obj;
|
||||
const TestStruct bar_obj;
|
||||
|
||||
SECTION("read access")
|
||||
{
|
||||
@@ -1973,4 +1988,120 @@ TEST_CASE("operator[] with user-defined std::string_view-convertible types")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("keys convertible to std::string_view work with all lookup functions (regression test for #5663)")
|
||||
{
|
||||
// a key type convertible only to std::string_view: the case #4958 added
|
||||
// support for, but only the non-const operator[] compiled with it
|
||||
struct ViewKey
|
||||
{
|
||||
operator std::string_view() const
|
||||
{
|
||||
return "a";
|
||||
}
|
||||
};
|
||||
|
||||
// a key type convertible to both std::string and std::string_view: with
|
||||
// 3.12.0, such a key worked with at, the const operator[], find, count and
|
||||
// contains via the conversion to std::string; #4958 made the KeyType&&
|
||||
// templates win overload resolution for it instead, and those then failed
|
||||
// the lookups pick the conversion to std::string_view, which leaves the one
|
||||
// to std::string unused; it has to exist to reproduce the ambiguity
|
||||
DOCTEST_CLANG_SUPPRESS_WARNING_PUSH
|
||||
DOCTEST_CLANG_SUPPRESS_WARNING("-Wunused-member-function")
|
||||
struct DualKey
|
||||
{
|
||||
operator std::string() const
|
||||
{
|
||||
return "a";
|
||||
}
|
||||
operator std::string_view() const
|
||||
{
|
||||
return "a";
|
||||
}
|
||||
};
|
||||
DOCTEST_CLANG_SUPPRESS_WARNING_POP
|
||||
|
||||
SECTION("nlohmann::json")
|
||||
{
|
||||
using json = nlohmann::json;
|
||||
|
||||
SECTION("ViewKey")
|
||||
{
|
||||
json j = {{"a", 1}};
|
||||
const json& cj = j;
|
||||
|
||||
CHECK(j[ViewKey{}] == 1);
|
||||
CHECK(cj[ViewKey{}] == 1);
|
||||
CHECK(j.at(ViewKey{}) == 1);
|
||||
CHECK(cj.at(ViewKey{}) == 1);
|
||||
CHECK(j.find(ViewKey{}) != j.end());
|
||||
CHECK(cj.find(ViewKey{}) != cj.end());
|
||||
CHECK(j.count(ViewKey{}) == 1);
|
||||
CHECK(j.contains(ViewKey{}));
|
||||
CHECK(j.value(ViewKey{}, 0) == 1);
|
||||
CHECK(j.erase(ViewKey{}) == 1);
|
||||
CHECK(!j.contains("a"));
|
||||
}
|
||||
|
||||
SECTION("DualKey")
|
||||
{
|
||||
json j = {{"a", 1}};
|
||||
const json& cj = j;
|
||||
|
||||
CHECK(j[DualKey{}] == 1);
|
||||
CHECK(cj[DualKey{}] == 1);
|
||||
CHECK(j.at(DualKey{}) == 1);
|
||||
CHECK(cj.at(DualKey{}) == 1);
|
||||
CHECK(j.find(DualKey{}) != j.end());
|
||||
CHECK(cj.find(DualKey{}) != cj.end());
|
||||
CHECK(j.count(DualKey{}) == 1);
|
||||
CHECK(j.contains(DualKey{}));
|
||||
CHECK(j.value(DualKey{}, 0) == 1);
|
||||
CHECK(j.erase(DualKey{}) == 1);
|
||||
CHECK(!j.contains("a"));
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("nlohmann::ordered_json")
|
||||
{
|
||||
using ordered_json = nlohmann::ordered_json;
|
||||
|
||||
SECTION("ViewKey")
|
||||
{
|
||||
ordered_json j = {{"a", 1}};
|
||||
const ordered_json& cj = j;
|
||||
|
||||
CHECK(j[ViewKey{}] == 1);
|
||||
CHECK(cj[ViewKey{}] == 1);
|
||||
CHECK(j.at(ViewKey{}) == 1);
|
||||
CHECK(cj.at(ViewKey{}) == 1);
|
||||
CHECK(j.find(ViewKey{}) != j.end());
|
||||
CHECK(cj.find(ViewKey{}) != cj.end());
|
||||
CHECK(j.count(ViewKey{}) == 1);
|
||||
CHECK(j.contains(ViewKey{}));
|
||||
CHECK(j.value(ViewKey{}, 0) == 1);
|
||||
CHECK(j.erase(ViewKey{}) == 1);
|
||||
CHECK(!j.contains("a"));
|
||||
}
|
||||
|
||||
SECTION("DualKey")
|
||||
{
|
||||
ordered_json j = {{"a", 1}};
|
||||
const ordered_json& cj = j;
|
||||
|
||||
CHECK(j[DualKey{}] == 1);
|
||||
CHECK(cj[DualKey{}] == 1);
|
||||
CHECK(j.at(DualKey{}) == 1);
|
||||
CHECK(cj.at(DualKey{}) == 1);
|
||||
CHECK(j.find(DualKey{}) != j.end());
|
||||
CHECK(cj.find(DualKey{}) != cj.end());
|
||||
CHECK(j.count(DualKey{}) == 1);
|
||||
CHECK(j.contains(DualKey{}));
|
||||
CHECK(j.value(DualKey{}, 0) == 1);
|
||||
CHECK(j.erase(DualKey{}) == 1);
|
||||
CHECK(!j.contains("a"));
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -630,6 +630,49 @@ TEST_CASE("modifiers")
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("rvalue at position moves rather than copies")
|
||||
{
|
||||
// regression test: insert(pos, basic_json&&) used to forward to
|
||||
// insert(pos, const basic_json&) because the named rvalue
|
||||
// reference parameter is itself an lvalue, so it always
|
||||
// deep-copied its argument instead of moving it
|
||||
json j_big = std::string(1000, 'x');
|
||||
const auto* const original_buffer = j_big.get_ref<const std::string&>().data();
|
||||
|
||||
auto it = j_array.insert(j_array.begin(), std::move(j_big));
|
||||
CHECK(j_array.size() == 5);
|
||||
CHECK(*it == json(std::string(1000, 'x')));
|
||||
CHECK((*it).get_ref<const std::string&>().data() == original_buffer);
|
||||
|
||||
// the moved-from value is null, the same as after push_back(&&)
|
||||
CHECK(j_big.is_null()); // NOLINT(bugprone-use-after-move,hicpp-invalid-access-moved)
|
||||
}
|
||||
|
||||
SECTION("self-aliasing insertion")
|
||||
{
|
||||
SECTION("without reallocation")
|
||||
{
|
||||
json j_self = {1, 2, 3, 4};
|
||||
j_self.get_ref<json::array_t&>().reserve(j_self.size() + 1);
|
||||
|
||||
auto it = j_self.insert(j_self.begin(), std::move(j_self[1]));
|
||||
CHECK(j_self.size() == 5);
|
||||
CHECK(*it == json(2));
|
||||
CHECK(j_self == json({2, 1, nullptr, 3, 4}));
|
||||
}
|
||||
|
||||
SECTION("with reallocation")
|
||||
{
|
||||
json j_self = {1, 2, 3, 4};
|
||||
j_self.get_ref<json::array_t&>().shrink_to_fit();
|
||||
|
||||
auto it = j_self.insert(j_self.begin(), std::move(j_self[1]));
|
||||
CHECK(j_self.size() == 5);
|
||||
CHECK(*it == json(2));
|
||||
CHECK(j_self == json({2, 1, nullptr, 3, 4}));
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("copies at position")
|
||||
{
|
||||
SECTION("insert before begin()")
|
||||
@@ -1119,6 +1162,7 @@ TEST_CASE("update() on deeply nested values")
|
||||
|
||||
TEST_CASE("update() with an argument that aliases *this (#5641)")
|
||||
{
|
||||
#if !defined(JSON_NOEXCEPTION) // checks which exception is thrown, and that nothing changed
|
||||
SECTION("the target is checked before the argument, as before the copy")
|
||||
{
|
||||
json j = 1;
|
||||
@@ -1129,6 +1173,7 @@ TEST_CASE("update() with an argument that aliases *this (#5641)")
|
||||
CHECK_THROWS_WITH_AS(k.update(json::array()), "[json.exception.type_error.312] cannot use update() with array", json::type_error&);
|
||||
CHECK(k == json::object());
|
||||
}
|
||||
#endif
|
||||
|
||||
SECTION("const reference")
|
||||
{
|
||||
|
||||
@@ -1540,19 +1540,39 @@ TEST_CASE("MessagePack")
|
||||
CHECK_THROWS_WITH_AS(_ = json::from_msgpack(std::vector<uint8_t>({0x81})), "[json.exception.parse_error.110] parse error at byte 2: syntax error while parsing MessagePack string: unexpected end of input", json::parse_error&);
|
||||
}
|
||||
|
||||
SECTION("invalid UTF-8 in string (see #5529)")
|
||||
SECTION("ill-formed UTF-8 in string (see #5529, #5651)")
|
||||
{
|
||||
// the MessagePack specification explicitly allows a str object to
|
||||
// contain a byte sequence that is not valid UTF-8 and expects a
|
||||
// deserializer to hand the original bytes back unchanged; this
|
||||
// library follows that, unlike CBOR/UBJSON/BJData/BSON, whose
|
||||
// specifications require text strings to be valid UTF-8
|
||||
|
||||
// a fixstr of length 2 (0xA0 | 2) whose bytes are not valid UTF-8
|
||||
// (0xC0 0xAE is an overlong encoding of '.') must be rejected at
|
||||
// decode time, matching every other kind of malformed binary
|
||||
// input, rather than only failing later when the resulting
|
||||
// value is dumped
|
||||
json _;
|
||||
CHECK_THROWS_WITH_AS(_ = json::from_msgpack(std::vector<uint8_t>({0xa2, 0xc0, 0xae})), "[json.exception.parse_error.113] parse error at byte 3: syntax error while parsing MessagePack string: invalid string: ill-formed UTF-8 byte", json::parse_error&);
|
||||
CHECK(json::from_msgpack(std::vector<uint8_t>({0xa2, 0xc0, 0xae}), true, false).is_discarded());
|
||||
// (0xC0 0xAE is an overlong encoding of '.') round-trips byte for
|
||||
// byte as a string value
|
||||
const std::vector<uint8_t> ill_formed_value = {0xa2, 0xc0, 0xae};
|
||||
json j_value;
|
||||
CHECK_NOTHROW(j_value = json::from_msgpack(ill_formed_value));
|
||||
REQUIRE(j_value.is_string());
|
||||
CHECK(j_value.get_ref<const json::string_t&>() == std::string("\xc0\xae"));
|
||||
CHECK(json::from_msgpack(json::to_msgpack(j_value)) == j_value);
|
||||
// dump() still requires valid UTF-8 and throws for such a value,
|
||||
// unless an error handler that replaces or ignores the bytes is
|
||||
// passed
|
||||
CHECK_THROWS_AS(utils::ignore_return_value(j_value.dump()), json::type_error&);
|
||||
|
||||
// the same bytes as an object key round-trip as well
|
||||
const std::vector<uint8_t> ill_formed_key = {0x81, 0xa2, 0xc0, 0xae, 0x01};
|
||||
json j_key;
|
||||
CHECK_NOTHROW(j_key = json::from_msgpack(ill_formed_key));
|
||||
REQUIRE(j_key.is_object());
|
||||
CHECK(j_key.contains(std::string("\xc0\xae")));
|
||||
CHECK(json::from_msgpack(json::to_msgpack(j_key)) == j_key);
|
||||
|
||||
// a MessagePack bin8 blob with the very same bytes is NOT text
|
||||
// and must still be accepted as-is
|
||||
json _;
|
||||
CHECK_NOTHROW(_ = json::from_msgpack(std::vector<uint8_t>({0xc4, 0x02, 0xc0, 0xae})));
|
||||
CHECK(_ == json::binary(std::vector<std::uint8_t>({0xc0, 0xae})));
|
||||
|
||||
|
||||
@@ -54,6 +54,15 @@ static_assert(noexcept(json(pod {})), "");
|
||||
static_assert(noexcept(std::declval<json>().get<pod>()), "");
|
||||
static_assert(!noexcept(std::declval<json>().get<pod_bis>()), "");
|
||||
static_assert(noexcept(json(pod{})), "");
|
||||
|
||||
// comparing with a scalar is noexcept exactly when converting the scalar is
|
||||
static_assert(noexcept(std::declval<const json&>() == 1), "");
|
||||
static_assert(noexcept(1 != std::declval<const json&>()), "");
|
||||
static_assert(noexcept(std::declval<const json&>() < 2.5), "");
|
||||
static_assert(noexcept(nullptr == std::declval<const json&>()), "");
|
||||
static_assert(!noexcept(std::declval<const json&>() == "foo"), "");
|
||||
static_assert(!noexcept("foo" >= std::declval<const json&>()), "");
|
||||
static_assert(noexcept(std::declval<const json&>() == std::declval<const json&>()), "");
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("noexcept")
|
||||
|
||||
@@ -196,3 +196,44 @@ TEST_CASE("regression test - diff() must account for ordered_json member order")
|
||||
CHECK(a.patch(p) == b);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("regression test for issue #5673 - ordered_json::emplace with a non-rvalue value")
|
||||
{
|
||||
SECTION("lvalue value")
|
||||
{
|
||||
ordered_json oj = ordered_json::object();
|
||||
ordered_json value = 1;
|
||||
auto res = oj.emplace("a", value);
|
||||
CHECK(res.second == true);
|
||||
CHECK(oj.dump() == "{\"a\":1}");
|
||||
}
|
||||
|
||||
SECTION("const lvalue value")
|
||||
{
|
||||
ordered_json oj = ordered_json::object();
|
||||
const ordered_json value = 1;
|
||||
auto res = oj.emplace("a", value);
|
||||
CHECK(res.second == true);
|
||||
CHECK(oj.dump() == "{\"a\":1}");
|
||||
}
|
||||
|
||||
SECTION("rvalue value")
|
||||
{
|
||||
ordered_json oj = ordered_json::object();
|
||||
auto res = oj.emplace("a", ordered_json(1));
|
||||
CHECK(res.second == true);
|
||||
CHECK(oj.dump() == "{\"a\":1}");
|
||||
}
|
||||
|
||||
SECTION("existing key is not overwritten (std::map-compatible semantics)")
|
||||
{
|
||||
ordered_json oj = ordered_json::object();
|
||||
ordered_json value = 1;
|
||||
oj.emplace("a", value);
|
||||
|
||||
ordered_json other_value = 2;
|
||||
auto res = oj.emplace("a", other_value);
|
||||
CHECK(res.second == false);
|
||||
CHECK(oj.dump() == "{\"a\":1}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -403,6 +403,79 @@ TEST_CASE("ordered_map")
|
||||
CHECK(om.size() == 4);
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("emplace")
|
||||
{
|
||||
// regression test for issue #5673: the mapped-value parameter must
|
||||
// accept lvalues and const lvalues, not just rvalues
|
||||
ordered_map<std::string, std::string> om;
|
||||
om["eins"] = "one";
|
||||
om["zwei"] = "two";
|
||||
om["drei"] = "three";
|
||||
|
||||
SECTION("with T&& (rvalue)")
|
||||
{
|
||||
auto res1 = om.emplace("eins", std::string("1"));
|
||||
CHECK(res1.first == om.begin());
|
||||
CHECK(res1.second == false);
|
||||
CHECK(om.size() == 3);
|
||||
CHECK(om.at("eins") == "one"); // existing key is not overwritten
|
||||
|
||||
auto res4 = om.emplace("vier", std::string("four"));
|
||||
CHECK(res4.first == om.begin() + 3);
|
||||
CHECK(res4.second == true);
|
||||
CHECK(om.size() == 4);
|
||||
CHECK(om.at("vier") == "four");
|
||||
}
|
||||
|
||||
SECTION("with T& (lvalue)")
|
||||
{
|
||||
std::string one = "1"; // NOLINT(misc-const-correctness): emplace must accept a non-const lvalue
|
||||
std::string four = "four"; // NOLINT(misc-const-correctness): see above
|
||||
|
||||
auto res1 = om.emplace("eins", one);
|
||||
CHECK(res1.first == om.begin());
|
||||
CHECK(res1.second == false);
|
||||
CHECK(om.size() == 3);
|
||||
CHECK(om.at("eins") == "one"); // existing key is not overwritten
|
||||
|
||||
auto res4 = om.emplace("vier", four);
|
||||
CHECK(res4.first == om.begin() + 3);
|
||||
CHECK(res4.second == true);
|
||||
CHECK(om.size() == 4);
|
||||
CHECK(om.at("vier") == "four");
|
||||
CHECK(four == "four"); // source was copied, not moved from
|
||||
}
|
||||
|
||||
SECTION("with const T&")
|
||||
{
|
||||
const std::string one = "1";
|
||||
const std::string four = "four";
|
||||
|
||||
auto res1 = om.emplace("eins", one);
|
||||
CHECK(res1.first == om.begin());
|
||||
CHECK(res1.second == false);
|
||||
CHECK(om.size() == 3);
|
||||
|
||||
auto res4 = om.emplace("vier", four);
|
||||
CHECK(res4.first == om.begin() + 3);
|
||||
CHECK(res4.second == true);
|
||||
CHECK(om.size() == 4);
|
||||
CHECK(om.at("vier") == "four");
|
||||
}
|
||||
|
||||
SECTION("with key of key_type (non-template overload)")
|
||||
{
|
||||
const std::string key_vier{"vier"};
|
||||
std::string four = "four"; // NOLINT(misc-const-correctness): emplace must accept a non-const lvalue
|
||||
|
||||
auto res4 = om.emplace(key_vier, four);
|
||||
CHECK(res4.first == om.begin() + 3);
|
||||
CHECK(res4.second == true);
|
||||
CHECK(om.size() == 4);
|
||||
CHECK(om.at("vier") == "four");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("ordered_map growth")
|
||||
|
||||
@@ -233,6 +233,52 @@ class my_allocator : public std::allocator<T>
|
||||
};
|
||||
};
|
||||
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
// for #3669
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
|
||||
// mimics boost::optional's converting constructor, whose SFINAE check asks
|
||||
// whether T is constructible from const U&
|
||||
template<class T, class Arg>
|
||||
struct issue3669_is_constructible
|
||||
{
|
||||
template<class T2, class A2, class = decltype(T2(std::declval<A2>()))>
|
||||
static char test(int);
|
||||
template<class, class>
|
||||
static long test(...);
|
||||
static constexpr bool value = sizeof(test<T, Arg>(0)) == 1;
|
||||
};
|
||||
|
||||
template<class T>
|
||||
class issue3669_optional
|
||||
{
|
||||
public:
|
||||
issue3669_optional() = default;
|
||||
template<class U>
|
||||
issue3669_optional(const issue3669_optional<U>& /*unused*/, // NOLINT(google-explicit-constructor,hicpp-explicit-conversions)
|
||||
typename std::enable_if<issue3669_is_constructible<T, const U&>::value, bool>::type /*unused*/ = true) {}
|
||||
};
|
||||
|
||||
class Issue3669Dummy
|
||||
{
|
||||
public:
|
||||
explicit Issue3669Dummy(const json& /*unused*/) {}
|
||||
};
|
||||
|
||||
class Issue3669Holder
|
||||
{
|
||||
issue3669_optional<Issue3669Dummy> d{};
|
||||
|
||||
// GCC < 11 (C++11/14) rejects a free to_json(json&, const Issue3669Holder&)
|
||||
// here, because ADL for Issue3669Dummy finds it and closes an instantiation
|
||||
// cycle; a hidden friend is only visible to ADL for Issue3669Holder
|
||||
friend void to_json(json& j, const Issue3669Holder& h)
|
||||
{
|
||||
static_cast<void>(h.d); // silence -Wunused-private-field
|
||||
j = "holder";
|
||||
}
|
||||
};
|
||||
|
||||
TEST_CASE("regression tests 2")
|
||||
{
|
||||
SECTION("issue #1001 - Fix memory leak during parser callback")
|
||||
@@ -762,6 +808,34 @@ TEST_CASE("regression tests 2")
|
||||
CHECK(j == k);
|
||||
}
|
||||
|
||||
#ifdef JSON_HAS_CPP_17
|
||||
SECTION("issue #5066 - MSVC converts json to std::variant<json> via the conversion operator")
|
||||
{
|
||||
// std::variant<json> must not be retrievable via get<>(), because otherwise the
|
||||
// implicit conversion operator becomes a candidate that MSVC picks over the variant's
|
||||
// converting constructor, routing a number through the string from_json overload
|
||||
static_assert(!nlohmann::detail::is_detected<nlohmann::detail::get_template_function, const json&, std::variant<json>>::value,
|
||||
"std::variant<json> must not be retrievable via get<>()");
|
||||
|
||||
// clang before 7 cannot instantiate libstdc++'s std::variant<json>
|
||||
#if !(defined(__clang__) && __clang_major__ < 7)
|
||||
// push_back, not emplace_back: #5066 needs the implicit conversion
|
||||
// from json to the vector's value type
|
||||
std::vector<std::variant<json>> v;
|
||||
v.push_back(json(1)); // NOLINT(hicpp-use-emplace,modernize-use-emplace)
|
||||
CHECK(std::get<0>(v[0]) == 1);
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
SECTION("issue #3669 - invalid use of incomplete type with optional member and to_json")
|
||||
{
|
||||
const Issue3669Holder h{};
|
||||
const Issue3669Holder h2(h); // NOLINT(performance-unnecessary-copy-initialization)
|
||||
const json j = h2;
|
||||
CHECK(j == "holder");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
TEST_CASE("regression test - parser callback must not lose a duplicate key's prior value")
|
||||
|
||||
@@ -10,10 +10,14 @@
|
||||
|
||||
#if JSON_TEST_USING_MULTIPLE_HEADERS
|
||||
#include <nlohmann/detail/meta/type_traits.hpp>
|
||||
#include <nlohmann/ordered_map.hpp>
|
||||
#else
|
||||
#include <nlohmann/json.hpp>
|
||||
#endif
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
TEST_CASE("type traits")
|
||||
{
|
||||
SECTION("is_c_string")
|
||||
@@ -83,4 +87,12 @@ TEST_CASE("type traits")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("is_ordered_map")
|
||||
{
|
||||
using nlohmann::detail::is_ordered_map;
|
||||
|
||||
CHECK(is_ordered_map<nlohmann::ordered_map<std::string, int>>::value);
|
||||
CHECK_FALSE(is_ordered_map<std::map<std::string, int>>::value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2505,6 +2505,43 @@ TEST_CASE("Universal Binary JSON Specification Examples 1")
|
||||
CHECK(json::to_ubjson(j) == v);
|
||||
CHECK(json::from_ubjson(v) == j);
|
||||
}
|
||||
|
||||
SECTION("ill-formed UTF-8 (see #5529, #5651)")
|
||||
{
|
||||
// none of the binary format specs requires a decoder to reject
|
||||
// ill-formed UTF-8 in a text string, so a value whose bytes are
|
||||
// not valid UTF-8 (0xC0 0xAE is an overlong encoding of '.')
|
||||
// round-trips byte for byte as a string value; to_ubjson() writes
|
||||
// the bytes unchanged, as before 3.13.0, unless
|
||||
// JSON_STRICT_BINARY_UTF8 is enabled (see
|
||||
// unit-binary_utf8_strict.cpp)
|
||||
const std::vector<uint8_t> v = {'S', 'i', 2, 0xc0, 0xae};
|
||||
json j;
|
||||
CHECK_NOTHROW(j = json::from_ubjson(v));
|
||||
REQUIRE(j.is_string());
|
||||
CHECK(j.get_ref<const json::string_t&>() == std::string("\xc0\xae"));
|
||||
CHECK_THROWS_AS(utils::ignore_return_value(j.dump()), json::type_error&);
|
||||
CHECK(json::from_ubjson(json::to_ubjson(j)) == j);
|
||||
|
||||
// the same bytes as an object key round-trip as well
|
||||
const std::vector<uint8_t> v_key = {'{', 'i', 2, 0xc0, 0xae, 'i', 1, '}'};
|
||||
json j_key;
|
||||
CHECK_NOTHROW(j_key = json::from_ubjson(v_key));
|
||||
REQUIRE(j_key.is_object());
|
||||
CHECK(j_key.contains(std::string("\xc0\xae")));
|
||||
CHECK(json::from_ubjson(json::to_ubjson(j_key)) == j_key);
|
||||
|
||||
CHECK(json::from_ubjson(json::to_ubjson(json("\xFF"))) == json("\xFF"));
|
||||
// a truncated multi-byte sequence
|
||||
CHECK(json::from_ubjson(json::to_ubjson(json("\xC3"))) == json("\xC3"));
|
||||
// an encoded surrogate half (U+D800)
|
||||
CHECK(json::from_ubjson(json::to_ubjson(json("\xED\xA0\x80"))) == json("\xED\xA0\x80"));
|
||||
// an overlong encoding of '.'
|
||||
CHECK(json::from_ubjson(json::to_ubjson(json("\xC0\xAF"))) == json("\xC0\xAF"));
|
||||
|
||||
// an object key with ill-formed UTF-8 is kept the same way
|
||||
CHECK(json::from_ubjson(json::to_ubjson(json{{"\xFF", 1}})) == json{{"\xFF", 1}});
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("Array Type")
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
#include "doctest_compatibility.h"
|
||||
|
||||
#include <cwchar>
|
||||
#include <nlohmann/json.hpp>
|
||||
using nlohmann::json;
|
||||
|
||||
@@ -36,10 +37,10 @@ TEST_CASE("wide strings")
|
||||
// 32-bit wchar_t first encodes it as an ill-formed three-byte
|
||||
// sequence (rejected one byte later, at column 3)
|
||||
const char* const error_low_surrogate = sizeof(wchar_t) == 2
|
||||
? "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid string: ill-formed UTF-8 byte; last read: '\"<U+0000>'"
|
||||
? "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid string: ill-formed UTF-8 byte; last read: '\"\xFF'"
|
||||
: "[json.exception.parse_error.101] parse error at line 1, column 3: syntax error while parsing value - invalid string: ill-formed UTF-8 byte; last read: '\"\xED\xB0'";
|
||||
const char* const error_high_surrogate = sizeof(wchar_t) == 2
|
||||
? "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid string: ill-formed UTF-8 byte; last read: '\"<U+0000>'"
|
||||
? "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid string: ill-formed UTF-8 byte; last read: '\"\xFF'"
|
||||
: "[json.exception.parse_error.101] parse error at line 1, column 3: syntax error while parsing value - invalid string: ill-formed UTF-8 byte; last read: '\"\xED\xA0'";
|
||||
|
||||
// a lone low surrogate cannot start a pair
|
||||
@@ -68,15 +69,15 @@ TEST_CASE("wide strings")
|
||||
CHECK_THROWS_AS(_ = json::parse(w), json::parse_error&);
|
||||
|
||||
// a lone low surrogate cannot start a pair
|
||||
CHECK_THROWS_WITH_AS(_ = json::parse(std::u16string{u'"', 0xDC00, u'"'}), "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid string: ill-formed UTF-8 byte; last read: '\"<U+0000>'", json::parse_error&);
|
||||
CHECK_THROWS_WITH_AS(_ = json::parse(std::u16string{u'"', 0xDC00, u'"'}), "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid string: ill-formed UTF-8 byte; last read: '\"\xFF'", json::parse_error&);
|
||||
// a high surrogate followed by a non-low-surrogate unit is invalid
|
||||
CHECK_THROWS_WITH_AS(_ = json::parse(std::u16string{u'"', 0xD800, u'a', u'"'}), "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid string: ill-formed UTF-8 byte; last read: '\"<U+0000>'", json::parse_error&);
|
||||
CHECK_THROWS_WITH_AS(_ = json::parse(std::u16string{u'"', 0xD800, u'a', u'"'}), "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid string: ill-formed UTF-8 byte; last read: '\"\xFF'", json::parse_error&);
|
||||
// ... also when the unit is above the low surrogates
|
||||
CHECK_THROWS_WITH_AS(_ = json::parse(std::u16string{u'"', 0xD800, 0xE000, u'"'}), "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid string: ill-formed UTF-8 byte; last read: '\"<U+0000>'", json::parse_error&);
|
||||
CHECK_THROWS_WITH_AS(_ = json::parse(std::u16string{u'"', 0xD800, 0xE000, u'"'}), "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid string: ill-formed UTF-8 byte; last read: '\"\xFF'", json::parse_error&);
|
||||
// a lone low surrogate must not swallow the following unit: pairing
|
||||
// it with any second unit would produce valid UTF-8, so the error
|
||||
// has to report an ill-formed byte at the surrogate's own position
|
||||
CHECK_THROWS_WITH_AS(_ = json::parse(std::u16string{u'"', 0xDC00, u'a', u'"'}), "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid string: ill-formed UTF-8 byte; last read: '\"<U+0000>'", json::parse_error&);
|
||||
CHECK_THROWS_WITH_AS(_ = json::parse(std::u16string{u'"', 0xDC00, u'a', u'"'}), "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid string: ill-formed UTF-8 byte; last read: '\"\xFF'", json::parse_error&);
|
||||
// a valid surrogate pair is still decoded (U+1F600)
|
||||
CHECK(json::parse(std::u16string{u'"', 0xD83D, 0xDE00, u'"'}).get<std::string>() == "\xF0\x9F\x98\x80");
|
||||
}
|
||||
@@ -104,4 +105,55 @@ TEST_CASE("wide strings")
|
||||
// the same unit inside a string is reported as an ill-formed byte
|
||||
CHECK_THROWS_WITH_AS(_ = json::parse(std::u32string{U'"', static_cast<char32_t>(0xFFFFFFFF), U'"'}), "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid string: ill-formed UTF-8 byte; last read: '\"\xFF'", json::parse_error&);
|
||||
}
|
||||
|
||||
SECTION("malformed wide-string input outside strings (#5645)")
|
||||
{
|
||||
json _;
|
||||
|
||||
// a lone low surrogate inside a literal must not be truncated to its
|
||||
// low byte and mistaken for the letter the literal expects next
|
||||
// (0xDC72 truncates to 'r', which is what "true" expects after 't')
|
||||
CHECK_THROWS_WITH_AS(_ = json::parse(std::u16string{u't', static_cast<char16_t>(0xDC72), u'u', u'e'}),
|
||||
"[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid literal; last read: 't\xFF'", json::parse_error&);
|
||||
|
||||
// ... also when the lone surrogate is the last unit of the input
|
||||
CHECK_THROWS_WITH_AS(_ = json::parse(std::u16string{u'f', u'a', u'l', u's', static_cast<char16_t>(0xDD65)}),
|
||||
"[json.exception.parse_error.101] parse error at line 1, column 5: syntax error while parsing value - invalid literal; last read: 'fals\xFF'", json::parse_error&);
|
||||
|
||||
// a high surrogate followed by a unit that is not its low surrogate
|
||||
// must not silently swallow that unit
|
||||
CHECK_THROWS_WITH_AS(_ = json::parse(std::u16string{u't', static_cast<char16_t>(0xD872), u'X', u'u', u'e'}),
|
||||
"[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid literal; last read: 't\xFF'", json::parse_error&);
|
||||
|
||||
// ... in particular, if the swallowed unit is the newline that ends a
|
||||
// // comment, the comment must not extend over the following line
|
||||
CHECK(json::parse(std::u16string{u'[', u'1', u' ', u'/', u'/', static_cast<char16_t>(0xD800), u'\n',
|
||||
u',', u'2', u' ', u'/', u'/', u'\n', u']'},
|
||||
nullptr, true, /*ignore_comments*/true) == json::parse("[1,2]"));
|
||||
CHECK(json::accept(std::u16string{u'[', u'1', u' ', u'/', u'/', static_cast<char16_t>(0xD800), u'\n',
|
||||
u',', u'2', u' ', u'/', u'/', u'\n', u']'}, /*ignore_comments*/true));
|
||||
|
||||
// cases 5 and 6 use a 32-bit wchar_t (Linux, macOS, the BSDs) to reach
|
||||
// the UTF-32 helper tested above via u32string; the 16-bit wchar_t of
|
||||
// Windows goes through the UTF-16 helper instead, already covered by
|
||||
// the u16string cases above
|
||||
#if WCHAR_MAX > 0xFFFFu
|
||||
// a negative wchar_t must not be mistaken for
|
||||
// char_traits<char>::eof() and silently end the input, letting
|
||||
// trailing garbage pass the strict end-of-input check (only observable
|
||||
// where wint_t is signed, e.g. macOS/the BSDs; on Linux wint_t is
|
||||
// unsigned and this was already handled by #5348)
|
||||
std::wstring w = L"[1]";
|
||||
w.push_back(static_cast<wchar_t>(-1));
|
||||
w += L"garbage";
|
||||
CHECK(!json::accept(w));
|
||||
CHECK_THROWS_WITH_AS(_ = json::parse(w),
|
||||
"[json.exception.parse_error.101] parse error at line 1, column 4: syntax error while parsing value - invalid literal; last read: '1]\xFF'; expected end of input", json::parse_error&);
|
||||
|
||||
// other negative wchar_t units must not be truncated to their low
|
||||
// byte (0xFFFFFF72 truncates to 'r', as in the u16string case above)
|
||||
CHECK_THROWS_WITH_AS(_ = json::parse(std::wstring{L't', static_cast<wchar_t>(0xFFFFFF72), L'u', L'e'}),
|
||||
"[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid literal; last read: 't\xFF'", json::parse_error&);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user