diff --git a/docs/mkdocs/docs/api/basic_json/to_bjdata.md b/docs/mkdocs/docs/api/basic_json/to_bjdata.md index df3b69004..583139068 100644 --- a/docs/mkdocs/docs/api/basic_json/to_bjdata.md +++ b/docs/mkdocs/docs/api/basic_json/to_bjdata.md @@ -68,6 +68,8 @@ Strong guarantee: if an exception is thrown, there are no changes in the JSON va - Throws [type_error.316](../../home/exceptions.md#jsonexceptiontype_error316) if a string or object key in `j` is not valid UTF-8 and `error_handler` is `strict` (the default only if [`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled) +- Throws [type_error.321](../../home/exceptions.md#jsonexceptiontype_error321) if `j` or a value nested in it is + discarded; example: `"cannot serialize discarded value to BJData"` ## Complexity @@ -119,4 +121,6 @@ Linear in the size of the JSON value `j`. - BJData version parameter (for draft3 binary encoding) added in version 3.12.0. - Added `error_handler` parameter in version 3.13.0. Its default, `keep`, writes the bytes of a string or object key that is not valid UTF-8 unchanged, as before; `strict` (the default if - [`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled) throws `type_error.316`. \ No newline at end of file + [`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled) throws `type_error.316`. +- Throws `type_error.321` for a discarded value since version 3.13.0; previously, a discarded value nested in an + array or object was silently skipped, producing invalid BJData. \ No newline at end of file diff --git a/docs/mkdocs/docs/api/basic_json/to_bson.md b/docs/mkdocs/docs/api/basic_json/to_bson.md index ad974e048..05f8b8548 100644 --- a/docs/mkdocs/docs/api/basic_json/to_bson.md +++ b/docs/mkdocs/docs/api/basic_json/to_bson.md @@ -58,6 +58,9 @@ Strong guarantee: if an exception is thrown, there are no changes in the JSON va - Throws [type_error.316](../../home/exceptions.md#jsonexceptiontype_error316) if a string or object key is not valid UTF-8 and `error_handler` is `strict` (the default only if [`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled) +- Throws [type_error.321](../../home/exceptions.md#jsonexceptiontype_error321) if a value nested in `j` is discarded + (the top-level value itself is covered by `type_error.317` above, since it must be an object); example: + `"cannot serialize discarded value to BSON"` ## Complexity @@ -110,6 +113,8 @@ pass before anything is written. - Throws `out_of_range.412` and `out_of_range.415` since version 3.13.0. - Linear in the size of `j`, and no longer limited by the call stack for deeply nested values, since version 3.13.0. - `out_of_range.415` is now detected before anything is written, like the other exceptions above, since version 3.13.0. +- Throws `type_error.321` for a discarded value nested in `j` since version 3.13.0; previously, it was silently + skipped, producing a document whose declared size did not match what was actually written. - Added `error_handler` parameter in version 3.13.0. Its default, `keep`, writes the bytes of a string or object key that is not valid UTF-8 unchanged, as before; `strict` (the default if [`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled) throws `type_error.316` before anything diff --git a/docs/mkdocs/docs/api/basic_json/to_cbor.md b/docs/mkdocs/docs/api/basic_json/to_cbor.md index c72f310bb..f5b6a07bb 100644 --- a/docs/mkdocs/docs/api/basic_json/to_cbor.md +++ b/docs/mkdocs/docs/api/basic_json/to_cbor.md @@ -49,6 +49,8 @@ Strong guarantee: if an exception is thrown, there are no changes in the JSON va - Throws [type_error.316](../../home/exceptions.md#jsonexceptiontype_error316) if a string or object key in `j` is not valid UTF-8 and `error_handler` is `strict` (the default only if [`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled) +- Throws [type_error.321](../../home/exceptions.md#jsonexceptiontype_error321) if `j` or a value nested in it is + discarded; example: `"cannot serialize discarded value to CBOR"` ## Complexity @@ -86,3 +88,5 @@ Linear in the size of the JSON value `j`. - Added `error_handler` parameter in version 3.13.0. Its default, `keep`, writes the bytes of a string or object key that is not valid UTF-8 unchanged, as before; `strict` (the default if [`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled) throws `type_error.316`. +- Throws `type_error.321` for a discarded value since version 3.13.0; previously, a discarded value nested in an + array or object was silently skipped, producing invalid CBOR. diff --git a/docs/mkdocs/docs/api/basic_json/to_msgpack.md b/docs/mkdocs/docs/api/basic_json/to_msgpack.md index 17b9fe29c..2e32208bf 100644 --- a/docs/mkdocs/docs/api/basic_json/to_msgpack.md +++ b/docs/mkdocs/docs/api/basic_json/to_msgpack.md @@ -54,6 +54,8 @@ Strong guarantee: if an exception is thrown, there are no changes in the JSON va `"subtype 70000 is too large for the MessagePack ext type (max 255)"` - Throws [type_error.316](../../home/exceptions.md#jsonexceptiontype_error316) if a string or object key in `j` is not valid UTF-8 and `error_handler` is `strict` +- Throws [type_error.321](../../home/exceptions.md#jsonexceptiontype_error321) if `j` or a value nested in it is + discarded; example: `"cannot serialize discarded value to MessagePack"` ## Complexity @@ -108,3 +110,5 @@ Linear in the size of the JSON value `j`. - Fixed in version 3.13.0 to serialize `number_integer_t`/`number_unsigned_t` pairs of different width correctly; before, integers could be serialized with the wrong value if `number_integer_t` was narrower than `number_unsigned_t`. +- Throws `type_error.321` for a discarded value since version 3.13.0; previously, a discarded value nested in an + array or object was silently skipped, producing invalid MessagePack. diff --git a/docs/mkdocs/docs/api/basic_json/to_ubjson.md b/docs/mkdocs/docs/api/basic_json/to_ubjson.md index 6860c01c6..6aad86683 100644 --- a/docs/mkdocs/docs/api/basic_json/to_ubjson.md +++ b/docs/mkdocs/docs/api/basic_json/to_ubjson.md @@ -61,6 +61,8 @@ Strong guarantee: if an exception is thrown, there are no changes in the JSON va - Throws [type_error.316](../../home/exceptions.md#jsonexceptiontype_error316) if a string or object key in `j` is not valid UTF-8 and `error_handler` is `strict` (the default only if [`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled) +- Throws [type_error.321](../../home/exceptions.md#jsonexceptiontype_error321) if `j` or a value nested in it is + discarded; example: `"cannot serialize discarded value to UBJSON"` ## Complexity @@ -112,3 +114,5 @@ Linear in the size of the JSON value `j`. - Added `error_handler` parameter in version 3.13.0. Its default, `keep`, writes the bytes of a string or object key that is not valid UTF-8 unchanged, as before; `strict` (the default if [`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled) throws `type_error.316`. +- Throws `type_error.321` for a discarded value since version 3.13.0; previously, a discarded value nested in an + array or object was silently skipped, producing invalid UBJSON. diff --git a/docs/mkdocs/docs/home/exceptions.md b/docs/mkdocs/docs/home/exceptions.md index 673c0c8dd..9cb5c8f62 100644 --- a/docs/mkdocs/docs/home/exceptions.md +++ b/docs/mkdocs/docs/home/exceptions.md @@ -804,6 +804,20 @@ does not list an enumerator and it is therefore converted like the first listed [json.exception.type_error.318] duplicate object key 'red' ``` +### json.exception.type_error.321 + +A discarded value (one created by [`parse()`](../api/basic_json/parse.md) with a callback that returns `false` for the +value, or by default-constructing a [`basic_json`](../api/basic_json/index.md) with +[`value_t::discarded`](../api/basic_json/value_t.md)) was passed to a binary serialization function, either directly or +nested in an array or object. There is no way to represent a discarded value in CBOR, MessagePack, UBJSON, BJData, or BSON. + +!!! failure "Example message" + + Serializing `#!json [1, 2]` to CBOR, where the second element was discarded by a parser callback: + ``` + [json.exception.type_error.321] cannot serialize discarded value to CBOR + ``` + ## Out of range This exception is thrown in case a library function is called on an input parameter that exceeds the expected range, for instance, in the case of array indices or nonexisting object keys. diff --git a/tests/src/unit-bjdata.cpp b/tests/src/unit-bjdata.cpp index 0098541cf..2851d4810 100644 --- a/tests/src/unit-bjdata.cpp +++ b/tests/src/unit-bjdata.cpp @@ -106,10 +106,59 @@ TEST_CASE("BJData") { SECTION("discarded") { - // discarded values are not serialized + // a discarded value cannot be serialized to BJData json const j = json::value_t::discarded; - const auto result = json::to_bjdata(j); - CHECK(result.empty()); + CHECK_THROWS_WITH_AS(json::to_bjdata(j), "[json.exception.type_error.321] cannot serialize discarded value to BJData", json::type_error&); + } + + SECTION("discarded values nested in a container") + { + json const discarded = json::value_t::discarded; + + SECTION("in an array") + { + json const j = {1, discarded, 2}; +#if JSON_DIAGNOSTICS + CHECK_THROWS_WITH_AS(json::to_bjdata(j), "[json.exception.type_error.321] (/1) cannot serialize discarded value to BJData", json::type_error&); +#else + CHECK_THROWS_WITH_AS(json::to_bjdata(j), "[json.exception.type_error.321] cannot serialize discarded value to BJData", json::type_error&); +#endif + } + + SECTION("as an object value") + { + json j; + j["a"] = 1; + j["b"] = discarded; +#if JSON_DIAGNOSTICS + CHECK_THROWS_WITH_AS(json::to_bjdata(j), "[json.exception.type_error.321] (/b) cannot serialize discarded value to BJData", json::type_error&); +#else + CHECK_THROWS_WITH_AS(json::to_bjdata(j), "[json.exception.type_error.321] cannot serialize discarded value to BJData", json::type_error&); +#endif + } + + SECTION("nested deeper (array in object in array)") + { + json inner_array = {1, discarded}; + json middle_object; + middle_object["x"] = inner_array; + json const j = {middle_object}; +#if JSON_DIAGNOSTICS + CHECK_THROWS_WITH_AS(json::to_bjdata(j), "[json.exception.type_error.321] (/0/x/1) cannot serialize discarded value to BJData", json::type_error&); +#else + CHECK_THROWS_WITH_AS(json::to_bjdata(j), "[json.exception.type_error.321] cannot serialize discarded value to BJData", json::type_error&); +#endif + } + + SECTION("optimized array of all-discarded elements") + { + json const j = {discarded, discarded}; +#if JSON_DIAGNOSTICS + CHECK_THROWS_WITH_AS(json::to_bjdata(j, true, true), "[json.exception.type_error.321] (/0) cannot serialize discarded value to BJData", json::type_error&); +#else + CHECK_THROWS_WITH_AS(json::to_bjdata(j, true, true), "[json.exception.type_error.321] cannot serialize discarded value to BJData", json::type_error&); +#endif + } } SECTION("null") diff --git a/tests/src/unit-bson.cpp b/tests/src/unit-bson.cpp index 17819dc65..e183723ae 100644 --- a/tests/src/unit-bson.cpp +++ b/tests/src/unit-bson.cpp @@ -141,6 +141,54 @@ TEST_CASE("BSON") json const j = std::vector {1, 2, 3, 4, 5, 6, 7}; CHECK_THROWS_WITH_AS(json::to_bson(j), "[json.exception.type_error.317] to serialize to BSON, top-level type must be object, but is array", json::type_error&); } + + SECTION("discarded") + { + json const j = json::value_t::discarded; + CHECK_THROWS_WITH_AS(json::to_bson(j), "[json.exception.type_error.317] to serialize to BSON, top-level type must be object, but is discarded", json::type_error&); + } + } + + SECTION("discarded values nested in a container cannot be serialized to BSON") + { + json const discarded = json::value_t::discarded; + + SECTION("as an object value") + { + json j; + j["a"] = 1; + j["b"] = discarded; +#if JSON_DIAGNOSTICS + CHECK_THROWS_WITH_AS(json::to_bson(j), "[json.exception.type_error.321] (/b) cannot serialize discarded value to BSON", json::type_error&); +#else + CHECK_THROWS_WITH_AS(json::to_bson(j), "[json.exception.type_error.321] cannot serialize discarded value to BSON", json::type_error&); +#endif + } + + SECTION("in an array that is an object value") + { + json j; + j["a"] = json::array({1, discarded, 2}); +#if JSON_DIAGNOSTICS + CHECK_THROWS_WITH_AS(json::to_bson(j), "[json.exception.type_error.321] (/a/1) cannot serialize discarded value to BSON", json::type_error&); +#else + CHECK_THROWS_WITH_AS(json::to_bson(j), "[json.exception.type_error.321] cannot serialize discarded value to BSON", json::type_error&); +#endif + } + + SECTION("nested deeper (array in object in object)") + { + json inner_array = {1, discarded}; + json middle_object; + middle_object["x"] = inner_array; + json j; + j["outer"] = middle_object; +#if JSON_DIAGNOSTICS + CHECK_THROWS_WITH_AS(json::to_bson(j), "[json.exception.type_error.321] (/outer/x/1) cannot serialize discarded value to BSON", json::type_error&); +#else + CHECK_THROWS_WITH_AS(json::to_bson(j), "[json.exception.type_error.321] cannot serialize discarded value to BSON", json::type_error&); +#endif + } } SECTION("keys containing code-point U+0000 cannot be serialized to BSON") diff --git a/tests/src/unit-cbor.cpp b/tests/src/unit-cbor.cpp index 546e256b1..fad0e3f9d 100644 --- a/tests/src/unit-cbor.cpp +++ b/tests/src/unit-cbor.cpp @@ -30,10 +30,49 @@ TEST_CASE("CBOR") { SECTION("discarded") { - // discarded values are not serialized + // a discarded value cannot be serialized to CBOR json const j = json::value_t::discarded; - const auto result = json::to_cbor(j); - CHECK(result.empty()); + CHECK_THROWS_WITH_AS(json::to_cbor(j), "[json.exception.type_error.321] cannot serialize discarded value to CBOR", json::type_error&); + } + + SECTION("discarded values nested in a container") + { + json const discarded = json::value_t::discarded; + + SECTION("in an array") + { + json const j = {1, discarded, 2}; +#if JSON_DIAGNOSTICS + CHECK_THROWS_WITH_AS(json::to_cbor(j), "[json.exception.type_error.321] (/1) cannot serialize discarded value to CBOR", json::type_error&); +#else + CHECK_THROWS_WITH_AS(json::to_cbor(j), "[json.exception.type_error.321] cannot serialize discarded value to CBOR", json::type_error&); +#endif + } + + SECTION("as an object value") + { + json j; + j["a"] = 1; + j["b"] = discarded; +#if JSON_DIAGNOSTICS + CHECK_THROWS_WITH_AS(json::to_cbor(j), "[json.exception.type_error.321] (/b) cannot serialize discarded value to CBOR", json::type_error&); +#else + CHECK_THROWS_WITH_AS(json::to_cbor(j), "[json.exception.type_error.321] cannot serialize discarded value to CBOR", json::type_error&); +#endif + } + + SECTION("nested deeper (array in object in array)") + { + json inner_array = {1, discarded}; + json middle_object; + middle_object["x"] = inner_array; + json const j = {middle_object}; +#if JSON_DIAGNOSTICS + CHECK_THROWS_WITH_AS(json::to_cbor(j), "[json.exception.type_error.321] (/0/x/1) cannot serialize discarded value to CBOR", json::type_error&); +#else + CHECK_THROWS_WITH_AS(json::to_cbor(j), "[json.exception.type_error.321] cannot serialize discarded value to CBOR", json::type_error&); +#endif + } } SECTION("NaN") diff --git a/tests/src/unit-msgpack.cpp b/tests/src/unit-msgpack.cpp index c4b447d64..f0d3eac0a 100644 --- a/tests/src/unit-msgpack.cpp +++ b/tests/src/unit-msgpack.cpp @@ -33,10 +33,49 @@ TEST_CASE("MessagePack") { SECTION("discarded") { - // discarded values are not serialized + // a discarded value cannot be serialized to MessagePack json const j = json::value_t::discarded; - const auto result = json::to_msgpack(j); - CHECK(result.empty()); + CHECK_THROWS_WITH_AS(json::to_msgpack(j), "[json.exception.type_error.321] cannot serialize discarded value to MessagePack", json::type_error&); + } + + SECTION("discarded values nested in a container") + { + json const discarded = json::value_t::discarded; + + SECTION("in an array") + { + json const j = {1, discarded, 2}; +#if JSON_DIAGNOSTICS + CHECK_THROWS_WITH_AS(json::to_msgpack(j), "[json.exception.type_error.321] (/1) cannot serialize discarded value to MessagePack", json::type_error&); +#else + CHECK_THROWS_WITH_AS(json::to_msgpack(j), "[json.exception.type_error.321] cannot serialize discarded value to MessagePack", json::type_error&); +#endif + } + + SECTION("as an object value") + { + json j; + j["a"] = 1; + j["b"] = discarded; +#if JSON_DIAGNOSTICS + CHECK_THROWS_WITH_AS(json::to_msgpack(j), "[json.exception.type_error.321] (/b) cannot serialize discarded value to MessagePack", json::type_error&); +#else + CHECK_THROWS_WITH_AS(json::to_msgpack(j), "[json.exception.type_error.321] cannot serialize discarded value to MessagePack", json::type_error&); +#endif + } + + SECTION("nested deeper (array in object in array)") + { + json inner_array = {1, discarded}; + json middle_object; + middle_object["x"] = inner_array; + json const j = {middle_object}; +#if JSON_DIAGNOSTICS + CHECK_THROWS_WITH_AS(json::to_msgpack(j), "[json.exception.type_error.321] (/0/x/1) cannot serialize discarded value to MessagePack", json::type_error&); +#else + CHECK_THROWS_WITH_AS(json::to_msgpack(j), "[json.exception.type_error.321] cannot serialize discarded value to MessagePack", json::type_error&); +#endif + } } SECTION("null") diff --git a/tests/src/unit-ubjson.cpp b/tests/src/unit-ubjson.cpp index 80ca214a4..b31d42abd 100644 --- a/tests/src/unit-ubjson.cpp +++ b/tests/src/unit-ubjson.cpp @@ -27,10 +27,59 @@ TEST_CASE("UBJSON") { SECTION("discarded") { - // discarded values are not serialized + // a discarded value cannot be serialized to UBJSON json const j = json::value_t::discarded; - const auto result = json::to_ubjson(j); - CHECK(result.empty()); + CHECK_THROWS_WITH_AS(json::to_ubjson(j), "[json.exception.type_error.321] cannot serialize discarded value to UBJSON", json::type_error&); + } + + SECTION("discarded values nested in a container") + { + json const discarded = json::value_t::discarded; + + SECTION("in an array") + { + json const j = {1, discarded, 2}; +#if JSON_DIAGNOSTICS + CHECK_THROWS_WITH_AS(json::to_ubjson(j), "[json.exception.type_error.321] (/1) cannot serialize discarded value to UBJSON", json::type_error&); +#else + CHECK_THROWS_WITH_AS(json::to_ubjson(j), "[json.exception.type_error.321] cannot serialize discarded value to UBJSON", json::type_error&); +#endif + } + + SECTION("as an object value") + { + json j; + j["a"] = 1; + j["b"] = discarded; +#if JSON_DIAGNOSTICS + CHECK_THROWS_WITH_AS(json::to_ubjson(j), "[json.exception.type_error.321] (/b) cannot serialize discarded value to UBJSON", json::type_error&); +#else + CHECK_THROWS_WITH_AS(json::to_ubjson(j), "[json.exception.type_error.321] cannot serialize discarded value to UBJSON", json::type_error&); +#endif + } + + SECTION("nested deeper (array in object in array)") + { + json inner_array = {1, discarded}; + json middle_object; + middle_object["x"] = inner_array; + json const j = {middle_object}; +#if JSON_DIAGNOSTICS + CHECK_THROWS_WITH_AS(json::to_ubjson(j), "[json.exception.type_error.321] (/0/x/1) cannot serialize discarded value to UBJSON", json::type_error&); +#else + CHECK_THROWS_WITH_AS(json::to_ubjson(j), "[json.exception.type_error.321] cannot serialize discarded value to UBJSON", json::type_error&); +#endif + } + + SECTION("optimized array of all-discarded elements") + { + json const j = {discarded, discarded}; +#if JSON_DIAGNOSTICS + CHECK_THROWS_WITH_AS(json::to_ubjson(j, true, true), "[json.exception.type_error.321] (/0) cannot serialize discarded value to UBJSON", json::type_error&); +#else + CHECK_THROWS_WITH_AS(json::to_ubjson(j, true, true), "[json.exception.type_error.321] cannot serialize discarded value to UBJSON", json::type_error&); +#endif + } } SECTION("null") @@ -2091,9 +2140,14 @@ TEST_CASE("UBJSON") SECTION("discarded") { + // a discarded value cannot be serialized to UBJSON, even as part + // of an optimized array of a single (here: valueless) type json const j = {json::value_t::discarded, json::value_t::discarded}; - std::vector expected = {'[', '$', 'N', '#', 'i', 2}; - CHECK(json::to_ubjson(j, true, true) == expected); +#if JSON_DIAGNOSTICS + CHECK_THROWS_WITH_AS(json::to_ubjson(j, true, true), "[json.exception.type_error.321] (/0) cannot serialize discarded value to UBJSON", json::type_error&); +#else + CHECK_THROWS_WITH_AS(json::to_ubjson(j, true, true), "[json.exception.type_error.321] cannot serialize discarded value to UBJSON", json::type_error&); +#endif } } }