diff --git a/docs/mkdocs/docs/api/basic_json/to_bjdata.md b/docs/mkdocs/docs/api/basic_json/to_bjdata.md index 9c1efb183..2cf936a51 100644 --- a/docs/mkdocs/docs/api/basic_json/to_bjdata.md +++ b/docs/mkdocs/docs/api/basic_json/to_bjdata.md @@ -52,6 +52,11 @@ optional, `#!cpp bjdata_version_t::draft2` by default. Strong guarantee: if an exception is thrown, there are no changes in the JSON value. +## Exceptions + +- Throws [`other_error.502`](../../home/exceptions.md#jsonexceptionother_error502) if `use_type` is true and `use_size` + is false. + ## Complexity Linear in the size of the JSON value `j`. diff --git a/docs/mkdocs/docs/api/basic_json/to_ubjson.md b/docs/mkdocs/docs/api/basic_json/to_ubjson.md index 39b184e74..694fbbba6 100644 --- a/docs/mkdocs/docs/api/basic_json/to_ubjson.md +++ b/docs/mkdocs/docs/api/basic_json/to_ubjson.md @@ -45,6 +45,11 @@ The exact mapping and its limitations are described on a [dedicated page](../../ Strong guarantee: if an exception is thrown, there are no changes in the JSON value. +## Exceptions + +- Throws [`other_error.502`](../../home/exceptions.md#jsonexceptionother_error502) if `use_type` is true and `use_size` + is false. + ## Complexity Linear in the size of the JSON value `j`. diff --git a/docs/mkdocs/docs/home/exceptions.md b/docs/mkdocs/docs/home/exceptions.md index f75872505..7c7d22e23 100644 --- a/docs/mkdocs/docs/home/exceptions.md +++ b/docs/mkdocs/docs/home/exceptions.md @@ -965,3 +965,19 @@ A JSON Patch operation 'test' failed. The unsuccessful operation is also printed ``` [json.exception.other_error.501] unsuccessful: {"op":"test","path":"/baz","value":"bar"} ``` + +### json.exception.other_error.502 + +[`to_ubjson`](../api/basic_json/to_ubjson.md) and [`to_bjdata`](../api/basic_json/to_bjdata.md) were called with +`use_type = true` but `use_size = false`. UBJSON requires a size marker (`#`) after a type marker (`$`). + +!!! failure "Example message" + + ``` + [json.exception.other_error.502] use_type requires use_size = true + ``` + +!!! note + + This exception was added in version 3.13.0. Before that, debug builds aborted on an assertion and release builds + wrote a `$` marker without `#`, which [`from_ubjson`](../api/basic_json/from_ubjson.md) then rejected. diff --git a/include/nlohmann/detail/output/binary_writer.hpp b/include/nlohmann/detail/output/binary_writer.hpp index 35b5efa8a..e636c6d84 100644 --- a/include/nlohmann/detail/output/binary_writer.hpp +++ b/include/nlohmann/detail/output/binary_writer.hpp @@ -813,7 +813,10 @@ class binary_writer bool prefix_required = true; if (use_type && !j.m_data.m_value.array->empty()) { - JSON_ASSERT(use_count); + if (!use_count) + { + JSON_THROW(other_error::create(502, "use_type requires use_size = true", &j)); + } const CharType first_prefix = ubjson_prefix(j.front(), use_bjdata); const bool same_prefix = std::all_of(j.begin() + 1, j.end(), [this, first_prefix, use_bjdata](const BasicJsonType & v) @@ -859,7 +862,10 @@ class binary_writer if (use_type && (bjdata_draft3 || !j.m_data.m_value.binary->empty())) { - JSON_ASSERT(use_count); + if (!use_count) + { + JSON_THROW(other_error::create(502, "use_type requires use_size = true", &j)); + } oa->write_character(to_char_type('$')); oa->write_character(bjdata_draft3 ? 'B' : 'U'); } @@ -911,7 +917,10 @@ class binary_writer bool prefix_required = true; if (use_type && !j.m_data.m_value.object->empty()) { - JSON_ASSERT(use_count); + if (!use_count) + { + JSON_THROW(other_error::create(502, "use_type requires use_size = true", &j)); + } const CharType first_prefix = ubjson_prefix(j.front(), use_bjdata); const bool same_prefix = std::all_of(j.begin(), j.end(), [this, first_prefix, use_bjdata](const BasicJsonType & v) diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 0ea563fb9..904d8659c 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -17756,7 +17756,10 @@ class binary_writer bool prefix_required = true; if (use_type && !j.m_data.m_value.array->empty()) { - JSON_ASSERT(use_count); + if (!use_count) + { + JSON_THROW(other_error::create(502, "use_type requires use_size = true", &j)); + } const CharType first_prefix = ubjson_prefix(j.front(), use_bjdata); const bool same_prefix = std::all_of(j.begin() + 1, j.end(), [this, first_prefix, use_bjdata](const BasicJsonType & v) @@ -17802,7 +17805,10 @@ class binary_writer if (use_type && (bjdata_draft3 || !j.m_data.m_value.binary->empty())) { - JSON_ASSERT(use_count); + if (!use_count) + { + JSON_THROW(other_error::create(502, "use_type requires use_size = true", &j)); + } oa->write_character(to_char_type('$')); oa->write_character(bjdata_draft3 ? 'B' : 'U'); } @@ -17854,7 +17860,10 @@ class binary_writer bool prefix_required = true; if (use_type && !j.m_data.m_value.object->empty()) { - JSON_ASSERT(use_count); + if (!use_count) + { + JSON_THROW(other_error::create(502, "use_type requires use_size = true", &j)); + } const CharType first_prefix = ubjson_prefix(j.front(), use_bjdata); const bool same_prefix = std::all_of(j.begin(), j.end(), [this, first_prefix, use_bjdata](const BasicJsonType & v) diff --git a/tests/src/unit-bjdata.cpp b/tests/src/unit-bjdata.cpp index d5f9c3acd..41feb5288 100644 --- a/tests/src/unit-bjdata.cpp +++ b/tests/src/unit-bjdata.cpp @@ -3843,6 +3843,48 @@ TEST_CASE("all BJData first bytes") } #endif +TEST_CASE("BJData use_type requires use_size") +{ + SECTION("non-empty object throws other_error.502") + { + const json j = {{"a", 1}, {"b", 2}}; + CHECK_THROWS_WITH_AS(json::to_bjdata(j, false, true), + "[json.exception.other_error.502] use_type requires use_size = true", + json::other_error&); + } + + SECTION("non-empty array throws other_error.502") + { + const json j = {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&); + } + + SECTION("scalars do not throw with use_type=true, use_count=false") + { + CHECK_NOTHROW(json::to_bjdata(42, false, true)); + CHECK_NOTHROW(json::to_bjdata(3.14, false, true)); + CHECK_NOTHROW(json::to_bjdata("hello", false, true)); + CHECK_NOTHROW(json::to_bjdata(true, false, true)); + CHECK_NOTHROW(json::to_bjdata(nullptr, false, true)); + } + + SECTION("empty containers do not throw with use_type=true, use_count=false") + { + CHECK_NOTHROW(json::to_bjdata(json::array(), false, true)); + CHECK_NOTHROW(json::to_bjdata(json::object(), false, true)); + } + + SECTION("valid combinations on non-empty containers") + { + const json j = {{"a", 1}, {"b", 2}}; + CHECK_NOTHROW(json::to_bjdata(j, false, false)); + CHECK_NOTHROW(json::to_bjdata(j, true, false)); + CHECK_NOTHROW(json::to_bjdata(j, true, true)); + } +} + TEST_CASE("BJData roundtrips" * doctest::skip()) { SECTION("input from self-generated BJData files") diff --git a/tests/src/unit-ubjson.cpp b/tests/src/unit-ubjson.cpp index e1e327563..b59ac6b38 100644 --- a/tests/src/unit-ubjson.cpp +++ b/tests/src/unit-ubjson.cpp @@ -2503,6 +2503,48 @@ TEST_CASE("all UBJSON first bytes") } #endif +TEST_CASE("UBJSON use_type requires use_size") +{ + SECTION("non-empty array throws other_error.502") + { + const json j = {1, 2, 3}; + 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("non-empty object throws other_error.502") + { + const json j = {{"a", 1}, {"b", 2}}; + 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_ubjson(42, false, true)); + CHECK_NOTHROW(json::to_ubjson(3.14, false, true)); + CHECK_NOTHROW(json::to_ubjson("hello", false, true)); + CHECK_NOTHROW(json::to_ubjson(true, false, true)); + CHECK_NOTHROW(json::to_ubjson(nullptr, false, true)); + } + + SECTION("empty containers do not throw with use_type=true, use_count=false") + { + CHECK_NOTHROW(json::to_ubjson(json::array(), false, true)); + CHECK_NOTHROW(json::to_ubjson(json::object(), false, true)); + } + + SECTION("valid combinations on non-empty containers") + { + const json j = {1, 2, 3}; + CHECK_NOTHROW(json::to_ubjson(j, false, false)); + CHECK_NOTHROW(json::to_ubjson(j, true, false)); + CHECK_NOTHROW(json::to_ubjson(j, true, true)); + } +} + TEST_CASE("UBJSON roundtrips" * doctest::skip()) { SECTION("input from self-generated UBJSON files")