From 675e519966404f2e2ea0160c5ea28d4a3607fc2a Mon Sep 17 00:00:00 2001 From: Mohd Quamar Tyagi <104281681+Tyagiquamar@users.noreply.github.com> Date: Wed, 7 Oct 2026 11:08:24 +0530 Subject: [PATCH] Allow SAX parsing of tagged CBOR (#5740) * Allow SAX parsing of tagged CBOR Signed-off-by: Tyagiquamar * Consolidate sax_parse overloads with default tag_handler parameter Signed-off-by: Tyagiquamar * Add version history entry for tag_handler in sax_parse documentation --------- Signed-off-by: Tyagiquamar Signed-off-by: Niels Lohmann Co-authored-by: Niels Lohmann --- .../docs/api/basic_json/cbor_tag_handler_t.md | 4 +- docs/mkdocs/docs/api/basic_json/sax_parse.md | 13 ++++-- include/nlohmann/json.hpp | 15 ++++--- tests/src/unit-regression3.cpp | 40 +++++++++++++++++++ 4 files changed, 61 insertions(+), 11 deletions(-) diff --git a/docs/mkdocs/docs/api/basic_json/cbor_tag_handler_t.md b/docs/mkdocs/docs/api/basic_json/cbor_tag_handler_t.md index dfeb5d693..55a7954d5 100644 --- a/docs/mkdocs/docs/api/basic_json/cbor_tag_handler_t.md +++ b/docs/mkdocs/docs/api/basic_json/cbor_tag_handler_t.md @@ -9,10 +9,10 @@ enum class cbor_tag_handler_t }; ``` -This enumeration is used in the [`from_cbor`](from_cbor.md) function to choose how to treat tags: +This enumeration is used in [`from_cbor`](from_cbor.md) and [`sax_parse`](sax_parse.md) to choose how to treat tags: error -: throw a `parse_error` exception in case of a tag +: report a parse error in case of a tag (the `from_cbor` overloads throw a `parse_error` exception by default) ignore : ignore tags diff --git a/docs/mkdocs/docs/api/basic_json/sax_parse.md b/docs/mkdocs/docs/api/basic_json/sax_parse.md index 0d4b8da8a..fc48c9e12 100644 --- a/docs/mkdocs/docs/api/basic_json/sax_parse.md +++ b/docs/mkdocs/docs/api/basic_json/sax_parse.md @@ -8,7 +8,8 @@ static bool sax_parse(InputType&& i, input_format_t format = input_format_t::json, const bool strict = true, const bool ignore_comments = false, - const bool ignore_trailing_commas = false); + const bool ignore_trailing_commas = false, + const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error); // (2) template @@ -17,13 +18,14 @@ static bool sax_parse(IteratorType first, SentinelType last, input_format_t format = input_format_t::json, const bool strict = true, const bool ignore_comments = false, - const bool ignore_trailing_commas = false); + const bool ignore_trailing_commas = false, + const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error); ``` Read from input and generate SAX events 1. Read from a compatible input. -2. Read from a pair of character iterators, or an iterator and a sentinel of a different type (C++20 ranges support) +2. Read from a pair of character iterators, or an iterator and a sentinel of a different type (C++20 ranges support). The value_type of the iterator must be an integral type with a size of 1, 2, or 4 bytes, which will be interpreted respectively as UTF-8, UTF-16, and UTF-32. If `SentinelType` differs from `IteratorType`, it must be comparable to @@ -82,6 +84,10 @@ The SAX event lister must follow the interface of [`json_sax`](../json_sax/index : whether trailing commas in arrays or objects should be ignored and treated like whitespace (`#!cpp true`) or yield a parse error (`#!cpp false`); (optional, `#!cpp false` by default) +`tag_handler` (in) +: how to handle CBOR tags; see [`cbor_tag_handler_t`](cbor_tag_handler_t.md). Ignored for formats other than CBOR + (optional, `cbor_tag_handler_t::error` by default). + `first` (in) : iterator to the start of a character range @@ -137,6 +143,7 @@ A UTF-8 byte order mark is silently ignored. - Added in version 3.2.0. - Ignoring comments via `ignore_comments` added in version 3.9.0. - Added `ignore_trailing_commas` in version 3.13.0. +- Added `tag_handler` in version 3.13.0. - Extended container support (1) to include types with lvalue-only ADL `begin`/`end` (matching `std::begin`/`std::end` semantics) in version 3.13.0. - Extended overload (2) to accept heterogeneous iterator+sentinel pairs (C++20 ranges support) in version 3.13.0. - `JSON_PRECISE_STREAM_POSITION` added in version 3.13.0 to optionally leave a `#!cpp std::istream` positioned right diff --git a/include/nlohmann/json.hpp b/include/nlohmann/json.hpp index 788cc999c..397b253ad 100644 --- a/include/nlohmann/json.hpp +++ b/include/nlohmann/json.hpp @@ -5671,12 +5671,13 @@ public: input_format_t format = input_format_t::json, const bool strict = true, const bool ignore_comments = false, - const bool ignore_trailing_commas = false) + const bool ignore_trailing_commas = false, + const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error) { auto ia = detail::input_adapter(std::forward(i)); return format == input_format_t::json ? parser(std::move(ia), nullptr, true, ignore_comments, ignore_trailing_commas).sax_parse(sax, strict) - : detail::binary_reader(std::move(ia), format).sax_parse(sax, strict); + : detail::binary_reader(std::move(ia), format).sax_parse(sax, strict, tag_handler); } /// @brief generate SAX events (iterator pair, or iterator+sentinel pair for C++20 ranges support) @@ -5688,12 +5689,13 @@ public: input_format_t format = input_format_t::json, const bool strict = true, const bool ignore_comments = false, - const bool ignore_trailing_commas = false) + const bool ignore_trailing_commas = false, + const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error) { auto ia = detail::input_adapter(std::move(first), std::move(last)); return format == input_format_t::json ? parser(std::move(ia), nullptr, true, ignore_comments, ignore_trailing_commas).sax_parse(sax, strict) - : detail::binary_reader(std::move(ia), format).sax_parse(sax, strict); + : detail::binary_reader(std::move(ia), format).sax_parse(sax, strict, tag_handler); } /// @brief generate SAX events @@ -5721,7 +5723,8 @@ public: input_format_t format = input_format_t::json, const bool strict = true, const bool ignore_comments = false, - const bool ignore_trailing_commas = false) + const bool ignore_trailing_commas = false, + const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error) #if JSON_DELETE_DEPRECATED_FUNCTIONS = delete; #else @@ -5731,7 +5734,7 @@ public: // NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg) ? parser(std::move(ia), nullptr, true, ignore_comments, ignore_trailing_commas).sax_parse(sax, strict) // NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg) - : detail::binary_reader(std::move(ia), format).sax_parse(sax, strict); + : detail::binary_reader(std::move(ia), format).sax_parse(sax, strict, tag_handler); } #endif #if defined(__clang__) diff --git a/tests/src/unit-regression3.cpp b/tests/src/unit-regression3.cpp index 95b2b6d4f..12060c63d 100644 --- a/tests/src/unit-regression3.cpp +++ b/tests/src/unit-regression3.cpp @@ -862,6 +862,46 @@ TEST_CASE("issue #5338 - truncated CBOR tagged binary subtype is rejected") } } +TEST_CASE("issue #5676 - SAX parsing of CBOR tags") +{ + const json expected = json::binary({1, 2, 3}, 42); + const auto cbor = json::to_cbor(expected); + + nlohmann::detail::json_sax_acceptor acceptor; + CHECK_FALSE(json::sax_parse(cbor, &acceptor, json::input_format_t::cbor)); + CHECK_FALSE(json::sax_parse(cbor, &acceptor, json::input_format_t::cbor, + true, false, false, json::cbor_tag_handler_t::error)); + + CHECK(json::sax_parse(cbor, &acceptor, json::input_format_t::cbor, + true, false, false, json::cbor_tag_handler_t::ignore)); + + json parsed; + nlohmann::detail::json_sax_dom_parser sax(parsed); + CHECK(json::sax_parse(cbor, &sax, json::input_format_t::cbor, + true, false, false, json::cbor_tag_handler_t::store)); + CHECK(parsed == expected); + + json iterator_parsed; + nlohmann::detail::json_sax_dom_parser iterator_sax(iterator_parsed); + CHECK(json::sax_parse(cbor.begin(), cbor.end(), &iterator_sax, json::input_format_t::cbor, + true, false, false, json::cbor_tag_handler_t::store)); + CHECK(iterator_parsed == expected); + + json span_parsed; + nlohmann::detail::json_sax_dom_parser span_sax(span_parsed); + CHECK(json::sax_parse(nlohmann::detail::span_input_adapter(cbor.data(), cbor.size()), &span_sax, + json::input_format_t::cbor, true, false, false, json::cbor_tag_handler_t::store)); + CHECK(span_parsed == expected); + + const std::string text = "null"; + CHECK(json::sax_parse(text, &acceptor, json::input_format_t::json, + true, false, false, json::cbor_tag_handler_t::store)); + CHECK(json::sax_parse(text.begin(), text.end(), &acceptor, json::input_format_t::json, + true, false, false, json::cbor_tag_handler_t::store)); + CHECK(json::sax_parse(nlohmann::detail::span_input_adapter(text.data(), text.size()), &acceptor, + json::input_format_t::json, true, false, false, json::cbor_tag_handler_t::store)); +} + TEST_CASE("issue #5402 - update(merge_objects=true) overwrites a primitive with an object") { json t = {{"k", 1}};