Skip to content

sax_parse cannot read CBOR that contains tags, including binary values with a subtype written by to_cbor #5676

Description

@nlohmann

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

  1. Save the program below as sax_cbor.cpp.
  2. 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.

Activity

  1. Tyagiquamar commented on Sep 30, 2026

    @Tyagiquamar
    Contributor

    I can reproduce this on current develop. I plan to add explicit SAX overloads with a required CBOR tag-handler argument, preserving the signatures and defaults of the existing overloads, plus regression coverage and documentation. I will check for overlapping PRs again before submitting.

  2. added this to the Release 3.13.0 milestone on Oct 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions