mirror of
https://github.com/nlohmann/json.git
synced 2026-09-29 03:00:31 +00:00
Merge remote-tracking branch 'origin/develop' into claude/iterative-diff
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
This commit is contained in:
@@ -112,7 +112,7 @@ endif()
|
||||
|
||||
if (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
|
||||
# avoid stack overflow, see https://github.com/nlohmann/json/issues/2955
|
||||
json_test_set_test_options("test-cbor;test-msgpack;test-ubjson;test-bjdata;test-binary_formats" LINK_OPTIONS /STACK:4000000)
|
||||
json_test_set_test_options("test-bon8;test-cbor;test-msgpack;test-ubjson;test-bjdata;test-binary_formats" LINK_OPTIONS /STACK:4000000)
|
||||
endif()
|
||||
|
||||
# disable exceptions for test-disabled_exceptions
|
||||
|
||||
+4
-1
@@ -10,7 +10,7 @@ CXXFLAGS += -std=c++11
|
||||
CPPFLAGS += -I ../single_include
|
||||
|
||||
FUZZER_ENGINE = src/fuzzer-driver_afl.cpp
|
||||
FUZZERS = parse_afl_fuzzer parse_bson_fuzzer parse_cbor_fuzzer parse_msgpack_fuzzer parse_ubjson_fuzzer parse_bjdata_fuzzer
|
||||
FUZZERS = parse_afl_fuzzer parse_bson_fuzzer parse_cbor_fuzzer parse_msgpack_fuzzer parse_ubjson_fuzzer parse_bjdata_fuzzer parse_bon8_fuzzer
|
||||
fuzzers: $(FUZZERS)
|
||||
|
||||
parse_afl_fuzzer:
|
||||
@@ -30,3 +30,6 @@ parse_ubjson_fuzzer:
|
||||
|
||||
parse_bjdata_fuzzer:
|
||||
$(CXX) $(CXXFLAGS) $(CPPFLAGS) $(FUZZER_ENGINE) src/fuzzer-parse_bjdata.cpp -o $@
|
||||
|
||||
parse_bon8_fuzzer:
|
||||
$(CXX) $(CXXFLAGS) $(CPPFLAGS) $(FUZZER_ENGINE) src/fuzzer-parse_bon8.cpp -o $@
|
||||
|
||||
@@ -274,7 +274,8 @@ enum class binary_format
|
||||
ubjson_optimized,
|
||||
bjdata,
|
||||
bjdata_optimized,
|
||||
bson
|
||||
bson,
|
||||
bon8
|
||||
};
|
||||
|
||||
static std::vector<std::uint8_t> to_binary(const json& j, const binary_format format)
|
||||
@@ -293,6 +294,8 @@ static std::vector<std::uint8_t> to_binary(const json& j, const binary_format fo
|
||||
return json::to_bjdata(j);
|
||||
case binary_format::bjdata_optimized:
|
||||
return json::to_bjdata(j, true, true);
|
||||
case binary_format::bon8:
|
||||
return json::to_bon8(j);
|
||||
case binary_format::bson:
|
||||
default:
|
||||
return json::to_bson(j);
|
||||
@@ -313,6 +316,8 @@ static json from_binary(const std::vector<std::uint8_t>& bytes, const binary_for
|
||||
case binary_format::bjdata:
|
||||
case binary_format::bjdata_optimized:
|
||||
return json::from_bjdata(bytes);
|
||||
case binary_format::bon8:
|
||||
return json::from_bon8(bytes);
|
||||
case binary_format::bson:
|
||||
default:
|
||||
return json::from_bson(bytes);
|
||||
@@ -333,6 +338,8 @@ static json from_binary(std::FILE* file, const binary_format format)
|
||||
case binary_format::bjdata:
|
||||
case binary_format::bjdata_optimized:
|
||||
return json::from_bjdata(file);
|
||||
case binary_format::bon8:
|
||||
return json::from_bon8(file);
|
||||
case binary_format::bson:
|
||||
default:
|
||||
return json::from_bson(file);
|
||||
@@ -407,6 +414,10 @@ BENCHMARK_CAPTURE(FromBinaryBuffer, bjdata / canada, TEST_DATA_DIRECTORY "/nativ
|
||||
BENCHMARK_CAPTURE(FromBinaryBuffer, bjdata / twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json", binary_format::bjdata);
|
||||
BENCHMARK_CAPTURE(FromBinaryBuffer, bjdata_optimized / canada, TEST_DATA_DIRECTORY "/nativejson-benchmark/canada.json", binary_format::bjdata_optimized);
|
||||
BENCHMARK_CAPTURE(FromBinaryBuffer, bjdata_optimized / twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json", binary_format::bjdata_optimized);
|
||||
BENCHMARK_CAPTURE(FromBinaryBuffer, bon8 / jeopardy, TEST_DATA_DIRECTORY "/jeopardy/jeopardy.json", binary_format::bon8);
|
||||
BENCHMARK_CAPTURE(FromBinaryBuffer, bon8 / canada, TEST_DATA_DIRECTORY "/nativejson-benchmark/canada.json", binary_format::bon8);
|
||||
BENCHMARK_CAPTURE(FromBinaryBuffer, bon8 / citm_catalog, TEST_DATA_DIRECTORY "/nativejson-benchmark/citm_catalog.json", binary_format::bon8);
|
||||
BENCHMARK_CAPTURE(FromBinaryBuffer, bon8 / twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json", binary_format::bon8);
|
||||
// BSON requires an object at the top level, so the array-rooted test files
|
||||
// (jeopardy and the regression files) cannot be captured here
|
||||
BENCHMARK_CAPTURE(FromBinaryBuffer, bson / canada, TEST_DATA_DIRECTORY "/nativejson-benchmark/canada.json", binary_format::bson);
|
||||
@@ -450,6 +461,8 @@ BENCHMARK_CAPTURE(FromBinaryFile, cbor / twitter, TEST_DATA_DIRECTORY "/nativejs
|
||||
BENCHMARK_CAPTURE(FromBinaryFile, ubjson / canada, TEST_DATA_DIRECTORY "/nativejson-benchmark/canada.json", binary_format::ubjson);
|
||||
BENCHMARK_CAPTURE(FromBinaryFile, ubjson / twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json", binary_format::ubjson);
|
||||
BENCHMARK_CAPTURE(FromBinaryFile, bjdata / twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json", binary_format::bjdata);
|
||||
BENCHMARK_CAPTURE(FromBinaryFile, bon8 / canada, TEST_DATA_DIRECTORY "/nativejson-benchmark/canada.json", binary_format::bon8);
|
||||
BENCHMARK_CAPTURE(FromBinaryFile, bon8 / twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json", binary_format::bon8);
|
||||
BENCHMARK_CAPTURE(FromBinaryFile, bson / twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json", binary_format::bson);
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
@@ -530,18 +543,21 @@ BENCHMARK_CAPTURE(FromBinaryShape, nested / msgpack, make_nested, binary_format:
|
||||
BENCHMARK_CAPTURE(FromBinaryShape, nested / ubjson, make_nested, binary_format::ubjson);
|
||||
BENCHMARK_CAPTURE(FromBinaryShape, nested / bjdata, make_nested, binary_format::bjdata);
|
||||
BENCHMARK_CAPTURE(FromBinaryShape, nested / bson, make_nested, binary_format::bson);
|
||||
BENCHMARK_CAPTURE(FromBinaryShape, nested / bon8, make_nested, binary_format::bon8);
|
||||
BENCHMARK_CAPTURE(FromBinaryShape, containers / cbor, make_containers, binary_format::cbor);
|
||||
BENCHMARK_CAPTURE(FromBinaryShape, containers / msgpack, make_containers, binary_format::msgpack);
|
||||
BENCHMARK_CAPTURE(FromBinaryShape, containers / ubjson, make_containers, binary_format::ubjson);
|
||||
BENCHMARK_CAPTURE(FromBinaryShape, containers / ubjson_optimized, make_containers, binary_format::ubjson_optimized);
|
||||
BENCHMARK_CAPTURE(FromBinaryShape, containers / bjdata, make_containers, binary_format::bjdata);
|
||||
BENCHMARK_CAPTURE(FromBinaryShape, containers / bson, make_containers, binary_format::bson);
|
||||
BENCHMARK_CAPTURE(FromBinaryShape, containers / bon8, make_containers, binary_format::bon8);
|
||||
// BSON names every array element, so a large array measures key generation
|
||||
// rather than scalar decoding and is left out here
|
||||
BENCHMARK_CAPTURE(FromBinaryShape, scalars / cbor, make_scalars, binary_format::cbor);
|
||||
BENCHMARK_CAPTURE(FromBinaryShape, scalars / msgpack, make_scalars, binary_format::msgpack);
|
||||
BENCHMARK_CAPTURE(FromBinaryShape, scalars / ubjson, make_scalars, binary_format::ubjson);
|
||||
BENCHMARK_CAPTURE(FromBinaryShape, scalars / bjdata, make_scalars, binary_format::bjdata);
|
||||
BENCHMARK_CAPTURE(FromBinaryShape, scalars / bon8, make_scalars, binary_format::bon8);
|
||||
|
||||
/*!
|
||||
@brief parse an indefinite-length CBOR string
|
||||
|
||||
+3
-3
@@ -1,6 +1,6 @@
|
||||
# Fuzz testing
|
||||
|
||||
Each parser of the library (JSON, BJData, BSON, CBOR, MessagePack, and UBJSON) can be fuzz tested. Currently,
|
||||
Each parser of the library (JSON, BJData, BON8, BSON, CBOR, MessagePack, and UBJSON) can be fuzz tested. Currently,
|
||||
[libFuzzer](https://llvm.org/docs/LibFuzzer.html) and [afl++](https://github.com/AFLplusplus/AFLplusplus) are supported.
|
||||
|
||||
## Corpus creation
|
||||
@@ -10,11 +10,11 @@ directory with some simple input files that cover several features of the parser
|
||||
for mutations.
|
||||
|
||||
```shell
|
||||
TEST_DATA_VERSION=3.1.0
|
||||
TEST_DATA_VERSION=3.2.0
|
||||
wget https://github.com/nlohmann/json_test_data/archive/refs/tags/v$TEST_DATA_VERSION.zip
|
||||
unzip v$TEST_DATA_VERSION.zip
|
||||
rm v$TEST_DATA_VERSION.zip
|
||||
for FORMAT in json bjdata bson cbor msgpack ubjson
|
||||
for FORMAT in json bjdata bon8 bson cbor msgpack ubjson
|
||||
do
|
||||
rm -fr corpus_$FORMAT
|
||||
mkdir corpus_$FORMAT
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
// __ _____ _____ _____
|
||||
// __| | __| | | | 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
|
||||
|
||||
/*
|
||||
This file implements a parser test suitable for fuzz testing. Given a byte
|
||||
array data, it performs the following steps:
|
||||
|
||||
- j1 = from_bon8(data)
|
||||
- vec = to_bon8(j1)
|
||||
- j2 = from_bon8(vec)
|
||||
- assert(j1 == j2)
|
||||
|
||||
It also checks that reading the data from a stream, which reads strings byte by
|
||||
byte, gives the same value or error as reading it from contiguous memory, which
|
||||
copies strings in bulk.
|
||||
|
||||
The provided function `LLVMFuzzerTestOneInput` can be used in different fuzzer
|
||||
drivers.
|
||||
*/
|
||||
|
||||
#include <cassert>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
// the round-trip checks below are assertions; NDEBUG would compile them away
|
||||
#ifdef NDEBUG
|
||||
#error "the fuzzer drivers must be built without NDEBUG"
|
||||
#endif
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
namespace
|
||||
{
|
||||
// the serialization of the value read from @a input, or the error message
|
||||
template<typename InputType>
|
||||
std::string read_bon8(InputType&& input)
|
||||
{
|
||||
try
|
||||
{
|
||||
const auto vec = json::to_bon8(json::from_bon8(std::forward<InputType>(input)));
|
||||
return {vec.begin(), vec.end()};
|
||||
}
|
||||
catch (const json::exception& e)
|
||||
{
|
||||
return e.what();
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
// see http://llvm.org/docs/LibFuzzer.html
|
||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
||||
{
|
||||
// contiguous and stream input must be read alike
|
||||
{
|
||||
std::istringstream stream(std::string(reinterpret_cast<const char*>(data), size));
|
||||
assert(read_bon8(std::vector<uint8_t>(data, data + size)) == read_bon8(stream));
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// step 1: parse input
|
||||
std::vector<uint8_t> const vec1(data, data + size);
|
||||
json const j1 = json::from_bon8(vec1);
|
||||
|
||||
try
|
||||
{
|
||||
// step 2: round trip
|
||||
std::vector<uint8_t> const vec2 = json::to_bon8(j1);
|
||||
|
||||
// parse serialization
|
||||
json const j2 = json::from_bon8(vec2);
|
||||
|
||||
// serializations must match
|
||||
assert(json::to_bon8(j2) == vec2);
|
||||
}
|
||||
catch (const json::parse_error&)
|
||||
{
|
||||
// parsing a BON8 serialization must not fail
|
||||
assert(false);
|
||||
}
|
||||
}
|
||||
catch (const json::parse_error&)
|
||||
{
|
||||
// parse errors are ok, because input may be random bytes
|
||||
}
|
||||
catch (const json::type_error&)
|
||||
{
|
||||
// type errors can occur during parsing, too
|
||||
}
|
||||
catch (const json::out_of_range&)
|
||||
{
|
||||
// out of range errors may happen if provided sizes are excessive
|
||||
}
|
||||
|
||||
// return 0 - non-zero return values are reserved for future use
|
||||
return 0;
|
||||
}
|
||||
@@ -9,6 +9,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint> // uint8_t
|
||||
#include <cstddef> // size_t
|
||||
#include <fstream> // ifstream, istreambuf_iterator, ios
|
||||
#include <vector> // vector
|
||||
|
||||
@@ -24,6 +25,23 @@ namespace utils
|
||||
template<typename T>
|
||||
inline void ignore_return_value(T&& /*unused*/) noexcept {}
|
||||
|
||||
// Advance i toward last (inclusive) by stride, always visiting last.
|
||||
// stride 7 is coprime to 256, so every low-byte residue is still hit.
|
||||
template<typename T>
|
||||
T next_integer_sample(T i, T last, T stride)
|
||||
{
|
||||
if (i >= last)
|
||||
{
|
||||
return static_cast<T>(last + 1);
|
||||
}
|
||||
if (stride > 0 && i > static_cast<T>(last - stride))
|
||||
{
|
||||
return last;
|
||||
}
|
||||
const T n = static_cast<T>(i + stride);
|
||||
return n < last ? n : last;
|
||||
}
|
||||
|
||||
inline std::vector<std::uint8_t> read_binary_file(const std::string& filename)
|
||||
{
|
||||
std::ifstream file(filename, std::ios::binary);
|
||||
|
||||
@@ -37,6 +37,12 @@ struct bad_allocator : std::allocator<T>
|
||||
};
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("get_allocator")
|
||||
{
|
||||
const auto alloc = nlohmann::json::get_allocator();
|
||||
CHECK(alloc == std::allocator<nlohmann::json>());
|
||||
}
|
||||
|
||||
TEST_CASE("bad_alloc")
|
||||
{
|
||||
SECTION("bad_alloc")
|
||||
|
||||
@@ -185,6 +185,7 @@ TEST_CASE("alternative string type")
|
||||
|
||||
CHECK(alt_json::from_cbor(alt_json::to_cbor(doc)) == doc);
|
||||
CHECK(alt_json::from_msgpack(alt_json::to_msgpack(doc)) == doc);
|
||||
CHECK(alt_json::from_bon8(alt_json::to_bon8(doc)) == doc);
|
||||
// BSON is not covered: it additionally needs string_t::find(value_type),
|
||||
// which alt_string does not provide
|
||||
CHECK(alt_json::from_ubjson(alt_json::to_ubjson(doc)) == doc);
|
||||
|
||||
@@ -25,6 +25,7 @@ TEST_CASE("Binary Formats" * doctest::skip())
|
||||
const auto bjdata_1_size = json::to_bjdata(j).size();
|
||||
const auto bjdata_2_size = json::to_bjdata(j, true).size();
|
||||
const auto bjdata_3_size = json::to_bjdata(j, true, true).size();
|
||||
const auto bon8_size = json::to_bon8(j).size();
|
||||
const auto bson_size = json::to_bson(j).size();
|
||||
const auto cbor_size = json::to_cbor(j).size();
|
||||
const auto msgpack_size = json::to_msgpack(j).size();
|
||||
@@ -36,6 +37,7 @@ TEST_CASE("Binary Formats" * doctest::skip())
|
||||
CHECK(bjdata_1_size == 1112030);
|
||||
CHECK(bjdata_2_size == 1224148);
|
||||
CHECK(bjdata_3_size == 1224148);
|
||||
CHECK(bon8_size == 1055792);
|
||||
CHECK(bson_size == 1794522);
|
||||
CHECK(cbor_size == 1055552);
|
||||
CHECK(msgpack_size == 1056145);
|
||||
@@ -47,6 +49,7 @@ TEST_CASE("Binary Formats" * doctest::skip())
|
||||
CHECK((100.0 * double(bjdata_1_size) / double(json_size)) == Approx(53.199));
|
||||
CHECK((100.0 * double(bjdata_2_size) / double(json_size)) == Approx(58.563));
|
||||
CHECK((100.0 * double(bjdata_3_size) / double(json_size)) == Approx(58.563));
|
||||
CHECK((100.0 * double(bon8_size) / double(json_size)) == Approx(50.509));
|
||||
CHECK((100.0 * double(bson_size) / double(json_size)) == Approx(85.849));
|
||||
CHECK((100.0 * double(cbor_size) / double(json_size)) == Approx(50.497));
|
||||
CHECK((100.0 * double(msgpack_size) / double(json_size)) == Approx(50.526));
|
||||
@@ -64,6 +67,7 @@ TEST_CASE("Binary Formats" * doctest::skip())
|
||||
const auto bjdata_1_size = json::to_bjdata(j).size();
|
||||
const auto bjdata_2_size = json::to_bjdata(j, true).size();
|
||||
const auto bjdata_3_size = json::to_bjdata(j, true, true).size();
|
||||
const auto bon8_size = json::to_bon8(j).size();
|
||||
const auto bson_size = json::to_bson(j).size();
|
||||
const auto cbor_size = json::to_cbor(j).size();
|
||||
const auto msgpack_size = json::to_msgpack(j).size();
|
||||
@@ -75,6 +79,7 @@ TEST_CASE("Binary Formats" * doctest::skip())
|
||||
CHECK(bjdata_1_size == 425342);
|
||||
CHECK(bjdata_2_size == 429970);
|
||||
CHECK(bjdata_3_size == 429970);
|
||||
CHECK(bon8_size == 391396);
|
||||
CHECK(bson_size == 444568);
|
||||
CHECK(cbor_size == 402814);
|
||||
CHECK(msgpack_size == 401510);
|
||||
@@ -86,6 +91,7 @@ TEST_CASE("Binary Formats" * doctest::skip())
|
||||
CHECK((100.0 * double(bjdata_1_size) / double(json_size)) == Approx(91.097));
|
||||
CHECK((100.0 * double(bjdata_2_size) / double(json_size)) == Approx(92.089));
|
||||
CHECK((100.0 * double(bjdata_3_size) / double(json_size)) == Approx(92.089));
|
||||
CHECK((100.0 * double(bon8_size) / double(json_size)) == Approx(83.828));
|
||||
CHECK((100.0 * double(bson_size) / double(json_size)) == Approx(95.215));
|
||||
CHECK((100.0 * double(cbor_size) / double(json_size)) == Approx(86.273));
|
||||
CHECK((100.0 * double(msgpack_size) / double(json_size)) == Approx(85.993));
|
||||
@@ -103,6 +109,7 @@ TEST_CASE("Binary Formats" * doctest::skip())
|
||||
const auto bjdata_1_size = json::to_bjdata(j).size();
|
||||
const auto bjdata_2_size = json::to_bjdata(j, true).size();
|
||||
const auto bjdata_3_size = json::to_bjdata(j, true, true).size();
|
||||
const auto bon8_size = json::to_bon8(j).size();
|
||||
const auto bson_size = json::to_bson(j).size();
|
||||
const auto cbor_size = json::to_cbor(j).size();
|
||||
const auto msgpack_size = json::to_msgpack(j).size();
|
||||
@@ -114,6 +121,7 @@ TEST_CASE("Binary Formats" * doctest::skip())
|
||||
CHECK(bjdata_1_size == 390781);
|
||||
CHECK(bjdata_2_size == 433557);
|
||||
CHECK(bjdata_3_size == 432964);
|
||||
CHECK(bon8_size == 317879);
|
||||
CHECK(bson_size == 479430);
|
||||
CHECK(cbor_size == 342373);
|
||||
CHECK(msgpack_size == 342473);
|
||||
@@ -125,6 +133,7 @@ TEST_CASE("Binary Formats" * doctest::skip())
|
||||
CHECK((100.0 * double(bjdata_1_size) / double(json_size)) == Approx(78.109));
|
||||
CHECK((100.0 * double(bjdata_2_size) / double(json_size)) == Approx(86.659));
|
||||
CHECK((100.0 * double(bjdata_3_size) / double(json_size)) == Approx(86.541));
|
||||
CHECK((100.0 * double(bon8_size) / double(json_size)) == Approx(63.538));
|
||||
CHECK((100.0 * double(bson_size) / double(json_size)) == Approx(95.828));
|
||||
CHECK((100.0 * double(cbor_size) / double(json_size)) == Approx(68.433));
|
||||
CHECK((100.0 * double(msgpack_size) / double(json_size)) == Approx(68.453));
|
||||
@@ -142,6 +151,7 @@ TEST_CASE("Binary Formats" * doctest::skip())
|
||||
const auto bjdata_1_size = json::to_bjdata(j).size();
|
||||
const auto bjdata_2_size = json::to_bjdata(j, true).size();
|
||||
const auto bjdata_3_size = json::to_bjdata(j, true, true).size();
|
||||
const auto bon8_size = json::to_bon8(j).size();
|
||||
const auto bson_size = json::to_bson({{"", j}}).size(); // wrap array in object for BSON
|
||||
const auto cbor_size = json::to_cbor(j).size();
|
||||
const auto msgpack_size = json::to_msgpack(j).size();
|
||||
@@ -153,6 +163,7 @@ TEST_CASE("Binary Formats" * doctest::skip())
|
||||
CHECK(bjdata_1_size == 50710965);
|
||||
CHECK(bjdata_2_size == 51144830);
|
||||
CHECK(bjdata_3_size == 51144830);
|
||||
CHECK(bon8_size == 45942080);
|
||||
CHECK(bson_size == 56008520);
|
||||
CHECK(cbor_size == 46187320);
|
||||
CHECK(msgpack_size == 46158575);
|
||||
@@ -164,6 +175,7 @@ TEST_CASE("Binary Formats" * doctest::skip())
|
||||
CHECK((100.0 * double(bjdata_1_size) / double(json_size)) == Approx(96.576));
|
||||
CHECK((100.0 * double(bjdata_2_size) / double(json_size)) == Approx(97.402));
|
||||
CHECK((100.0 * double(bjdata_3_size) / double(json_size)) == Approx(97.402));
|
||||
CHECK((100.0 * double(bon8_size) / double(json_size)) == Approx(87.494));
|
||||
CHECK((100.0 * double(bson_size) / double(json_size)) == Approx(106.665));
|
||||
CHECK((100.0 * double(cbor_size) / double(json_size)) == Approx(87.961));
|
||||
CHECK((100.0 * double(msgpack_size) / double(json_size)) == Approx(87.906));
|
||||
@@ -181,6 +193,7 @@ TEST_CASE("Binary Formats" * doctest::skip())
|
||||
const auto bjdata_1_size = json::to_bjdata(j).size();
|
||||
const auto bjdata_2_size = json::to_bjdata(j, true).size();
|
||||
const auto bjdata_3_size = json::to_bjdata(j, true, true).size();
|
||||
const auto bon8_size = json::to_bon8(j).size();
|
||||
// BSON cannot process the file as it contains code point U+0000
|
||||
const auto cbor_size = json::to_cbor(j).size();
|
||||
const auto msgpack_size = json::to_msgpack(j).size();
|
||||
@@ -192,6 +205,7 @@ TEST_CASE("Binary Formats" * doctest::skip())
|
||||
CHECK(bjdata_1_size == 148695);
|
||||
CHECK(bjdata_2_size == 150569);
|
||||
CHECK(bjdata_3_size == 150569);
|
||||
CHECK(bon8_size == 144477);
|
||||
CHECK(cbor_size == 147095);
|
||||
CHECK(msgpack_size == 147017);
|
||||
CHECK(ubjson_1_size == 148695);
|
||||
@@ -202,6 +216,7 @@ TEST_CASE("Binary Formats" * doctest::skip())
|
||||
CHECK((100.0 * double(bjdata_1_size) / double(json_size)) == Approx(88.153));
|
||||
CHECK((100.0 * double(bjdata_2_size) / double(json_size)) == Approx(89.264));
|
||||
CHECK((100.0 * double(bjdata_3_size) / double(json_size)) == Approx(89.264));
|
||||
CHECK((100.0 * double(bon8_size) / double(json_size)) == Approx(85.653));
|
||||
CHECK((100.0 * double(cbor_size) / double(json_size)) == Approx(87.205));
|
||||
CHECK((100.0 * double(msgpack_size) / double(json_size)) == Approx(87.158));
|
||||
CHECK((100.0 * double(ubjson_1_size) / double(json_size)) == Approx(88.153));
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
using nlohmann::json;
|
||||
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
@@ -49,6 +50,12 @@ std::vector<json> test_values()
|
||||
};
|
||||
}
|
||||
|
||||
// BON8 has no integers above the int64 range, so to_bon8() rejects them
|
||||
bool bon8_representable(const json& j)
|
||||
{
|
||||
return !j.is_number_unsigned() || j.get<std::uint64_t>() <= static_cast<std::uint64_t>((std::numeric_limits<std::int64_t>::max)());
|
||||
}
|
||||
|
||||
// values to_bson() accepts: the document must be an object
|
||||
std::vector<json> bson_values()
|
||||
{
|
||||
@@ -92,6 +99,13 @@ TEST_CASE("binary writer output sinks")
|
||||
json::to_msgpack(j, msgpack);
|
||||
CHECK(json::to_msgpack(j) == msgpack);
|
||||
|
||||
if (bon8_representable(j))
|
||||
{
|
||||
std::vector<std::uint8_t> bon8;
|
||||
json::to_bon8(j, bon8);
|
||||
CHECK(json::to_bon8(j) == bon8);
|
||||
}
|
||||
|
||||
for (const bool use_size :
|
||||
{
|
||||
false, true
|
||||
@@ -172,6 +186,10 @@ TEST_CASE("binary_reserve_hint never over-reserves")
|
||||
CHECK(hint <= json::to_ubjson(j).size());
|
||||
CHECK(hint <= json::to_ubjson(j, true, true).size());
|
||||
CHECK(hint <= json::to_bjdata(j).size());
|
||||
if (bon8_representable(j))
|
||||
{
|
||||
CHECK(hint <= json::to_bon8(j).size());
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& j : bson_values())
|
||||
|
||||
+120
-25
@@ -418,7 +418,7 @@ TEST_CASE("BJData")
|
||||
|
||||
SECTION("-32768..-129 (int16)")
|
||||
{
|
||||
for (int32_t i = -32768; i <= -129; ++i)
|
||||
for (int32_t i = -32768; i <= -129; i = utils::next_integer_sample(i, -129, 7))
|
||||
{
|
||||
CAPTURE(i)
|
||||
|
||||
@@ -578,7 +578,7 @@ TEST_CASE("BJData")
|
||||
|
||||
SECTION("256..32767 (int16)")
|
||||
{
|
||||
for (size_t i = 256; i <= 32767; ++i)
|
||||
for (size_t i = 256; i <= 32767; i = utils::next_integer_sample(i, static_cast<size_t>(32767), static_cast<size_t>(7)))
|
||||
{
|
||||
CAPTURE(i)
|
||||
|
||||
@@ -911,7 +911,7 @@ TEST_CASE("BJData")
|
||||
|
||||
SECTION("256..32767 (int16)")
|
||||
{
|
||||
for (size_t i = 256; i <= 32767; ++i)
|
||||
for (size_t i = 256; i <= 32767; i = utils::next_integer_sample(i, static_cast<size_t>(32767), static_cast<size_t>(7)))
|
||||
{
|
||||
CAPTURE(i)
|
||||
|
||||
@@ -3763,6 +3763,49 @@ TEST_CASE("BJData")
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("BJData input that cannot be read is discarded by every overload")
|
||||
{
|
||||
std::vector<std::uint8_t> input = json::to_bjdata(json({{"a", {1, 2}}}));
|
||||
input.pop_back();
|
||||
|
||||
json _;
|
||||
CHECK_THROWS_AS(_ = json::from_bjdata(input.begin(), input.end()), json::parse_error&);
|
||||
CHECK(json::from_bjdata(input, true, false).is_discarded());
|
||||
CHECK(json::from_bjdata(input.begin(), input.end(), true, false).is_discarded());
|
||||
}
|
||||
|
||||
TEST_CASE("BJData SAX parsing stops at every event")
|
||||
{
|
||||
// Containers are opened and closed by the loop that reads them; a SAX
|
||||
// handler that rejects any event - including the end of a nested
|
||||
// container - must stop the parse right there.
|
||||
const auto count_events = [](const std::vector<std::uint8_t>& input)
|
||||
{
|
||||
int events = 0;
|
||||
while (true)
|
||||
{
|
||||
SaxCountdown scp(events);
|
||||
if (json::sax_parse(input, &scp, json::input_format_t::bjdata))
|
||||
{
|
||||
return events;
|
||||
}
|
||||
++events;
|
||||
REQUIRE(events < 1000);
|
||||
}
|
||||
};
|
||||
|
||||
// 20 events: every container kind closes inside another one
|
||||
const json j = json::parse(R"({"a": [1, {"b": []}], "c": {"d": [[2]]}})");
|
||||
CHECK(count_events(json::to_bjdata(j)) == 20);
|
||||
CHECK(count_events(json::to_bjdata(j, true)) == 20);
|
||||
CHECK(count_events(json::to_bjdata(j, true, true)) == 20);
|
||||
|
||||
// an ND-array is announced as an annotated object: start_object, then
|
||||
// _ArrayType_, _ArraySize_ and _ArrayData_ with its elements
|
||||
const json ndarray = json::parse(R"({"_ArrayType_": "uint8", "_ArraySize_": [2, 2], "_ArrayData_": [1, 2, 3, 4]})");
|
||||
CHECK(count_events(json::to_bjdata(ndarray, true, true)) == 16);
|
||||
}
|
||||
|
||||
TEST_CASE("issue #5405 - array reserve for definite-length BJData arrays")
|
||||
{
|
||||
#if !defined(JSON_NOEXCEPTION)
|
||||
@@ -4247,6 +4290,65 @@ TEST_CASE("all BJData first bytes")
|
||||
}
|
||||
#endif
|
||||
|
||||
TEST_CASE("BJData and UBJSON can be written to a string")
|
||||
{
|
||||
const std::vector<json> values =
|
||||
{
|
||||
{{"a", {1, 2.5, "x", nullptr}}, {"b", json::binary({1, 2})}},
|
||||
// an annotated ND-array, and objects that only look like one
|
||||
json::parse(R"({"_ArrayType_": "uint8", "_ArraySize_": [2, 2], "_ArrayData_": [1, 2, 3, 4]})"),
|
||||
json::parse(R"({"_ArrayType_": 1, "_ArraySize_": [2, 2], "_ArrayData_": [1, 2, 3, 4]})"),
|
||||
json::parse(R"({"_ArrayType_": "uint8", "_ArraySize_": 4, "_ArrayData_": [1, 2, 3, 4]})"),
|
||||
json::parse(R"({"_ArrayType_": "uint8", "_ArraySize_": [2, -2], "_ArrayData_": [1, 2, 3, 4]})"),
|
||||
json::parse(R"({"_ArrayType_": "uint8", "_ArraySize_": [2, 2], "_ArrayData_": [1, 2, 3]})"),
|
||||
json::parse(R"({"_ArrayType_": "uint8", "_ArraySize_": [2, 2], "_ArrayData_": 1})"),
|
||||
};
|
||||
|
||||
// compared byte by byte: building a std::string from the bytes would
|
||||
// convert them implicitly, which -fsanitize=integer reports for bytes of
|
||||
// 0x80 and above
|
||||
const auto same_bytes = [](const std::vector<std::uint8_t>& bytes, const std::string & text)
|
||||
{
|
||||
return bytes.size() == text.size() && std::equal(bytes.begin(), bytes.end(), text.begin(), [](std::uint8_t byte, char c)
|
||||
{
|
||||
return byte == static_cast<std::uint8_t>(c);
|
||||
});
|
||||
};
|
||||
|
||||
for (const auto& j : values)
|
||||
{
|
||||
CAPTURE(j.dump());
|
||||
for (const bool use_size :
|
||||
{
|
||||
false, true
|
||||
})
|
||||
{
|
||||
for (const bool use_type :
|
||||
{
|
||||
false, true
|
||||
})
|
||||
{
|
||||
if (use_type && !use_size)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
CAPTURE(use_size);
|
||||
CAPTURE(use_type);
|
||||
|
||||
const auto bjdata = json::to_bjdata(j, use_size, use_type);
|
||||
std::string bjdata_string;
|
||||
json::to_bjdata(j, bjdata_string, use_size, use_type);
|
||||
CHECK(same_bytes(bjdata, bjdata_string));
|
||||
|
||||
const auto ubjson = json::to_ubjson(j, use_size, use_type);
|
||||
std::string ubjson_string;
|
||||
json::to_ubjson(j, ubjson_string, use_size, use_type);
|
||||
CHECK(same_bytes(ubjson, ubjson_string));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("BJData use_type requires use_size")
|
||||
{
|
||||
SECTION("non-empty object throws other_error.502")
|
||||
@@ -4265,6 +4367,17 @@ TEST_CASE("BJData use_type requires use_size")
|
||||
json::other_error&);
|
||||
}
|
||||
|
||||
SECTION("non-empty binary value throws other_error.502")
|
||||
{
|
||||
const json j = json::binary({1, 2, 3});
|
||||
CHECK_THROWS_WITH_AS(json::to_bjdata(j, false, true),
|
||||
"[json.exception.other_error.502] use_type requires use_size = true",
|
||||
json::other_error&);
|
||||
CHECK_THROWS_WITH_AS(json::to_ubjson(j, false, true),
|
||||
"[json.exception.other_error.502] use_type requires use_size = true",
|
||||
json::other_error&);
|
||||
}
|
||||
|
||||
SECTION("scalars do not throw with use_type=true, use_count=false")
|
||||
{
|
||||
CHECK_NOTHROW(json::to_bjdata(42, false, true));
|
||||
@@ -4428,45 +4541,27 @@ TEST_CASE("BJData roundtrips" * doctest::skip())
|
||||
{
|
||||
CAPTURE(filename)
|
||||
|
||||
std::ifstream f_json(filename);
|
||||
const json j1 = json::parse(f_json);
|
||||
auto packed = utils::read_binary_file(filename + ".bjdata");
|
||||
|
||||
{
|
||||
INFO_WITH_TEMP(filename + ": std::vector<uint8_t>");
|
||||
// parse JSON file
|
||||
std::ifstream f_json(filename);
|
||||
const json j1 = json::parse(f_json);
|
||||
|
||||
// parse BJData file
|
||||
auto packed = utils::read_binary_file(filename + ".bjdata");
|
||||
json j2;
|
||||
CHECK_NOTHROW(j2 = json::from_bjdata(packed));
|
||||
|
||||
// compare parsed JSON values
|
||||
CHECK(j1 == j2);
|
||||
}
|
||||
|
||||
{
|
||||
INFO_WITH_TEMP(filename + ": std::ifstream");
|
||||
// parse JSON file
|
||||
std::ifstream f_json(filename);
|
||||
const json j1 = json::parse(f_json);
|
||||
|
||||
// parse BJData file
|
||||
std::ifstream f_bjdata(filename + ".bjdata", std::ios::binary);
|
||||
json j2;
|
||||
CHECK_NOTHROW(j2 = json::from_bjdata(f_bjdata));
|
||||
|
||||
// compare parsed JSON values
|
||||
CHECK(j1 == j2);
|
||||
}
|
||||
|
||||
{
|
||||
INFO_WITH_TEMP(filename + ": output to output adapters");
|
||||
// parse JSON file
|
||||
std::ifstream f_json(filename);
|
||||
json const j1 = json::parse(f_json);
|
||||
|
||||
// parse BJData file
|
||||
auto packed = utils::read_binary_file(filename + ".bjdata");
|
||||
|
||||
{
|
||||
INFO_WITH_TEMP(filename + ": output adapters: std::vector<uint8_t>");
|
||||
std::vector<uint8_t> vec;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1066,6 +1066,45 @@ TEST_CASE("Incomplete BSON Input")
|
||||
}
|
||||
}
|
||||
|
||||
// the test catches the exceptions of invalid input
|
||||
#if !defined(JSON_NOEXCEPTION)
|
||||
TEST_CASE("BSON keys from contiguous and stream input")
|
||||
{
|
||||
// contiguous input reads a key up to its \x00-byte in one step, a stream
|
||||
// reads it byte by byte; both must give the same value or error for the
|
||||
// complete document and for every truncation of it
|
||||
const json j = {{"", true}, {"k", {1, 2, 3}}, {std::string(40, 'x'), {{"nested key", "value"}}}};
|
||||
const std::vector<std::uint8_t> bson = json::to_bson(j);
|
||||
CHECK(json::from_bson(bson) == j);
|
||||
|
||||
for (std::size_t length = 0; length <= bson.size(); ++length)
|
||||
{
|
||||
CAPTURE(length)
|
||||
const std::vector<std::uint8_t> input(bson.begin(), bson.begin() + static_cast<std::ptrdiff_t>(length));
|
||||
std::string from_vector;
|
||||
std::string from_stream;
|
||||
try
|
||||
{
|
||||
from_vector = json::from_bson(input).dump();
|
||||
}
|
||||
catch (const json::parse_error& e)
|
||||
{
|
||||
from_vector = e.what();
|
||||
}
|
||||
try
|
||||
{
|
||||
std::istringstream stream(std::string(input.begin(), input.end()));
|
||||
from_stream = json::from_bson(stream).dump();
|
||||
}
|
||||
catch (const json::parse_error& e)
|
||||
{
|
||||
from_stream = e.what();
|
||||
}
|
||||
CHECK(from_vector == from_stream);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
TEST_CASE("Negative size of binary value")
|
||||
{
|
||||
// invalid BSON: the size of the binary value is -1
|
||||
@@ -1244,6 +1283,44 @@ TEST_CASE("BSON nesting does not consume the call stack")
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("BSON input that cannot be read is discarded by every overload")
|
||||
{
|
||||
std::vector<std::uint8_t> input = json::to_bson(json({{"a", {1, 2}}}));
|
||||
input.pop_back();
|
||||
|
||||
json _;
|
||||
CHECK_THROWS_AS(_ = json::from_bson(input.begin(), input.end()), json::parse_error&);
|
||||
CHECK(json::from_bson(input, true, false).is_discarded());
|
||||
CHECK(json::from_bson(input.begin(), input.end(), true, false).is_discarded());
|
||||
CHECK(json::from_bson(input.data(), input.size(), true, false).is_discarded());
|
||||
CHECK(json::from_bson({input.data(), input.size()}, true, false).is_discarded());
|
||||
}
|
||||
|
||||
TEST_CASE("BSON SAX parsing stops at every event")
|
||||
{
|
||||
// Containers are opened and closed by the loop that reads them; a SAX
|
||||
// handler that rejects any event - including the end of a nested
|
||||
// container - must stop the parse right there.
|
||||
const auto count_events = [](const std::vector<std::uint8_t>& input)
|
||||
{
|
||||
int events = 0;
|
||||
while (true)
|
||||
{
|
||||
SaxCountdown scp(events);
|
||||
if (json::sax_parse(input, &scp, json::input_format_t::bson))
|
||||
{
|
||||
return events;
|
||||
}
|
||||
++events;
|
||||
REQUIRE(events < 1000);
|
||||
}
|
||||
};
|
||||
|
||||
// 20 events: every container kind closes inside another one
|
||||
const json j = json::parse(R"({"a": [1, {"b": []}], "c": {"d": [[2]]}})");
|
||||
CHECK(count_events(json::to_bson(j)) == 20);
|
||||
}
|
||||
|
||||
TEST_CASE("BSON numerical data")
|
||||
{
|
||||
SECTION("number")
|
||||
|
||||
+140
-34
@@ -15,6 +15,7 @@ using nlohmann::json;
|
||||
#include <sstream>
|
||||
#include <iomanip>
|
||||
#include <limits>
|
||||
#include <list>
|
||||
#include <set>
|
||||
#include "make_test_data_available.hpp"
|
||||
#include "test_utils.hpp"
|
||||
@@ -290,7 +291,7 @@ TEST_CASE("CBOR")
|
||||
|
||||
SECTION("-65536..-257")
|
||||
{
|
||||
for (int32_t i = -65536; i <= -257; ++i)
|
||||
for (int32_t i = -65536; i <= -257; i = utils::next_integer_sample(i, -257, 7))
|
||||
{
|
||||
CAPTURE(i)
|
||||
|
||||
@@ -478,7 +479,7 @@ TEST_CASE("CBOR")
|
||||
|
||||
SECTION("256..65535")
|
||||
{
|
||||
for (size_t i = 256; i <= 65535; ++i)
|
||||
for (size_t i = 256; i <= 65535; i = utils::next_integer_sample(i, static_cast<size_t>(65535), static_cast<size_t>(7)))
|
||||
{
|
||||
CAPTURE(i)
|
||||
|
||||
@@ -613,7 +614,7 @@ TEST_CASE("CBOR")
|
||||
|
||||
SECTION("-32768..-129 (int 16)")
|
||||
{
|
||||
for (int16_t i = -32768; i <= static_cast<std::int16_t>(-129); ++i)
|
||||
for (int16_t i = -32768; i <= static_cast<std::int16_t>(-129); i = utils::next_integer_sample(i, static_cast<int16_t>(-129), static_cast<int16_t>(7)))
|
||||
{
|
||||
CAPTURE(i)
|
||||
|
||||
@@ -718,7 +719,7 @@ TEST_CASE("CBOR")
|
||||
|
||||
SECTION("256..65535 (two-byte uint16_t)")
|
||||
{
|
||||
for (size_t i = 256; i <= 65535; ++i)
|
||||
for (size_t i = 256; i <= 65535; i = utils::next_integer_sample(i, static_cast<size_t>(65535), static_cast<size_t>(7)))
|
||||
{
|
||||
CAPTURE(i)
|
||||
|
||||
@@ -2123,6 +2124,20 @@ TEST_CASE("CBOR nesting does not consume the call stack")
|
||||
CHECK(json::from_cbor(input, true, false, json::cbor_tag_handler_t::ignore).is_discarded());
|
||||
}
|
||||
|
||||
SECTION("stored tags")
|
||||
{
|
||||
// a tag over something other than a byte string is read like for
|
||||
// ignore, so a chain of them must not recurse either (#5316)
|
||||
std::vector<uint8_t> input;
|
||||
for (std::size_t i = 0; i < 500000; ++i)
|
||||
{
|
||||
input.push_back(0xD8);
|
||||
input.push_back(0x18);
|
||||
}
|
||||
input.push_back(0x01);
|
||||
CHECK(json::from_cbor(input, true, true, json::cbor_tag_handler_t::store) == 1);
|
||||
}
|
||||
|
||||
SECTION("a well-formed deep value is read through the SAX interface")
|
||||
{
|
||||
std::vector<uint8_t> input(200000, 0x9F);
|
||||
@@ -2175,6 +2190,52 @@ TEST_CASE("CBOR nesting does not consume the call stack")
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("CBOR input that cannot be read is discarded by every overload")
|
||||
{
|
||||
std::vector<std::uint8_t> input = json::to_cbor(json({{"a", {1, 2}}}));
|
||||
input.pop_back();
|
||||
|
||||
json _;
|
||||
CHECK_THROWS_AS(_ = json::from_cbor(input.begin(), input.end()), json::parse_error&);
|
||||
CHECK(json::from_cbor(input, true, false).is_discarded());
|
||||
CHECK(json::from_cbor(input.begin(), input.end(), true, false).is_discarded());
|
||||
CHECK(json::from_cbor(input.data(), input.size(), true, false).is_discarded());
|
||||
CHECK(json::from_cbor({input.data(), input.size()}, true, false).is_discarded());
|
||||
|
||||
// a string that ends early, read through iterators that are not
|
||||
// contiguous and have to be copied from one element at a time
|
||||
const std::list<std::uint8_t> truncated_string = {0x63, 'a', 'b'};
|
||||
CHECK(json::from_cbor(truncated_string.begin(), truncated_string.end(), true, false).is_discarded());
|
||||
const std::list<std::uint8_t> complete_string = {0x63, 'a', 'b', 'c'};
|
||||
CHECK(json::from_cbor(complete_string.begin(), complete_string.end()) == "abc");
|
||||
}
|
||||
|
||||
TEST_CASE("CBOR SAX parsing stops at every event")
|
||||
{
|
||||
// Containers are opened and closed by the loop that reads them; a SAX
|
||||
// handler that rejects any event - including the end of a nested
|
||||
// container - must stop the parse right there.
|
||||
const auto count_events = [](const std::vector<std::uint8_t>& input)
|
||||
{
|
||||
int events = 0;
|
||||
while (true)
|
||||
{
|
||||
SaxCountdown scp(events);
|
||||
if (json::sax_parse(input, &scp, json::input_format_t::cbor))
|
||||
{
|
||||
return events;
|
||||
}
|
||||
++events;
|
||||
REQUIRE(events < 1000);
|
||||
}
|
||||
};
|
||||
|
||||
// 20 events: every container kind closes inside another one
|
||||
const json j = json::parse(R"({"a": [1, {"b": []}], "c": {"d": [[2]]}})");
|
||||
CHECK(count_events(json::to_cbor(j)) == 20);
|
||||
CHECK(count_events(std::vector<std::uint8_t>({0xBF, 0x61, 'a', 0x9F, 0x01, 0xFF, 0xFF})) == 6);
|
||||
}
|
||||
|
||||
TEST_CASE("CBOR indefinite-length strings do not recurse per chunk")
|
||||
{
|
||||
// Reading an indefinite-length string or byte array used to call itself
|
||||
@@ -2482,60 +2543,34 @@ TEST_CASE("CBOR roundtrips" * doctest::skip())
|
||||
{
|
||||
CAPTURE(filename)
|
||||
|
||||
std::ifstream f_json(filename);
|
||||
const json j1 = json::parse(f_json);
|
||||
const auto packed = utils::read_binary_file(filename + ".cbor");
|
||||
|
||||
{
|
||||
INFO_WITH_TEMP(filename + ": std::vector<uint8_t>");
|
||||
// parse JSON file
|
||||
std::ifstream f_json(filename);
|
||||
const json j1 = json::parse(f_json);
|
||||
|
||||
// parse CBOR file
|
||||
const auto packed = utils::read_binary_file(filename + ".cbor");
|
||||
json j2;
|
||||
CHECK_NOTHROW(j2 = json::from_cbor(packed));
|
||||
|
||||
// compare parsed JSON values
|
||||
CHECK(j1 == j2);
|
||||
}
|
||||
|
||||
{
|
||||
INFO_WITH_TEMP(filename + ": std::ifstream");
|
||||
// parse JSON file
|
||||
std::ifstream f_json(filename);
|
||||
const json j1 = json::parse(f_json);
|
||||
|
||||
// parse CBOR file
|
||||
std::ifstream f_cbor(filename + ".cbor", std::ios::binary);
|
||||
json j2;
|
||||
CHECK_NOTHROW(j2 = json::from_cbor(f_cbor));
|
||||
|
||||
// compare parsed JSON values
|
||||
CHECK(j1 == j2);
|
||||
}
|
||||
|
||||
{
|
||||
INFO_WITH_TEMP(filename + ": uint8_t* and size");
|
||||
// parse JSON file
|
||||
std::ifstream f_json(filename);
|
||||
const json j1 = json::parse(f_json);
|
||||
|
||||
// parse CBOR file
|
||||
const auto packed = utils::read_binary_file(filename + ".cbor");
|
||||
json j2;
|
||||
CHECK_NOTHROW(j2 = json::from_cbor({packed.data(), packed.size()}));
|
||||
|
||||
// compare parsed JSON values
|
||||
CHECK(j1 == j2);
|
||||
}
|
||||
|
||||
{
|
||||
INFO_WITH_TEMP(filename + ": output to output adapters");
|
||||
// parse JSON file
|
||||
std::ifstream f_json(filename);
|
||||
json const j1 = json::parse(f_json);
|
||||
|
||||
// parse CBOR file
|
||||
const auto packed = utils::read_binary_file(filename + ".cbor");
|
||||
|
||||
if (exclude_packed.count(filename) == 0u)
|
||||
{
|
||||
{
|
||||
@@ -3033,6 +3068,77 @@ TEST_CASE("Tagged values")
|
||||
CHECK_THROWS_AS(_ = json::from_cbor(v_tagged, true, true, json::cbor_tag_handler_t::error), json::parse_error);
|
||||
CHECK_THROWS_AS(_ = json::from_cbor(v_tagged, true, true, json::cbor_tag_handler_t::ignore), json::parse_error);
|
||||
}
|
||||
|
||||
SECTION("issue #5316 - cbor_tag_handler_t::store on non-binary tagged items")
|
||||
{
|
||||
// 55799({"a": 1}) -- CBOR self-describe magic followed by a map
|
||||
const std::vector<std::uint8_t> v_map{0xD9, 0xD9, 0xF7, 0xA1, 0x61, 0x61, 0x01};
|
||||
CHECK(json::from_cbor(v_map, true, true, json::cbor_tag_handler_t::ignore) == json({{"a", 1}}));
|
||||
CHECK(json::from_cbor(v_map, true, true, json::cbor_tag_handler_t::store) == json({{"a", 1}}));
|
||||
|
||||
// Tag 24 over unsigned integer 5
|
||||
const std::vector<std::uint8_t> v_int{0xD8, 0x18, 0x05};
|
||||
CHECK(json::from_cbor(v_int, true, true, json::cbor_tag_handler_t::ignore) == 5);
|
||||
CHECK(json::from_cbor(v_int, true, true, json::cbor_tag_handler_t::store) == 5);
|
||||
|
||||
// Tag 24 over text string "foo"
|
||||
const std::vector<std::uint8_t> v_str{0xD8, 0x18, 0x63, 'f', 'o', 'o'};
|
||||
CHECK(json::from_cbor(v_str, true, true, json::cbor_tag_handler_t::ignore) == "foo");
|
||||
CHECK(json::from_cbor(v_str, true, true, json::cbor_tag_handler_t::store) == "foo");
|
||||
|
||||
// Tag 24 over array [1, 2]
|
||||
const std::vector<std::uint8_t> v_arr{0xD8, 0x18, 0x82, 0x01, 0x02};
|
||||
CHECK(json::from_cbor(v_arr, true, true, json::cbor_tag_handler_t::ignore) == json({1, 2}));
|
||||
CHECK(json::from_cbor(v_arr, true, true, json::cbor_tag_handler_t::store) == json({1, 2}));
|
||||
|
||||
// Tag 24 over boolean true
|
||||
const std::vector<std::uint8_t> v_bool{0xD8, 0x18, 0xF5};
|
||||
CHECK(json::from_cbor(v_bool, true, true, json::cbor_tag_handler_t::ignore) == true);
|
||||
CHECK(json::from_cbor(v_bool, true, true, json::cbor_tag_handler_t::store) == true);
|
||||
|
||||
// Tag 24 over null
|
||||
const std::vector<std::uint8_t> v_null{0xD8, 0x18, 0xF6};
|
||||
CHECK(json::from_cbor(v_null, true, true, json::cbor_tag_handler_t::ignore) == nullptr);
|
||||
CHECK(json::from_cbor(v_null, true, true, json::cbor_tag_handler_t::store) == nullptr);
|
||||
|
||||
// Nested tags: tag 55799 over tag 24 over integer 42
|
||||
const std::vector<std::uint8_t> v_nested{0xD9, 0xD9, 0xF7, 0xD8, 0x18, 0x18, 0x2A};
|
||||
CHECK(json::from_cbor(v_nested, true, true, json::cbor_tag_handler_t::ignore) == 42);
|
||||
CHECK(json::from_cbor(v_nested, true, true, json::cbor_tag_handler_t::store) == 42);
|
||||
|
||||
// Tag 24 over byte string continues to store subtype as before
|
||||
const std::vector<std::uint8_t> v_bin{0xD8, 0x18, 0x42, 0xCA, 0xFE};
|
||||
auto j_bin_store = json::from_cbor(v_bin, true, true, json::cbor_tag_handler_t::store);
|
||||
CHECK(j_bin_store.is_binary());
|
||||
CHECK(j_bin_store.get_binary().has_subtype());
|
||||
CHECK(j_bin_store.get_binary().subtype() == 24);
|
||||
CHECK(j_bin_store.get_binary() == json::binary({0xCA, 0xFE}, 24).get_binary());
|
||||
|
||||
// Tagged values inside a container under store: [24(1), 25(h'0001')]
|
||||
const std::vector<std::uint8_t> v_container{0x82, 0xD8, 0x18, 0x01, 0xD8, 0x19, 0x42, 0x00, 0x01};
|
||||
auto j_container_store = json::from_cbor(v_container, true, true, json::cbor_tag_handler_t::store);
|
||||
CHECK(j_container_store.is_array());
|
||||
CHECK(j_container_store.size() == 2);
|
||||
CHECK(j_container_store[0] == 1);
|
||||
CHECK(j_container_store[1].is_binary());
|
||||
CHECK(j_container_store[1].get_binary().has_subtype());
|
||||
CHECK(j_container_store[1].get_binary().subtype() == 25);
|
||||
CHECK(j_container_store[1].get_binary() == json::binary({0x00, 0x01}, 25).get_binary());
|
||||
|
||||
// Tagged values as object values under store: {"a": 55799(1), "b": 24(h'01')}
|
||||
const std::vector<std::uint8_t> v_object{0xA2, 0x61, 'a', 0xD9, 0xD9, 0xF7, 0x01, 0x61, 'b', 0xD8, 0x18, 0x41, 0x01};
|
||||
CHECK(json::from_cbor(v_object, true, true, json::cbor_tag_handler_t::store) == json({{"a", 1}, {"b", json::binary({0x01}, 24)}}));
|
||||
|
||||
// two tags in a row before a byte string: the inner tag is stored
|
||||
// (this uses item_read and then the byte-string path)
|
||||
const std::vector<std::uint8_t> v_nested_byte_string{0xD8, 0x18, 0xD8, 0x19, 0x42, 0x00, 0x01};
|
||||
CHECK(json::from_cbor(v_nested_byte_string, true, true, json::cbor_tag_handler_t::store) == json::binary({0x00, 0x01}, 25));
|
||||
|
||||
// errors after a stored tag are now the same as with ignore
|
||||
json _;
|
||||
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<std::uint8_t> {0xD8, 0x18}, true, true, json::cbor_tag_handler_t::store), "[json.exception.parse_error.110] parse error at byte 3: syntax error while parsing CBOR value: unexpected end of input", json::parse_error&);
|
||||
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<std::uint8_t> {0xD8, 0x18, 0x1C}, true, true, json::cbor_tag_handler_t::store), "[json.exception.parse_error.112] parse error at byte 3: syntax error while parsing CBOR value: invalid byte: 0x1C", json::parse_error&);
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("negative integer overflow")
|
||||
|
||||
@@ -43,6 +43,13 @@ TEST_CASE("const_iterator class")
|
||||
json::const_iterator const it(&j);
|
||||
json::const_iterator it2(&j);
|
||||
it2 = it;
|
||||
|
||||
// assigning an iterator to itself leaves it unchanged
|
||||
json const a = {1, 2, 3};
|
||||
json::const_iterator it3 = a.cbegin() + 1;
|
||||
const json::const_iterator& same = it3;
|
||||
it3 = same;
|
||||
CHECK(*it3 == 2);
|
||||
}
|
||||
|
||||
SECTION("copy constructor from non-const iterator")
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include <nlohmann/json.hpp>
|
||||
using nlohmann::json;
|
||||
|
||||
#include <cfloat> // FLT_EVAL_METHOD
|
||||
#include <cstdlib> // strtod
|
||||
#include <sstream> // stringstream
|
||||
#include <string> // string
|
||||
@@ -657,3 +658,45 @@ TEST_CASE("lexer string fast path")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("parse_float_fast declines what it cannot convert exactly")
|
||||
{
|
||||
// The lexer only hands well-formed numbers to parse_float_fast, so the
|
||||
// malformed ones below can only be passed to it directly. Declining is
|
||||
// always safe: the caller then falls back to a slower, exact conversion.
|
||||
const auto fast = [](const std::string & s, double & out)
|
||||
{
|
||||
return nlohmann::detail::parse_float_fast(s.data(), s.data() + s.size(), '.', out);
|
||||
};
|
||||
double out = 0;
|
||||
|
||||
#if defined(FLT_EVAL_METHOD) && FLT_EVAL_METHOD != 0
|
||||
// without true double precision, the fast path declines everything
|
||||
CHECK_FALSE(fast("1.5", out));
|
||||
#else
|
||||
CHECK(fast("1.5", out));
|
||||
CHECK(out == 1.5);
|
||||
CHECK(fast("+2.5e1", out));
|
||||
CHECK(out == 25.0);
|
||||
CHECK(fast("-25E-1", out));
|
||||
CHECK(out == -2.5);
|
||||
CHECK(fast("1e", out));
|
||||
CHECK(out == 1.0);
|
||||
#endif
|
||||
|
||||
// not a number
|
||||
CHECK_FALSE(fast("", out));
|
||||
CHECK_FALSE(fast("-", out));
|
||||
CHECK_FALSE(fast(".", out));
|
||||
CHECK_FALSE(fast("1.2.3", out));
|
||||
CHECK_FALSE(fast("1x", out));
|
||||
CHECK_FALSE(fast("1e+", out));
|
||||
CHECK_FALSE(fast("1e1x", out));
|
||||
|
||||
// numbers that are not represented exactly on the fast path
|
||||
CHECK_FALSE(fast("12345678901234567890", out));
|
||||
CHECK_FALSE(fast("1e10000", out));
|
||||
CHECK_FALSE(fast("9007199254740993", out));
|
||||
CHECK_FALSE(fast("1e23", out));
|
||||
CHECK_FALSE(fast("1e-23", out));
|
||||
}
|
||||
|
||||
@@ -2761,7 +2761,7 @@ TEST_CASE("diagnostic positions: value lifetime, input adapters, and SAX")
|
||||
|
||||
SECTION("binary formats have no text positions")
|
||||
{
|
||||
// binary formats (CBOR, MessagePack, UBJSON, BSON, BJData) are
|
||||
// binary formats (BJData, BON8, BSON, CBOR, MessagePack, UBJSON) are
|
||||
// parsed via detail::binary_reader, which never sets
|
||||
// start_position/end_position on the values it produces (they
|
||||
// have no notion of a text offset), so every value's position
|
||||
@@ -2778,6 +2778,10 @@ TEST_CASE("diagnostic positions: value lifetime, input adapters, and SAX")
|
||||
CHECK(from_msgpack.start_pos() == std::string::npos);
|
||||
CHECK(from_msgpack.end_pos() == std::string::npos);
|
||||
|
||||
const json from_bon8 = json::from_bon8(json::to_bon8(src));
|
||||
CHECK(from_bon8.start_pos() == std::string::npos);
|
||||
CHECK(from_bon8.end_pos() == std::string::npos);
|
||||
|
||||
const json from_ubjson = json::from_ubjson(json::to_ubjson(src));
|
||||
CHECK(from_ubjson.start_pos() == std::string::npos);
|
||||
CHECK(from_ubjson.end_pos() == std::string::npos);
|
||||
|
||||
@@ -15,7 +15,13 @@
|
||||
|
||||
#include "doctest_compatibility.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include <cstdint>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#define JSON_TESTS_PRIVATE
|
||||
#include <nlohmann/json.hpp>
|
||||
@@ -359,6 +365,15 @@ TEST_CASE("lexicographical comparison operators")
|
||||
CHECK(json(1) < json(1.5));
|
||||
CHECK(json(1.5) < json(2));
|
||||
CHECK(json(2) > json(1.5));
|
||||
CHECK(json(-1) > json(-1.5));
|
||||
CHECK(json(-1.5) < json(-1));
|
||||
CHECK(json(-2) < json(-1.5));
|
||||
|
||||
// a float below the range of the integer type
|
||||
CHECK(json(0) > json(-1e30));
|
||||
CHECK(json(-1e30) < json(0));
|
||||
CHECK(json(0u) > json(-0.5));
|
||||
CHECK(json(-0.5) < json(0u));
|
||||
|
||||
// a NaN operand stays unordered against either integer kind
|
||||
CHECK_FALSE(json(1) == json(nan));
|
||||
@@ -735,3 +750,205 @@ TEST_CASE("regression #3868 - heterogeneous comparisons compile under C++20 (P24
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
namespace
|
||||
{
|
||||
// orders keys ascending or descending, as chosen when a map is created
|
||||
template<class Key>
|
||||
class directed_less
|
||||
{
|
||||
public:
|
||||
directed_less() = default;
|
||||
|
||||
explicit directed_less(const bool descending) noexcept
|
||||
: m_descending(descending)
|
||||
{}
|
||||
|
||||
bool operator()(const Key& lhs, const Key& rhs) const
|
||||
{
|
||||
return m_descending ? rhs < lhs : lhs < rhs;
|
||||
}
|
||||
|
||||
private:
|
||||
bool m_descending = false;
|
||||
};
|
||||
|
||||
// An object type that, like std::unordered_map, enumerates its entries in no
|
||||
// fixed order - ascending or descending by key, depending on how the map was
|
||||
// created - and whose operator== does not depend on that order.
|
||||
// std::unordered_map itself cannot be used here: the standard does not
|
||||
// require it to accept an incomplete mapped type such as basic_json, and
|
||||
// libstdc++ 6 to 9 as well as the EDG front ends of icpc and nvc++ reject
|
||||
// basic_json<std::unordered_map>. std::map, the default object type, works
|
||||
// with all supported compilers.
|
||||
template<class Key, class Value, class /*Compare*/, class Allocator>
|
||||
struct unordered_object_t : std::map<Key, Value, directed_less<Key>, Allocator>
|
||||
{
|
||||
using base_type = std::map<Key, Value, directed_less<Key>, Allocator>;
|
||||
using base_type::base_type;
|
||||
|
||||
friend bool operator==(const unordered_object_t& lhs, const unordered_object_t& rhs)
|
||||
{
|
||||
return lhs.size() == rhs.size() && std::all_of(lhs.begin(), lhs.end(), [&rhs](const std::pair<const Key, Value>& entry)
|
||||
{
|
||||
const auto it = rhs.find(entry.first);
|
||||
return it != rhs.end() && it->second == entry.second;
|
||||
});
|
||||
}
|
||||
|
||||
friend bool operator!=(const unordered_object_t& lhs, const unordered_object_t& rhs)
|
||||
{
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
};
|
||||
using unordered_json = nlohmann::basic_json<unordered_object_t>;
|
||||
|
||||
// the entries "0" to "9", enumerated in ascending or in descending order
|
||||
unordered_json make_unordered_object(const bool descending)
|
||||
{
|
||||
unordered_json j = unordered_json::object_t(directed_less<std::string>(descending));
|
||||
for (int i = 0; i < 10; ++i)
|
||||
{
|
||||
j[std::to_string(i)] = i;
|
||||
}
|
||||
return j;
|
||||
}
|
||||
|
||||
template<typename Json>
|
||||
Json nest(Json j, const std::size_t depth)
|
||||
{
|
||||
for (std::size_t i = 0; i < depth; ++i)
|
||||
{
|
||||
Json outer = Json::object();
|
||||
outer["x"] = std::move(j);
|
||||
j = std::move(outer);
|
||||
}
|
||||
return j;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("equality of objects whose entries have no fixed order")
|
||||
{
|
||||
// Values nested deeper than a bound are compared without the call stack,
|
||||
// entry by entry. That must agree with the object type's own operator==,
|
||||
// which for unordered_object_t (as for std::unordered_map) does not
|
||||
// depend on the order of the entries, and for ordered_map does.
|
||||
REQUIRE(make_unordered_object(true).begin().key() == "9");
|
||||
REQUIRE(make_unordered_object(false).begin().key() == "0");
|
||||
|
||||
for (const std::size_t depth : std::vector<std::size_t> {0, 200})
|
||||
{
|
||||
CAPTURE(depth);
|
||||
|
||||
const unordered_json descending = nest(make_unordered_object(true), depth);
|
||||
const unordered_json ascending = nest(make_unordered_object(false), depth);
|
||||
CHECK(descending == ascending);
|
||||
CHECK_FALSE(descending != ascending);
|
||||
|
||||
// a copy is equal to its original
|
||||
const unordered_json copy = descending; // NOLINT(performance-unnecessary-copy-initialization)
|
||||
CHECK(copy == descending);
|
||||
|
||||
// a different value, a different key, or another entry still count
|
||||
unordered_json other_value = make_unordered_object(true);
|
||||
other_value["5"] = 42;
|
||||
CHECK_FALSE(nest(other_value, depth) == ascending);
|
||||
|
||||
unordered_json other_key = make_unordered_object(true);
|
||||
other_key.erase("5");
|
||||
other_key["50"] = 5;
|
||||
CHECK_FALSE(nest(other_key, depth) == ascending);
|
||||
|
||||
unordered_json more_entries = make_unordered_object(true);
|
||||
more_entries["10"] = 10;
|
||||
CHECK_FALSE(nest(more_entries, depth) == ascending);
|
||||
CHECK_FALSE(ascending == nest(more_entries, depth));
|
||||
|
||||
// ordered_json compares its entries in sequence
|
||||
const nlohmann::ordered_json ab = nest(nlohmann::ordered_json({{"a", 1}, {"b", 2}}), depth);
|
||||
const nlohmann::ordered_json ba = nest(nlohmann::ordered_json({{"b", 2}, {"a", 1}}), depth);
|
||||
CHECK_FALSE(ab == ba);
|
||||
CHECK(ab != ba);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("containers are compared element by element")
|
||||
{
|
||||
// Containers nested deeper than a bound are compared without the call
|
||||
// stack, by code of their own; every relation is checked both at the top
|
||||
// level and below that bound.
|
||||
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;
|
||||
};
|
||||
|
||||
for (const std::size_t depth : std::vector<std::size_t> {0, 200})
|
||||
{
|
||||
CAPTURE(depth);
|
||||
|
||||
// objects with different keys
|
||||
{
|
||||
const json a = deep({{"a", 1}}, depth);
|
||||
const json b = deep({{"b", 1}}, depth);
|
||||
CHECK_FALSE(a == b);
|
||||
CHECK(a != b);
|
||||
CHECK(a < b);
|
||||
CHECK(b > a);
|
||||
CHECK_FALSE(b < a);
|
||||
#if JSON_HAS_THREE_WAY_COMPARISON
|
||||
// JSON_HAS_CPP_20 (do not remove; see note at top of file)
|
||||
CHECK((a <=> b) == std::partial_ordering::less); // *NOPAD*
|
||||
CHECK((b <=> a) == std::partial_ordering::greater); // *NOPAD*
|
||||
CHECK((a <=> a) == std::partial_ordering::equivalent); // *NOPAD*
|
||||
#endif
|
||||
}
|
||||
|
||||
// a container that is a prefix of the other one
|
||||
{
|
||||
// the one that runs out of elements first is the smaller one
|
||||
const json shorter = deep({1}, depth);
|
||||
const json longer = deep({1, 2}, depth);
|
||||
CHECK(shorter < longer);
|
||||
CHECK(longer > shorter);
|
||||
CHECK_FALSE(longer < shorter);
|
||||
CHECK_FALSE(shorter == longer);
|
||||
|
||||
const json smaller_object = deep({{"a", 1}}, depth);
|
||||
const json larger_object = deep({{"a", 1}, {"b", 2}}, depth);
|
||||
CHECK(smaller_object < larger_object);
|
||||
CHECK(larger_object > smaller_object);
|
||||
CHECK_FALSE(smaller_object == larger_object);
|
||||
#if JSON_HAS_THREE_WAY_COMPARISON
|
||||
// JSON_HAS_CPP_20 (do not remove; see note at top of file)
|
||||
CHECK((shorter <=> longer) == std::partial_ordering::less); // *NOPAD*
|
||||
CHECK((longer <=> shorter) == std::partial_ordering::greater); // *NOPAD*
|
||||
#endif
|
||||
}
|
||||
|
||||
// elements that cannot be ordered
|
||||
{
|
||||
const double nan = std::numeric_limits<double>::quiet_NaN();
|
||||
const json lhs = deep({nan, 1}, depth);
|
||||
const json rhs = deep({nan, 2}, depth);
|
||||
|
||||
CHECK_FALSE(lhs == lhs);
|
||||
CHECK_FALSE(rhs < lhs);
|
||||
#if JSON_HAS_THREE_WAY_COMPARISON
|
||||
// JSON_HAS_CPP_20 (do not remove; see note at top of file)
|
||||
// operator<=> stops there, as std::lexicographical_compare_three_way
|
||||
// does, and operator< is derived from it
|
||||
CHECK((lhs <=> rhs) == std::partial_ordering::unordered); // *NOPAD*
|
||||
CHECK_FALSE(lhs < rhs);
|
||||
#else
|
||||
// operator< skips a pair of elements that cannot be ordered, as
|
||||
// std::lexicographical_compare does, and the next pair decides
|
||||
CHECK(lhs < rhs);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,6 +49,16 @@ TEST_CASE("binary type whose value type is not std::uint8_t")
|
||||
CHECK(char_binary_json::binary({}).dump() == R"({"bytes":[],"subtype":null})");
|
||||
}
|
||||
|
||||
SECTION("a value is converted to the binary type if it is binary or an array")
|
||||
{
|
||||
const std::vector<char> chars{'\0', '\x01', '\x7F'};
|
||||
CHECK(char_binary_json::binary(chars).get<std::vector<char>>() == chars);
|
||||
CHECK(char_binary_json({0, 1, 127}).get<std::vector<char>>() == chars);
|
||||
CHECK_THROWS_WITH_AS(char_binary_json(1).get<std::vector<char>>(),
|
||||
"[json.exception.type_error.302] type must be binary or array, but is number",
|
||||
char_binary_json::type_error&);
|
||||
}
|
||||
|
||||
SECTION("the default binary type is unchanged")
|
||||
{
|
||||
CHECK(nlohmann::json::binary({0, 1, 255}, 42).dump() == R"({"bytes":[0,1,255],"subtype":42})");
|
||||
@@ -74,6 +84,8 @@ TEST_CASE("binary type whose value type is not std::uint8_t")
|
||||
|
||||
// UBJSON has no binary type, so binary values are written as an array
|
||||
CHECK(byte_binary_json::from_ubjson(byte_binary_json::to_ubjson(j)) == byte_binary_json({0, 1, 255}));
|
||||
// the same holds for BON8
|
||||
CHECK(byte_binary_json::from_bon8(byte_binary_json::to_bon8(j)) == byte_binary_json({0, 1, 255}));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -297,6 +297,7 @@ TEST_CASE("object type without key_compare")
|
||||
const auto j = no_key_compare_json::parse(R"({"a":[1,2,3],"b":"x"})");
|
||||
CHECK(no_key_compare_json::from_cbor(no_key_compare_json::to_cbor(j)) == j);
|
||||
CHECK(no_key_compare_json::from_msgpack(no_key_compare_json::to_msgpack(j)) == j);
|
||||
CHECK(no_key_compare_json::from_bon8(no_key_compare_json::to_bon8(j)) == j);
|
||||
}
|
||||
|
||||
SECTION("flatten and unflatten")
|
||||
|
||||
@@ -156,3 +156,38 @@ TEST_CASE("Better diagnostics with positions")
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("values read from a binary format have no positions")
|
||||
{
|
||||
// only the JSON lexer knows where a value started and ended
|
||||
const json source = {{"a", {1, "x", json::binary({1})}}, {"b", {{"c", true}}}, {"d", nullptr}, {"e", 1.5}};
|
||||
const std::vector<std::uint8_t> cbor = json::to_cbor(source);
|
||||
|
||||
const auto check_no_positions = [](const json & j)
|
||||
{
|
||||
CHECK(j.start_pos() == std::string::npos);
|
||||
CHECK(j.end_pos() == std::string::npos);
|
||||
CHECK(j.at("a").start_pos() == std::string::npos);
|
||||
CHECK(j.at("a").at(1).end_pos() == std::string::npos);
|
||||
CHECK(j.at("b").at("c").start_pos() == std::string::npos);
|
||||
};
|
||||
|
||||
SECTION("DOM parser")
|
||||
{
|
||||
const json j = json::from_cbor(cbor);
|
||||
CHECK(j == source);
|
||||
check_no_positions(j);
|
||||
}
|
||||
|
||||
SECTION("DOM parser with a callback")
|
||||
{
|
||||
json j;
|
||||
nlohmann::detail::json_sax_dom_callback_parser<json, decltype(nlohmann::detail::input_adapter(cbor))> sdp(j, [](int /*unused*/, json::parse_event_t /*unused*/, const json& /*unused*/) noexcept
|
||||
{
|
||||
return true;
|
||||
});
|
||||
CHECK(json::sax_parse(cbor, &sdp, json::input_format_t::cbor));
|
||||
CHECK(j == source);
|
||||
check_no_positions(j);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1517,6 +1517,16 @@ TEST_CASE_TEMPLATE("element access 2 (throwing tests)", Json, nlohmann::json, nl
|
||||
CHECK(j.value("/not/existing"_json_pointer, Json({{"foo", "bar"}})) == Json({{"foo", "bar"}}));
|
||||
CHECK(j.value("/not/existing"_json_pointer, Json({10, 100})) == Json({10, 100}));
|
||||
|
||||
// an array index that is out of range, too large to be
|
||||
// represented, or "-", and a token below a scalar
|
||||
CHECK(j.value("/array/3"_json_pointer, 2) == 2);
|
||||
CHECK(j.value("/array/-"_json_pointer, 2) == 2);
|
||||
CHECK(j.value("/array/99999999999999999999999999"_json_pointer, 2) == 2);
|
||||
CHECK(j.value("/integer/0"_json_pointer, 2) == 2);
|
||||
CHECK(j.value("/string/x"_json_pointer, 2) == 2);
|
||||
CHECK(j.value("/null/x"_json_pointer, 2) == 2);
|
||||
CHECK(j.value("/array/0"_json_pointer, 2) == 1);
|
||||
|
||||
CHECK(j_const.value("/not/existing"_json_pointer, 2) == 2);
|
||||
CHECK(j_const.value("/not/existing"_json_pointer, 2u) == 2u);
|
||||
CHECK(j_const.value("/not/existing"_json_pointer, false) == false);
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
// __ _____ _____ _____
|
||||
// __| | __| | | | 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.
|
||||
// The regression below only showed on C++17, so build this file for every
|
||||
// standard like the other regression tests:
|
||||
// JSON_HAS_CPP_17 JSON_HAS_CPP_20 (do not remove; see note at top of file)
|
||||
|
||||
#include "doctest_compatibility.h"
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
using json = nlohmann::json;
|
||||
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
// for #4825 - explicitly instantiating basic_json must compile; this
|
||||
// forces instantiation of binary_writer::write_bjdata_ndarray, whose
|
||||
// static_cast<string_t> was ambiguous under explicit instantiation on
|
||||
// C++17. Merely compiling this translation unit is the regression test.
|
||||
//
|
||||
// The instantiation compiles every member function, so it has a file of its
|
||||
// own: in unit-regression3.cpp it made the object too large for the MinGW
|
||||
// linker to relocate (see #5511).
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
template class nlohmann::basic_json<>;
|
||||
|
||||
TEST_CASE("explicit instantiation of basic_json (#4825)")
|
||||
{
|
||||
const json j = {1, "two", 3.0};
|
||||
CHECK(j.size() == 3);
|
||||
CHECK(json::from_bjdata(json::to_bjdata(j)) == j);
|
||||
}
|
||||
@@ -1904,3 +1904,98 @@ TEST_CASE("JSON patch: diff of deeply nested values")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("JSON patch - every operation on ordered_json")
|
||||
{
|
||||
using nlohmann::ordered_json;
|
||||
|
||||
const ordered_json doc = {{"foo", "bar"}, {"arr", {1, 2, 3}}, {"obj", {{"a", 1}}}};
|
||||
|
||||
SECTION("successful operations")
|
||||
{
|
||||
const ordered_json patch = ordered_json::parse(R"([
|
||||
{"op": "add", "path": "/obj/b", "value": 2},
|
||||
{"op": "add", "path": "/arr/1", "value": 9},
|
||||
{"op": "add", "path": "/arr/-", "value": 4},
|
||||
{"op": "remove", "path": "/arr/0"},
|
||||
{"op": "remove", "path": "/obj/a"},
|
||||
{"op": "replace", "path": "/foo", "value": "baz"},
|
||||
{"op": "move", "from": "/foo", "path": "/moved"},
|
||||
{"op": "copy", "from": "/obj", "path": "/copied"},
|
||||
{"op": "test", "path": "/copied/b", "value": 2}
|
||||
])");
|
||||
|
||||
const ordered_json expected = ordered_json::parse(R"({
|
||||
"arr": [9, 2, 3, 4], "obj": {"b": 2}, "moved": "baz", "copied": {"b": 2}
|
||||
})");
|
||||
|
||||
CHECK(doc.patch(patch) == expected);
|
||||
|
||||
// adding to the root replaces the document
|
||||
CHECK(doc.patch(ordered_json::parse(R"([{"op": "add", "path": "", "value": [1]}])")) == ordered_json({1}));
|
||||
}
|
||||
|
||||
SECTION("failing operations")
|
||||
{
|
||||
ordered_json _;
|
||||
#if JSON_DIAGNOSTICS
|
||||
CHECK_THROWS_WITH_AS(_ = doc.patch(ordered_json::parse(R"([{"op": "add", "path": "/arr/4", "value": 1}])")),
|
||||
"[json.exception.out_of_range.401] (/arr) array index 4 is out of range", ordered_json::out_of_range&);
|
||||
#else
|
||||
CHECK_THROWS_WITH_AS(_ = doc.patch(ordered_json::parse(R"([{"op": "add", "path": "/arr/4", "value": 1}])")),
|
||||
"[json.exception.out_of_range.401] array index 4 is out of range", ordered_json::out_of_range&);
|
||||
#endif
|
||||
CHECK_THROWS_WITH_AS(_ = doc.patch(ordered_json::parse(R"([{"op": "add", "path": "/nope/x", "value": 1}])")),
|
||||
"[json.exception.out_of_range.403] key 'nope' not found", ordered_json::out_of_range&);
|
||||
CHECK_THROWS_WITH_AS(_ = doc.patch(ordered_json::parse(R"([{"op": "remove", "path": "/obj/nope"}])")),
|
||||
"[json.exception.out_of_range.403] key 'nope' not found", ordered_json::out_of_range&);
|
||||
#if JSON_DIAGNOSTICS
|
||||
CHECK_THROWS_WITH_AS(_ = doc.patch(ordered_json::parse(R"([{"op": "remove", "path": "/arr/3"}])")),
|
||||
"[json.exception.out_of_range.401] (/arr) array index 3 is out of range", ordered_json::out_of_range&);
|
||||
#else
|
||||
CHECK_THROWS_WITH_AS(_ = doc.patch(ordered_json::parse(R"([{"op": "remove", "path": "/arr/3"}])")),
|
||||
"[json.exception.out_of_range.401] array index 3 is out of range", ordered_json::out_of_range&);
|
||||
#endif
|
||||
#if JSON_DIAGNOSTICS
|
||||
CHECK_THROWS_WITH_AS(_ = doc.patch(ordered_json::parse(R"([{"op": "test", "path": "/foo", "value": "qux"}])")),
|
||||
"[json.exception.other_error.501] (/0) unsuccessful: {\"op\":\"test\",\"path\":\"/foo\",\"value\":\"qux\"}", ordered_json::other_error&);
|
||||
#elif JSON_DIAGNOSTIC_POSITIONS
|
||||
CHECK_THROWS_WITH_AS(_ = doc.patch(ordered_json::parse(R"([{"op": "test", "path": "/foo", "value": "qux"}])")),
|
||||
"[json.exception.other_error.501] (bytes 1-47) unsuccessful: {\"op\":\"test\",\"path\":\"/foo\",\"value\":\"qux\"}", ordered_json::other_error&);
|
||||
#else
|
||||
CHECK_THROWS_WITH_AS(_ = doc.patch(ordered_json::parse(R"([{"op": "test", "path": "/foo", "value": "qux"}])")),
|
||||
"[json.exception.other_error.501] unsuccessful: {\"op\":\"test\",\"path\":\"/foo\",\"value\":\"qux\"}", ordered_json::other_error&);
|
||||
#endif
|
||||
#if JSON_DIAGNOSTICS
|
||||
CHECK_THROWS_WITH_AS(_ = doc.patch(ordered_json::parse(R"([{"op": "add", "path": "/foo"}])")),
|
||||
"[json.exception.parse_error.105] parse error: (/0) operation 'add' must have member 'value'", ordered_json::parse_error&);
|
||||
#elif JSON_DIAGNOSTIC_POSITIONS
|
||||
CHECK_THROWS_WITH_AS(_ = doc.patch(ordered_json::parse(R"([{"op": "add", "path": "/foo"}])")),
|
||||
"[json.exception.parse_error.105] parse error: (bytes 1-30) operation 'add' must have member 'value'", ordered_json::parse_error&);
|
||||
#else
|
||||
CHECK_THROWS_WITH_AS(_ = doc.patch(ordered_json::parse(R"([{"op": "add", "path": "/foo"}])")),
|
||||
"[json.exception.parse_error.105] parse error: operation 'add' must have member 'value'", ordered_json::parse_error&);
|
||||
#endif
|
||||
CHECK_THROWS_WITH_AS(_ = doc.patch(ordered_json::parse(R"([{"op": "move", "from": "/obj", "path": "/obj/a/b"}])")),
|
||||
"[json.exception.out_of_range.414] cannot move value: 'from' path '/obj' is a proper prefix of 'path' '/obj/a/b'", ordered_json::out_of_range&);
|
||||
}
|
||||
|
||||
SECTION("diff reproduces the target")
|
||||
{
|
||||
const ordered_json source = {{"a", 1}, {"b", 2}, {"c", {{"x", 1}}}, {"l", {1, 2, 3}}};
|
||||
const std::vector<ordered_json> targets =
|
||||
{
|
||||
// a key removed, a key added, a nested change, a shorter array
|
||||
{{"a", 1}, {"c", {{"x", 2}}}, {"l", {1}}, {"d", 4}},
|
||||
// the same keys in another order
|
||||
{{"c", {{"x", 1}}}, {"a", 1}, {"b", 2}, {"l", {1, 2, 3}}},
|
||||
// new keys ahead of the common ones
|
||||
{{"new", true}, {"a", 1}, {"b", 3}, {"c", {{"x", 1}}}, {"l", {1, 2, 3}}},
|
||||
};
|
||||
for (const auto& target : targets)
|
||||
{
|
||||
CAPTURE(target.dump());
|
||||
CHECK(source.patch(ordered_json::diff(source, target)) == target);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -872,3 +872,16 @@ TEST_CASE("JSON pointers")
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
TEST_CASE("unescaping keeps a '~' that does not start an escape sequence")
|
||||
{
|
||||
// the parser of a JSON pointer rejects such reference tokens before it
|
||||
// unescapes them, so this is only reachable by calling unescape directly
|
||||
std::string s = "a~2b~";
|
||||
nlohmann::detail::unescape(s);
|
||||
CHECK(s == "a~2b~");
|
||||
|
||||
s = "~0~1~";
|
||||
nlohmann::detail::unescape(s);
|
||||
CHECK(s == "~/~");
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ TEST_CASE("tests on very large JSONs")
|
||||
{
|
||||
SECTION("issue #1419 - Segmentation fault (stack overflow) due to unbounded recursion")
|
||||
{
|
||||
const auto depth = 5000000;
|
||||
const auto depth = 500000;
|
||||
|
||||
std::string s(static_cast<std::size_t>(2 * depth), '[');
|
||||
std::fill(s.begin() + depth, s.end(), ']');
|
||||
|
||||
@@ -158,6 +158,17 @@ TEST_CASE("locale-dependent test (LC_NUMERIC=de_DE)")
|
||||
json::sax_parse("12.34", &sax);
|
||||
CHECK(sax.float_string_copy == "12.34");
|
||||
}
|
||||
|
||||
SECTION("serializing a long double")
|
||||
{
|
||||
// a floating-point type that is not a float or a double is written
|
||||
// with snprintf, whose locale-specific decimal point and thousands
|
||||
// separator are undone afterwards
|
||||
using long_double_json = nlohmann::basic_json<std::map, std::vector, std::string, bool, std::int64_t, std::uint64_t, long double>;
|
||||
CHECK(long_double_json(12345.5L).dump() == "12345.5");
|
||||
CHECK(long_double_json(1.0L).dump() == "1.0");
|
||||
CHECK(long_double_json(-0.25L).dump() == "-0.25");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -345,3 +345,32 @@ TEST_CASE("JSON Merge Patch on deeply nested values")
|
||||
CHECK(p->at("x") == 1);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("JSON Merge Patch and update on ordered_json")
|
||||
{
|
||||
using nlohmann::ordered_json;
|
||||
|
||||
SECTION("merge_patch")
|
||||
{
|
||||
ordered_json target = ordered_json::parse(R"({"a": {"b": 1, "c": 2}, "d": 3, "e": [1]})");
|
||||
target.merge_patch(ordered_json::parse(R"({"a": {"b": null, "f": 4}, "d": {"x": {"y": null}}, "e": null, "g": {"h": 5}})"));
|
||||
CHECK(target == ordered_json::parse(R"({"a": {"c": 2, "f": 4}, "d": {"x": {}}, "g": {"h": 5}})"));
|
||||
|
||||
// a patch that is not an object replaces the target
|
||||
target.merge_patch(ordered_json({1, 2}));
|
||||
CHECK(target == ordered_json({1, 2}));
|
||||
// an object patch turns a target that is not an object into one
|
||||
target.merge_patch(ordered_json::parse(R"({"k": {"l": null}})"));
|
||||
CHECK(target == ordered_json::parse(R"({"k": {}})"));
|
||||
}
|
||||
|
||||
SECTION("update with merge_objects")
|
||||
{
|
||||
ordered_json target = ordered_json::parse(R"({"a": {"b": 1, "c": {"d": 2}}, "e": 3})");
|
||||
target.update(ordered_json::parse(R"({"a": {"c": {"x": 1}, "f": 4}, "e": {"y": 5}, "g": 6})"), true);
|
||||
CHECK(target == ordered_json::parse(R"({"a": {"b": 1, "c": {"d": 2, "x": 1}, "f": 4}, "e": {"y": 5}, "g": 6})"));
|
||||
|
||||
target.update(ordered_json::parse(R"({"a": 1})"), false);
|
||||
CHECK(target == ordered_json::parse(R"({"a": 1, "e": {"y": 5}, "g": 6})"));
|
||||
}
|
||||
}
|
||||
|
||||
+292
-33
@@ -14,6 +14,7 @@ using nlohmann::json;
|
||||
using namespace nlohmann::literals; // NOLINT(google-build-using-namespace)
|
||||
#endif
|
||||
|
||||
#include <cstdint> // SIZE_MAX, UINT32_MAX
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <iomanip>
|
||||
@@ -255,7 +256,7 @@ TEST_CASE("MessagePack")
|
||||
|
||||
SECTION("256..65535 (int 16)")
|
||||
{
|
||||
for (size_t i = 256; i <= 65535; ++i)
|
||||
for (size_t i = 256; i <= 65535; i = utils::next_integer_sample(i, static_cast<size_t>(65535), static_cast<size_t>(7)))
|
||||
{
|
||||
CAPTURE(i)
|
||||
|
||||
@@ -440,7 +441,7 @@ TEST_CASE("MessagePack")
|
||||
|
||||
SECTION("-32768..-129 (int 16)")
|
||||
{
|
||||
for (int16_t i = -32768; i <= static_cast<std::int16_t>(-129); ++i)
|
||||
for (int16_t i = -32768; i <= static_cast<std::int16_t>(-129); i = utils::next_integer_sample(i, static_cast<int16_t>(-129), static_cast<int16_t>(7)))
|
||||
{
|
||||
CAPTURE(i)
|
||||
|
||||
@@ -646,7 +647,7 @@ TEST_CASE("MessagePack")
|
||||
|
||||
SECTION("256..65535 (uint 16)")
|
||||
{
|
||||
for (size_t i = 256; i <= 65535; ++i)
|
||||
for (size_t i = 256; i <= 65535; i = utils::next_integer_sample(i, static_cast<size_t>(65535), static_cast<size_t>(7)))
|
||||
{
|
||||
CAPTURE(i)
|
||||
|
||||
@@ -1780,6 +1781,44 @@ TEST_CASE("MessagePack nesting does not consume the call stack")
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("MessagePack input that cannot be read is discarded by every overload")
|
||||
{
|
||||
std::vector<std::uint8_t> input = json::to_msgpack(json({{"a", {1, 2}}}));
|
||||
input.pop_back();
|
||||
|
||||
json _;
|
||||
CHECK_THROWS_AS(_ = json::from_msgpack(input.begin(), input.end()), json::parse_error&);
|
||||
CHECK(json::from_msgpack(input, true, false).is_discarded());
|
||||
CHECK(json::from_msgpack(input.begin(), input.end(), true, false).is_discarded());
|
||||
CHECK(json::from_msgpack(input.data(), input.size(), true, false).is_discarded());
|
||||
CHECK(json::from_msgpack({input.data(), input.size()}, true, false).is_discarded());
|
||||
}
|
||||
|
||||
TEST_CASE("MessagePack SAX parsing stops at every event")
|
||||
{
|
||||
// Containers are opened and closed by the loop that reads them; a SAX
|
||||
// handler that rejects any event - including the end of a nested
|
||||
// container - must stop the parse right there.
|
||||
const auto count_events = [](const std::vector<std::uint8_t>& input)
|
||||
{
|
||||
int events = 0;
|
||||
while (true)
|
||||
{
|
||||
SaxCountdown scp(events);
|
||||
if (json::sax_parse(input, &scp, json::input_format_t::msgpack))
|
||||
{
|
||||
return events;
|
||||
}
|
||||
++events;
|
||||
REQUIRE(events < 1000);
|
||||
}
|
||||
};
|
||||
|
||||
// 20 events: every container kind closes inside another one
|
||||
const json j = json::parse(R"({"a": [1, {"b": []}], "c": {"d": [[2]]}})");
|
||||
CHECK(count_events(json::to_msgpack(j)) == 20);
|
||||
}
|
||||
|
||||
TEST_CASE("single MessagePack roundtrip")
|
||||
{
|
||||
SECTION("sample.json")
|
||||
@@ -2004,60 +2043,34 @@ TEST_CASE("MessagePack roundtrips" * doctest::skip())
|
||||
{
|
||||
CAPTURE(filename)
|
||||
|
||||
std::ifstream f_json(filename);
|
||||
const json j1 = json::parse(f_json);
|
||||
auto packed = utils::read_binary_file(filename + ".msgpack");
|
||||
|
||||
{
|
||||
INFO_WITH_TEMP(filename + ": std::vector<uint8_t>");
|
||||
// parse JSON file
|
||||
std::ifstream f_json(filename);
|
||||
const json j1 = json::parse(f_json);
|
||||
|
||||
// parse MessagePack file
|
||||
auto packed = utils::read_binary_file(filename + ".msgpack");
|
||||
json j2;
|
||||
CHECK_NOTHROW(j2 = json::from_msgpack(packed));
|
||||
|
||||
// compare parsed JSON values
|
||||
CHECK(j1 == j2);
|
||||
}
|
||||
|
||||
{
|
||||
INFO_WITH_TEMP(filename + ": std::ifstream");
|
||||
// parse JSON file
|
||||
std::ifstream f_json(filename);
|
||||
const json j1 = json::parse(f_json);
|
||||
|
||||
// parse MessagePack file
|
||||
std::ifstream f_msgpack(filename + ".msgpack", std::ios::binary);
|
||||
json j2;
|
||||
CHECK_NOTHROW(j2 = json::from_msgpack(f_msgpack));
|
||||
|
||||
// compare parsed JSON values
|
||||
CHECK(j1 == j2);
|
||||
}
|
||||
|
||||
{
|
||||
INFO_WITH_TEMP(filename + ": uint8_t* and size");
|
||||
// parse JSON file
|
||||
std::ifstream f_json(filename);
|
||||
const json j1 = json::parse(f_json);
|
||||
|
||||
// parse MessagePack file
|
||||
auto packed = utils::read_binary_file(filename + ".msgpack");
|
||||
json j2;
|
||||
CHECK_NOTHROW(j2 = json::from_msgpack({packed.data(), packed.size()}));
|
||||
|
||||
// compare parsed JSON values
|
||||
CHECK(j1 == j2);
|
||||
}
|
||||
|
||||
{
|
||||
INFO_WITH_TEMP(filename + ": output to output adapters");
|
||||
// parse JSON file
|
||||
std::ifstream f_json(filename);
|
||||
json const j1 = json::parse(f_json);
|
||||
|
||||
// parse MessagePack file
|
||||
auto packed = utils::read_binary_file(filename + ".msgpack");
|
||||
|
||||
if (exclude_packed.count(filename) == 0u)
|
||||
{
|
||||
{
|
||||
@@ -2150,3 +2163,249 @@ TEST_CASE("MessagePack with std::byte")
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// the fake sizes below do not fit into a 32-bit std::size_t
|
||||
#if SIZE_MAX > UINT32_MAX
|
||||
template<typename T, typename A = std::allocator<T>>
|
||||
struct huge_array : std::vector<T, A>
|
||||
{
|
||||
using base = std::vector<T, A>;
|
||||
using base::base;
|
||||
|
||||
bool fake_size = false;
|
||||
|
||||
std::size_t size() const noexcept
|
||||
{
|
||||
if (fake_size)
|
||||
{
|
||||
return (std::numeric_limits<std::uint32_t>::max)() + 1ULL;
|
||||
}
|
||||
|
||||
return base::size();
|
||||
}
|
||||
};
|
||||
|
||||
using huge_array_json = nlohmann::basic_json <
|
||||
std::map, huge_array, std::string, bool, std::int64_t, std::uint64_t,
|
||||
double, std::allocator, nlohmann::adl_serializer,
|
||||
std::vector<std::uint8_t>, void >;
|
||||
|
||||
TEST_CASE("MessagePack Size above uint32 for array")
|
||||
{
|
||||
huge_array_json j = huge_array_json::array();
|
||||
|
||||
j.push_back(1);
|
||||
j.push_back(2);
|
||||
j.push_back(3);
|
||||
|
||||
auto& array = j.get_ref<huge_array_json::array_t&>();
|
||||
array.fake_size = true;
|
||||
|
||||
CHECK_THROWS_WITH_AS(
|
||||
huge_array_json::to_msgpack(j),
|
||||
"[json.exception.out_of_range.412] MessagePack length 4294967296 exceeds maximum of 4294967295",
|
||||
json::out_of_range&);
|
||||
|
||||
array.fake_size = false;
|
||||
}
|
||||
|
||||
template<typename K, typename V,
|
||||
typename C = std::less<K>,
|
||||
typename A = std::allocator<std::pair<const K, V>>>
|
||||
struct huge_map : std::map<K, V, C, A>
|
||||
{
|
||||
using base = std::map<K, V, C, A>;
|
||||
using base::base;
|
||||
|
||||
bool fake_size = false;
|
||||
|
||||
std::size_t size() const noexcept
|
||||
{
|
||||
if (fake_size)
|
||||
{
|
||||
return static_cast<std::size_t>(UINT32_MAX) + 1ULL;
|
||||
}
|
||||
|
||||
return base::size();
|
||||
}
|
||||
};
|
||||
|
||||
using huge_object_json = nlohmann::basic_json <
|
||||
huge_map,
|
||||
std::vector,
|
||||
std::string,
|
||||
bool,
|
||||
std::int64_t,
|
||||
std::uint64_t,
|
||||
double,
|
||||
std::allocator,
|
||||
nlohmann::adl_serializer,
|
||||
std::vector<std::uint8_t>,
|
||||
void >;
|
||||
|
||||
TEST_CASE("MessagePack Size above uint32 for object")
|
||||
{
|
||||
|
||||
huge_object_json j = huge_object_json::object();
|
||||
|
||||
j["one"] = 1;
|
||||
j["two"] = 2;
|
||||
|
||||
auto& object = j.get_ref<huge_object_json::object_t&>();
|
||||
object.fake_size = true;
|
||||
|
||||
CHECK_THROWS_WITH_AS(
|
||||
huge_object_json::to_msgpack(j),
|
||||
"[json.exception.out_of_range.412] MessagePack length 4294967296 exceeds maximum of 4294967295",
|
||||
json::out_of_range&);
|
||||
|
||||
object.fake_size = false;
|
||||
}
|
||||
|
||||
struct huge_string : std::string
|
||||
{
|
||||
using std::string::string;
|
||||
|
||||
std::size_t size() const noexcept
|
||||
{
|
||||
return static_cast<std::size_t>(UINT32_MAX) + 1ULL;
|
||||
}
|
||||
};
|
||||
|
||||
using huge_string_json = nlohmann::basic_json <
|
||||
std::map,
|
||||
std::vector,
|
||||
huge_string,
|
||||
bool,
|
||||
std::int64_t,
|
||||
std::uint64_t,
|
||||
double,
|
||||
std::allocator,
|
||||
nlohmann::adl_serializer,
|
||||
std::vector<std::uint8_t>,
|
||||
void >;
|
||||
|
||||
TEST_CASE("MessagePack Size above uint32 for string")
|
||||
{
|
||||
|
||||
huge_string_json j = "hello";
|
||||
|
||||
CHECK_THROWS_WITH_AS(
|
||||
huge_string_json::to_msgpack(j),
|
||||
"[json.exception.out_of_range.412] MessagePack length 4294967296 exceeds maximum of 4294967295",
|
||||
json::out_of_range&);
|
||||
}
|
||||
|
||||
struct huge_binary : std::vector<std::uint8_t>
|
||||
{
|
||||
using std::vector<std::uint8_t>::vector;
|
||||
|
||||
std::size_t size() const noexcept
|
||||
{
|
||||
return static_cast<std::size_t>(UINT32_MAX) + 1ULL;
|
||||
}
|
||||
};
|
||||
|
||||
using huge_binary_json = nlohmann::basic_json <
|
||||
std::map,
|
||||
std::vector,
|
||||
std::string,
|
||||
bool,
|
||||
std::int64_t,
|
||||
std::uint64_t,
|
||||
double,
|
||||
std::allocator,
|
||||
nlohmann::adl_serializer,
|
||||
huge_binary,
|
||||
void >;
|
||||
|
||||
TEST_CASE("MessagePack Size above uint32 for binary")
|
||||
{
|
||||
|
||||
huge_binary_json j = huge_binary_json::binary(huge_binary{});
|
||||
|
||||
j.get_binary().push_back(0x01);
|
||||
j.get_binary().push_back(0x02);
|
||||
|
||||
CHECK_THROWS_WITH_AS(
|
||||
huge_binary_json::to_msgpack(j),
|
||||
"[json.exception.out_of_range.412] MessagePack length 4294967296 exceeds maximum of 4294967295",
|
||||
json::out_of_range&);
|
||||
}
|
||||
#endif
|
||||
|
||||
namespace
|
||||
{
|
||||
// types that report a size beyond UINT32_MAX without allocating that much
|
||||
// memory, so the MessagePack length limit can be tested cheaply; see the
|
||||
// similar types in unit-bson.cpp
|
||||
std::size_t beyond_uint32_size()
|
||||
{
|
||||
return static_cast<std::size_t>((std::numeric_limits<std::uint32_t>::max)()) + 1;
|
||||
}
|
||||
|
||||
class beyond_uint32_binary_t : public std::vector<std::uint8_t>
|
||||
{
|
||||
public:
|
||||
using std::vector<std::uint8_t>::vector;
|
||||
|
||||
size_type size() const noexcept // NOLINT(readability-convert-member-functions-to-static)
|
||||
{
|
||||
return beyond_uint32_size();
|
||||
}
|
||||
};
|
||||
|
||||
// with clang and libstdc++ 10, the std::filesystem::path conversion that
|
||||
// C++17 builds consider for every string type is ambiguous for a class
|
||||
// derived from std::string, so the string case is not tested there
|
||||
#if !(defined(__clang__) && defined(_GLIBCXX_RELEASE) && _GLIBCXX_RELEASE < 11)
|
||||
#define JSON_TEST_BEYOND_UINT32_STRING 1
|
||||
#endif
|
||||
|
||||
#ifdef JSON_TEST_BEYOND_UINT32_STRING
|
||||
class beyond_uint32_string_t : public std::string
|
||||
{
|
||||
public:
|
||||
using std::string::string;
|
||||
|
||||
size_type size() const noexcept // NOLINT(readability-convert-member-functions-to-static)
|
||||
{
|
||||
return beyond_uint32_size();
|
||||
}
|
||||
};
|
||||
|
||||
using beyond_uint32_string_json = nlohmann::basic_json <
|
||||
std::map, std::vector, beyond_uint32_string_t, bool, std::int64_t, std::uint64_t,
|
||||
double, std::allocator, nlohmann::adl_serializer, std::vector<std::uint8_t>, void >;
|
||||
#endif
|
||||
|
||||
using beyond_uint32_binary_json = nlohmann::basic_json <
|
||||
std::map, std::vector, std::string, bool, std::int64_t, std::uint64_t,
|
||||
double, std::allocator, nlohmann::adl_serializer, beyond_uint32_binary_t, void >;
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("MessagePack lengths beyond UINT32_MAX cannot be serialized")
|
||||
{
|
||||
// MessagePack stores the length of a string, binary value, array, or
|
||||
// object in at most 32 bits; a larger one used to be written without any
|
||||
// length at all
|
||||
#if SIZE_MAX > UINT32_MAX
|
||||
{
|
||||
const char* const expected = "[json.exception.out_of_range.412] MessagePack length 4294967296 exceeds maximum of 4294967295";
|
||||
|
||||
const beyond_uint32_binary_json binary = beyond_uint32_binary_json::binary(beyond_uint32_binary_t{});
|
||||
CHECK_THROWS_WITH_AS(beyond_uint32_binary_json::to_msgpack(binary), expected, beyond_uint32_binary_json::out_of_range&);
|
||||
|
||||
const beyond_uint32_binary_json ext = beyond_uint32_binary_json::binary(beyond_uint32_binary_t{}, 42);
|
||||
CHECK_THROWS_WITH_AS(beyond_uint32_binary_json::to_msgpack(ext), expected, beyond_uint32_binary_json::out_of_range&);
|
||||
|
||||
#ifdef JSON_TEST_BEYOND_UINT32_STRING
|
||||
// created from its type rather than from a beyond_uint32_string_t:
|
||||
// that would consider the std::filesystem::path conversion, which
|
||||
// libstdc++ 10 cannot decide for a class derived from std::string
|
||||
const beyond_uint32_string_json string(beyond_uint32_string_json::value_t::string);
|
||||
CHECK_THROWS_WITH_AS(beyond_uint32_string_json::to_msgpack(string), expected, beyond_uint32_string_json::out_of_range&);
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -326,6 +326,15 @@ TEST_CASE("ordered_json across binary formats")
|
||||
CHECK(collect_keys(restored) == original_keys);
|
||||
CHECK(collect_keys(restored["mango"]) == original_mango_keys);
|
||||
}
|
||||
|
||||
SECTION("BON8")
|
||||
{
|
||||
const auto bytes = ordered_json::to_bon8(original);
|
||||
const auto restored = ordered_json::from_bon8(bytes);
|
||||
CHECK(restored == original);
|
||||
CHECK(collect_keys(restored) == original_keys);
|
||||
CHECK(collect_keys(restored["mango"]) == original_mango_keys);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("alt_json (custom string_t) across binary formats")
|
||||
@@ -353,6 +362,13 @@ TEST_CASE("alt_json (custom string_t) across binary formats")
|
||||
CHECK(restored == original);
|
||||
}
|
||||
|
||||
SECTION("BON8")
|
||||
{
|
||||
const auto bytes = alt_json::to_bon8(original);
|
||||
const auto restored = alt_json::from_bon8(bytes);
|
||||
CHECK(restored == original);
|
||||
}
|
||||
|
||||
SECTION("BSON")
|
||||
{
|
||||
const auto bytes = alt_json::to_bson(original);
|
||||
|
||||
@@ -332,6 +332,7 @@ TEST_CASE("regression tests 2")
|
||||
CHECK(float_json::from_cbor(float_json::to_cbor(j)) == j);
|
||||
CHECK(float_json::from_msgpack(float_json::to_msgpack(j)) == j);
|
||||
CHECK(float_json::from_ubjson(float_json::to_ubjson(j)) == j);
|
||||
CHECK(float_json::from_bon8(float_json::to_bon8(j)) == j);
|
||||
|
||||
float_json j2 = {1000.0, 2000.0, 3000.0};
|
||||
CHECK(float_json::from_ubjson(float_json::to_ubjson(j2, true, true)) == j2);
|
||||
|
||||
@@ -74,13 +74,7 @@ using ordered_json = nlohmann::ordered_json;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
// for #4825 - explicitly instantiating basic_json must compile; this
|
||||
// forces instantiation of binary_writer::write_bjdata_ndarray, whose
|
||||
// static_cast<string_t> was ambiguous under explicit instantiation on
|
||||
// C++17. Merely compiling this translation unit is the regression test.
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
template class nlohmann::basic_json<>;
|
||||
// the explicit instantiation for #4825 is in unit-explicit_instantiation.cpp
|
||||
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
// for #4440
|
||||
@@ -894,6 +888,7 @@ TEST_CASE("regression test #5476 - array type without reserve()")
|
||||
// the binary formats pass a definite length to start_array()
|
||||
CHECK(deque_json::from_cbor(deque_json::to_cbor(j)) == j);
|
||||
CHECK(deque_json::from_msgpack(deque_json::to_msgpack(j)) == j);
|
||||
CHECK(deque_json::from_bon8(deque_json::to_bon8(j)) == j);
|
||||
|
||||
// parse() instantiates the callback parser as well, which reserves too
|
||||
const auto with_callback = deque_json::parse(R"([1,2,3])", [](int /*depth*/, deque_json::parse_event_t /*event*/, deque_json& /*parsed*/) noexcept
|
||||
|
||||
@@ -639,3 +639,163 @@ TEST_CASE("serialization of deeply nested values")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
// wraps @a inner into @a depth single-element arrays
|
||||
json wrap_in_arrays(const json& inner, const std::size_t depth)
|
||||
{
|
||||
json j = inner;
|
||||
for (std::size_t i = 0; i < depth; ++i)
|
||||
{
|
||||
j = json::array({std::move(j)});
|
||||
}
|
||||
return j;
|
||||
}
|
||||
|
||||
// what wrap_in_arrays(inner, depth).dump(2) is expected to be: the arrays
|
||||
// around inner.dump(2), with inner's own lines indented by the depth
|
||||
std::string expected_pretty_in_arrays(const json& inner, const std::size_t depth)
|
||||
{
|
||||
std::string expected;
|
||||
for (std::size_t i = 0; i < depth; ++i)
|
||||
{
|
||||
expected += std::string(2 * i, ' ') + "[\n";
|
||||
}
|
||||
|
||||
const std::string indent(2 * depth, ' ');
|
||||
expected += indent;
|
||||
for (const char c : inner.dump(2))
|
||||
{
|
||||
expected += c;
|
||||
if (c == '\n')
|
||||
{
|
||||
expected += indent;
|
||||
}
|
||||
}
|
||||
|
||||
for (std::size_t i = depth; i > 0; --i)
|
||||
{
|
||||
expected += '\n' + std::string(2 * (i - 1), ' ') + ']';
|
||||
}
|
||||
return expected;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("serialization of every kind of value below the bound of the descent")
|
||||
{
|
||||
// Values nested deeper than the bound are written without the call stack,
|
||||
// by code of their own; each kind of value must come out the same there as
|
||||
// it does at the top level, compact and pretty-printed.
|
||||
std::vector<json> values =
|
||||
{
|
||||
json::parse(R"({"a": 1, "b": [1, 2, {"c": "x"}], "d": {}, "e": []})"),
|
||||
json::parse(R"([1, [2, 3], {"k": null}, "s"])"),
|
||||
json::object(),
|
||||
json::array(),
|
||||
json::binary({1, 2, 3}, 42),
|
||||
json::binary({1, 2, 3}),
|
||||
json::binary({}, 7),
|
||||
json::binary({}),
|
||||
"a string with \"escapes\"\n",
|
||||
true,
|
||||
false,
|
||||
-42,
|
||||
42u,
|
||||
1.5,
|
||||
nullptr,
|
||||
json(json::value_t::discarded),
|
||||
};
|
||||
// a pretty-printed object whose members are themselves deep
|
||||
values.push_back({{"x", wrap_in_arrays(1, 5)}, {"y", {{"z", 2}}}});
|
||||
|
||||
for (const std::size_t depth : std::vector<std::size_t> {1, 200})
|
||||
{
|
||||
CAPTURE(depth);
|
||||
for (const auto& inner : values)
|
||||
{
|
||||
CAPTURE(inner.dump());
|
||||
const json j = wrap_in_arrays(inner, depth);
|
||||
CHECK(j.dump() == std::string(depth, '[') + inner.dump() + std::string(depth, ']'));
|
||||
CHECK(j.dump(2) == expected_pretty_in_arrays(inner, depth));
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("pretty-printed objects across the bound")
|
||||
{
|
||||
for (std::size_t d = 120; d <= 140; ++d)
|
||||
{
|
||||
CAPTURE(d);
|
||||
|
||||
// built from the inside out: {"k": <level below>, "n": <level>}
|
||||
json j = 7;
|
||||
std::string expected = "7";
|
||||
for (std::size_t i = d; i > 0; --i)
|
||||
{
|
||||
j = json({{"k", std::move(j)}, {"n", i}});
|
||||
|
||||
const std::string indent(2 * i, ' ');
|
||||
const std::string outer_indent(2 * (i - 1), ' ');
|
||||
std::string next = "{\n";
|
||||
next += indent;
|
||||
next += "\"k\": ";
|
||||
next += expected;
|
||||
next += ",\n";
|
||||
next += indent;
|
||||
next += "\"n\": ";
|
||||
next += std::to_string(i);
|
||||
next += '\n';
|
||||
next += outer_indent;
|
||||
next += '}';
|
||||
expected = std::move(next);
|
||||
}
|
||||
|
||||
CHECK(j.dump(2) == expected);
|
||||
CHECK(json::parse(j.dump(2)) == j);
|
||||
CHECK(json::parse(j.dump()) == j);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("serializer buffers are flushed mid-string and mid-binary")
|
||||
{
|
||||
SECTION("a long run of escaped characters")
|
||||
{
|
||||
// each character is escaped on its own, so the escape buffer fills up
|
||||
const json newlines = std::string(600, '\n');
|
||||
std::string expected = "\"";
|
||||
for (int i = 0; i < 600; ++i)
|
||||
{
|
||||
expected += "\\n";
|
||||
}
|
||||
expected += '"';
|
||||
CHECK(newlines.dump() == expected);
|
||||
|
||||
// every character is \u-escaped under ensure_ascii
|
||||
std::string umlauts;
|
||||
std::string escaped_umlauts = "\"";
|
||||
for (int i = 0; i < 300; ++i)
|
||||
{
|
||||
umlauts += "\xC3\xA4";
|
||||
escaped_umlauts += "\\u00e4";
|
||||
}
|
||||
escaped_umlauts += '"';
|
||||
CHECK(json(umlauts).dump(-1, ' ', true) == escaped_umlauts);
|
||||
}
|
||||
|
||||
SECTION("a large binary value")
|
||||
{
|
||||
std::vector<std::uint8_t> bytes(3000);
|
||||
std::string expected_bytes;
|
||||
std::string expected_pretty_bytes;
|
||||
for (std::size_t i = 0; i < bytes.size(); ++i)
|
||||
{
|
||||
bytes[i] = static_cast<std::uint8_t>(i % 256);
|
||||
expected_bytes += (i == 0 ? "" : ",") + std::to_string(i % 256);
|
||||
expected_pretty_bytes += (i == 0 ? "" : ", ") + std::to_string(i % 256);
|
||||
}
|
||||
const json j = json::binary(bytes);
|
||||
CHECK(j.dump() == "{\"bytes\":[" + expected_bytes + "],\"subtype\":null}");
|
||||
CHECK(j.dump(2) == "{\n \"bytes\": [" + expected_pretty_bytes + "],\n \"subtype\": null\n}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,6 +102,29 @@ TEST_CASE("std::formatter<nlohmann::json>")
|
||||
CHECK_THROWS_AS(std::vformat("{:{}}", std::make_format_args(j, dynamic_width)), std::format_error); // dynamic width
|
||||
}
|
||||
|
||||
SECTION("a format spec may run to the end of the parse context")
|
||||
{
|
||||
// std::format always hands parse() a range that still holds the closing
|
||||
// '}', but a parse context may also end right after the spec
|
||||
const auto parse = [](const char* spec)
|
||||
{
|
||||
std::format_parse_context ctx(spec);
|
||||
std::formatter<json> f;
|
||||
CHECK(f.parse(ctx) == ctx.end());
|
||||
return f;
|
||||
};
|
||||
|
||||
CHECK(parse("").indent == -1);
|
||||
CHECK(parse(">").indent == -1);
|
||||
CHECK(parse("#").indent == 4);
|
||||
CHECK(parse("3").indent == 3);
|
||||
CHECK(parse("#12").indent == 12);
|
||||
|
||||
const auto f = parse(".>");
|
||||
CHECK(f.indent == -1);
|
||||
CHECK(f.indent_char == '.');
|
||||
}
|
||||
|
||||
SECTION("std::format_to writes through an arbitrary output iterator")
|
||||
{
|
||||
const json j = {{"foo", 1}, {"bar", {1, 2, 3}}};
|
||||
|
||||
+85
-33
@@ -265,7 +265,7 @@ TEST_CASE("UBJSON")
|
||||
|
||||
SECTION("-32768..-129 (int16)")
|
||||
{
|
||||
for (int32_t i = -32768; i <= -129; ++i)
|
||||
for (int32_t i = -32768; i <= -129; i = utils::next_integer_sample(i, -129, 7))
|
||||
{
|
||||
CAPTURE(i)
|
||||
|
||||
@@ -425,7 +425,7 @@ TEST_CASE("UBJSON")
|
||||
|
||||
SECTION("256..32767 (int16)")
|
||||
{
|
||||
for (size_t i = 256; i <= 32767; ++i)
|
||||
for (size_t i = 256; i <= 32767; i = utils::next_integer_sample(i, static_cast<size_t>(32767), static_cast<size_t>(7)))
|
||||
{
|
||||
CAPTURE(i)
|
||||
|
||||
@@ -631,7 +631,7 @@ TEST_CASE("UBJSON")
|
||||
|
||||
SECTION("256..32767 (int16)")
|
||||
{
|
||||
for (size_t i = 256; i <= 32767; ++i)
|
||||
for (size_t i = 256; i <= 32767; i = utils::next_integer_sample(i, static_cast<size_t>(32767), static_cast<size_t>(7)))
|
||||
{
|
||||
CAPTURE(i)
|
||||
|
||||
@@ -1640,6 +1640,29 @@ TEST_CASE("UBJSON")
|
||||
});
|
||||
CHECK_THROWS_AS(_ = json::sax_parse(v_ubjson, &scp, json::input_format_t::ubjson), json::out_of_range&);
|
||||
}
|
||||
|
||||
SECTION("array with a known size, read with a callback")
|
||||
{
|
||||
// a sized array announces its length to start_array()
|
||||
std::vector<uint8_t> const v_ubjson = {'[', '#', 'i', 2, 'i', 1, 'i', 2};
|
||||
json j;
|
||||
nlohmann::detail::json_sax_dom_callback_parser<json, decltype(nlohmann::detail::input_adapter(v_ubjson))> scp(j, [](int /*unused*/, json::parse_event_t /*unused*/, const json& /*unused*/) noexcept
|
||||
{
|
||||
return true;
|
||||
});
|
||||
CHECK(json::sax_parse(v_ubjson, &scp, json::input_format_t::ubjson));
|
||||
CHECK(j == json({1, 2}));
|
||||
|
||||
// the readers reject a size this large before they announce
|
||||
// it, so it can only reach start_array() directly (the largest
|
||||
// value stands for an unknown size and is never checked)
|
||||
json k;
|
||||
nlohmann::detail::json_sax_dom_callback_parser<json, decltype(nlohmann::detail::input_adapter(v_ubjson))> scp2(k, [](int /*unused*/, json::parse_event_t /*unused*/, const json& /*unused*/) noexcept
|
||||
{
|
||||
return true;
|
||||
});
|
||||
CHECK_THROWS_AS(scp2.start_array((std::numeric_limits<std::size_t>::max)() - 1), json::out_of_range&);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2255,6 +2278,46 @@ TEST_CASE("UBJSON nesting does not consume the call stack")
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("UBJSON input that cannot be read is discarded by every overload")
|
||||
{
|
||||
std::vector<std::uint8_t> input = json::to_ubjson(json({{"a", {1, 2}}}));
|
||||
input.pop_back();
|
||||
|
||||
json _;
|
||||
CHECK_THROWS_AS(_ = json::from_ubjson(input.begin(), input.end()), json::parse_error&);
|
||||
CHECK(json::from_ubjson(input, true, false).is_discarded());
|
||||
CHECK(json::from_ubjson(input.begin(), input.end(), true, false).is_discarded());
|
||||
CHECK(json::from_ubjson(input.data(), input.size(), true, false).is_discarded());
|
||||
CHECK(json::from_ubjson({input.data(), input.size()}, true, false).is_discarded());
|
||||
}
|
||||
|
||||
TEST_CASE("UBJSON SAX parsing stops at every event")
|
||||
{
|
||||
// Containers are opened and closed by the loop that reads them; a SAX
|
||||
// handler that rejects any event - including the end of a nested
|
||||
// container - must stop the parse right there.
|
||||
const auto count_events = [](const std::vector<std::uint8_t>& input)
|
||||
{
|
||||
int events = 0;
|
||||
while (true)
|
||||
{
|
||||
SaxCountdown scp(events);
|
||||
if (json::sax_parse(input, &scp, json::input_format_t::ubjson))
|
||||
{
|
||||
return events;
|
||||
}
|
||||
++events;
|
||||
REQUIRE(events < 1000);
|
||||
}
|
||||
};
|
||||
|
||||
// 20 events: every container kind closes inside another one
|
||||
const json j = json::parse(R"({"a": [1, {"b": []}], "c": {"d": [[2]]}})");
|
||||
CHECK(count_events(json::to_ubjson(j)) == 20);
|
||||
CHECK(count_events(json::to_ubjson(j, true)) == 20);
|
||||
CHECK(count_events(json::to_ubjson(j, true, true)) == 20);
|
||||
}
|
||||
|
||||
TEST_CASE("UBJSON optimized arrays of a valueless type are bounded")
|
||||
{
|
||||
// An element of type 'Z', 'T' or 'F' is encoded by its marker alone, so an
|
||||
@@ -2917,60 +2980,34 @@ TEST_CASE("UBJSON roundtrips" * doctest::skip())
|
||||
{
|
||||
CAPTURE(filename)
|
||||
|
||||
std::ifstream f_json(filename);
|
||||
json const j1 = json::parse(f_json);
|
||||
auto const packed = utils::read_binary_file(filename + ".ubjson");
|
||||
|
||||
{
|
||||
INFO_WITH_TEMP(filename + ": std::vector<uint8_t>");
|
||||
// parse JSON file
|
||||
std::ifstream f_json(filename);
|
||||
json const j1 = json::parse(f_json);
|
||||
|
||||
// parse UBJSON file
|
||||
auto const packed = utils::read_binary_file(filename + ".ubjson");
|
||||
json j2;
|
||||
CHECK_NOTHROW(j2 = json::from_ubjson(packed));
|
||||
|
||||
// compare parsed JSON values
|
||||
CHECK(j1 == j2);
|
||||
}
|
||||
|
||||
{
|
||||
INFO_WITH_TEMP(filename + ": std::ifstream");
|
||||
// parse JSON file
|
||||
std::ifstream f_json(filename);
|
||||
json const j1 = json::parse(f_json);
|
||||
|
||||
// parse UBJSON file
|
||||
std::ifstream f_ubjson(filename + ".ubjson", std::ios::binary);
|
||||
json j2;
|
||||
CHECK_NOTHROW(j2 = json::from_ubjson(f_ubjson));
|
||||
|
||||
// compare parsed JSON values
|
||||
CHECK(j1 == j2);
|
||||
}
|
||||
|
||||
{
|
||||
INFO_WITH_TEMP(filename + ": uint8_t* and size");
|
||||
// parse JSON file
|
||||
std::ifstream f_json(filename);
|
||||
const json j1 = json::parse(f_json);
|
||||
|
||||
// parse UBJSON file
|
||||
auto const packed = utils::read_binary_file(filename + ".ubjson");
|
||||
json j2;
|
||||
CHECK_NOTHROW(j2 = json::from_ubjson({packed.data(), packed.size()}));
|
||||
|
||||
// compare parsed JSON values
|
||||
CHECK(j1 == j2);
|
||||
}
|
||||
|
||||
{
|
||||
INFO_WITH_TEMP(filename + ": output to output adapters");
|
||||
// parse JSON file
|
||||
std::ifstream f_json(filename);
|
||||
json const j1 = json::parse(f_json);
|
||||
|
||||
// parse UBJSON file
|
||||
auto const packed = utils::read_binary_file(filename + ".ubjson");
|
||||
|
||||
{
|
||||
INFO_WITH_TEMP(filename + ": output adapters: std::vector<uint8_t>");
|
||||
std::vector<uint8_t> vec;
|
||||
@@ -2981,3 +3018,18 @@ TEST_CASE("UBJSON roundtrips" * doctest::skip())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("UBJSON optimized array of unsigned integers beyond int64")
|
||||
{
|
||||
// UBJSON has no unsigned 64-bit type, so such values are written as
|
||||
// high-precision numbers - also as the type of an optimized container
|
||||
const json j = {18446744073709551615ULL, 9223372036854775808ULL};
|
||||
const std::vector<std::uint8_t> expected =
|
||||
{
|
||||
'[', '$', 'H', '#', 'i', 2,
|
||||
'i', 20, '1', '8', '4', '4', '6', '7', '4', '4', '0', '7', '3', '7', '0', '9', '5', '5', '1', '6', '1', '5',
|
||||
'i', 19, '9', '2', '2', '3', '3', '7', '2', '0', '3', '6', '8', '5', '4', '7', '7', '5', '8', '0', '8'
|
||||
};
|
||||
CHECK(json::to_ubjson(j, true, true) == expected);
|
||||
CHECK(json::from_ubjson(expected) == j);
|
||||
}
|
||||
|
||||
@@ -70,6 +70,8 @@ TEST_CASE("wide strings")
|
||||
CHECK_THROWS_WITH_AS(_ = json::parse(std::wstring{L'"', static_cast<wchar_t>(0xDC00), L'"'}), error_low_surrogate, json::parse_error&);
|
||||
// a high surrogate followed by a non-low-surrogate unit is invalid
|
||||
CHECK_THROWS_WITH_AS(_ = json::parse(std::wstring{L'"', static_cast<wchar_t>(0xD800), L'a', L'"'}), error_high_surrogate, json::parse_error&);
|
||||
// ... also when the unit is above the low surrogates
|
||||
CHECK_THROWS_WITH_AS(_ = json::parse(std::wstring{L'"', static_cast<wchar_t>(0xD800), static_cast<wchar_t>(0xE000), L'"'}), error_high_surrogate, 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
|
||||
@@ -99,6 +101,8 @@ TEST_CASE("wide strings")
|
||||
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&);
|
||||
// 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&);
|
||||
// ... 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&);
|
||||
// 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
|
||||
|
||||
Reference in New Issue
Block a user