Description
sax_parse supports CBOR input, but it has no parameter for a cbor_tag_handler_t. The CBOR reader therefore always runs with the default cbor_tag_handler_t::error, so any CBOR document that contains a tag fails with parse_error.112. That includes the output of to_cbor for every binary value with a subtype, because the writer stores subtypes as tags 0xD8..0xDB (cbor.md).
from_cbor has the same default, but it takes a tag_handler argument, and the documentation tells users to pass ignore or store (cbor.md). With sax_parse there is no such way, so tagged CBOR cannot be processed with the SAX interface at all. Tags are common in real-world CBOR, for example for date/time, bignums and URIs.
The underlying detail::binary_reader::sax_parse already takes the handler (binary_reader.hpp). The three public basic_json::sax_parse overloads just don't forward one (json.hpp).
Possible fix (untested):
- Add a trailing
const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error parameter to the three sax_parse overloads and pass it to binary_reader::sax_parse.
- Alternatively, add CBOR-specific overloads, since the parameter only matters for CBOR, much like
ignore_comments and ignore_trailing_commas only matter for JSON.
Neither changes behavior for existing callers.
Related: #1968 (tags were not skipped by from_cbor, fixed by adding cbor_tag_handler_t in #2273), #5316 (store semantics).
Reproduction steps
- Save the program below as
sax_cbor.cpp.
clang++ -std=c++11 -I include sax_cbor.cpp -o sax_cbor && ./sax_cbor
Expected vs. actual results
- Expected: a way to SAX-parse the CBOR document that
to_cbor produced, as from_cbor(..., cbor_tag_handler_t::store) can.
- Actual:
sax_parse reports parse_error.112 at the tag byte 0xD8, and there is no argument to change that.
Minimal code example
#include <nlohmann/json.hpp>
#include <cstdio>
#include <iostream>
using json = nlohmann::json;
// a SAX handler that only counts events
struct counter : nlohmann::json_sax<json>
{
std::size_t events = 0;
bool null() override { return ++events != 0; }
bool boolean(bool) override { return ++events != 0; }
bool number_integer(number_integer_t) override { return ++events != 0; }
bool number_unsigned(number_unsigned_t) override { return ++events != 0; }
bool number_float(number_float_t, const string_t&) override { return ++events != 0; }
bool string(string_t&) override { return ++events != 0; }
bool binary(binary_t&) override { return ++events != 0; }
bool start_object(std::size_t) override { return ++events != 0; }
bool key(string_t&) override { return ++events != 0; }
bool end_object() override { return ++events != 0; }
bool start_array(std::size_t) override { return ++events != 0; }
bool end_array() override { return ++events != 0; }
bool parse_error(std::size_t, const std::string&, const nlohmann::detail::exception& e) override
{
std::cout << "parse_error: " << e.what() << '\n';
return false;
}
};
int main()
{
// to_cbor writes the subtype of a binary value as a CBOR tag (here: D8 2A)
const json j = {{"data", json::binary({1, 2, 3}, 42)}};
const std::vector<std::uint8_t> cbor = json::to_cbor(j);
for (auto b : cbor)
{
std::printf("%02X ", b);
}
std::printf("\n");
// from_cbor reads it when given a tag handler
std::cout << "from_cbor with cbor_tag_handler_t::store: " << json::from_cbor(cbor, true, true, json::cbor_tag_handler_t::store) << '\n';
// sax_parse has no tag handler parameter and always rejects tags
counter handler;
const bool ok = json::sax_parse(cbor, &handler, json::input_format_t::cbor);
std::cout << "sax_parse returned " << ok << " after " << handler.events << " events\n";
}
Error messages
A1 64 64 61 74 61 D8 2A 43 01 02 03
from_cbor with cbor_tag_handler_t::store: {"data":{"bytes":[1,2,3],"subtype":42}}
parse_error: [json.exception.parse_error.112] parse error at byte 7: syntax error while parsing CBOR value: invalid byte: 0xD8
sax_parse returned 0 after 2 events
Compiler and operating system
Apple clang 21.0.0 (clang-2100.3.34.2), macOS 27.0 (arm64)
Library version
develop @ 633de8e
Validation
This issue was written by Claude Code on behalf of @nlohmann.
Description
sax_parsesupports CBOR input, but it has no parameter for acbor_tag_handler_t. The CBOR reader therefore always runs with the defaultcbor_tag_handler_t::error, so any CBOR document that contains a tag fails with parse_error.112. That includes the output ofto_cborfor every binary value with a subtype, because the writer stores subtypes as tags 0xD8..0xDB (cbor.md).from_cborhas the same default, but it takes atag_handlerargument, and the documentation tells users to passignoreorstore(cbor.md). Withsax_parsethere is no such way, so tagged CBOR cannot be processed with the SAX interface at all. Tags are common in real-world CBOR, for example for date/time, bignums and URIs.The underlying
detail::binary_reader::sax_parsealready takes the handler (binary_reader.hpp). The three publicbasic_json::sax_parseoverloads just don't forward one (json.hpp).Possible fix (untested):
const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::errorparameter to the threesax_parseoverloads and pass it tobinary_reader::sax_parse.ignore_commentsandignore_trailing_commasonly matter for JSON.Neither changes behavior for existing callers.
Related: #1968 (tags were not skipped by
from_cbor, fixed by addingcbor_tag_handler_tin #2273), #5316 (storesemantics).Reproduction steps
sax_cbor.cpp.clang++ -std=c++11 -I include sax_cbor.cpp -o sax_cbor && ./sax_cborExpected vs. actual results
to_cborproduced, asfrom_cbor(..., cbor_tag_handler_t::store)can.sax_parsereports parse_error.112 at the tag byte 0xD8, and there is no argument to change that.Minimal code example
Error messages
Compiler and operating system
Apple clang 21.0.0 (clang-2100.3.34.2), macOS 27.0 (arm64)
Library version
develop@ 633de8eValidation
developbranch is used.This issue was written by Claude Code on behalf of @nlohmann.