Skip to content

Request for JCS serialization mode (rfc8785) #1101

@slnj

Description

@slnj

I was wondering if there is any interest in implementing an incorporating JCS serialization (https://datatracker.ietf.org/doc/html/rfc8785) to be able to hash/sign json documents.

I understand something like this should do it, but a lowlevel implementation accessing internal structure should be much better:

#pragma once
#include <boost/json.hpp>
#include <vector>
#include <string>
#include <algorithm>
#include <ostream>

namespace jcs // keep our code out of boost::json to avoid ODR surprises
{
namespace detail {

namespace json = boost::json;

// Writes a JSON string literal (with quotes and escapes) to the stream
inline void write_string_literal(std::ostream& os, boost::json::string_view sv) {
    // Construct a temporary boost::json::string and let Boost do the escaping
    json::string tmp{sv.data(), sv.size()};
    os << tmp; // operator<< will serialize with quotes and escapes
}

inline void write_value_jcs(std::ostream& os, const json::value& v); // fwd

inline void write_object_jcs(std::ostream& os, const json::object& obj) {
    // Collect pointers to members and sort by key (lexicographic UTF-8)
    std::vector<std::pair<json::string_view, const json::value*>> members;
    members.reserve(obj.size());
    for (const auto& kv : obj) {
        members.emplace_back(kv.key(), &kv.value());
    }

    std::sort(members.begin(), members.end(),
              [](const auto& a, const auto& b) {
                  // UTF-8 bytewise comparison is acceptable for JCS key ordering
                  return a.first < b.first;
              });

    os << '{';
    bool first = true;
    for (const auto& [k, vp] : members) {
        if (!first) os << ',';
        first = false;

        // "key":
        write_string_literal(os, k);
        os << ':';

        // value
        write_value_jcs(os, *vp);
    }
    os << '}';
}

inline void write_array_jcs(std::ostream& os, const json::array& arr) {
    os << '[';
    bool first = true;
    for (const auto& e : arr) {
        if (!first) os << ',';
        first = false;
        write_value_jcs(os, e); // preserve order; no sorting in JCS
    }
    os << ']';
}

inline void write_value_jcs(std::ostream& os, const json::value& v) {
    if (v.is_object()) {
        write_object_jcs(os, v.as_object());
    } else if (v.is_array()) {
        write_array_jcs(os, v.as_array());
    } else {
        // Scalars: let Boost.JSON print compact JSON (numbers/strings/bool/null)
        // This uses Boost's serializer for correct numeric format and string escapes
        os << v;
    }
}

} // namespace detail

// Public API: write a boost::json::value in JCS-style to a std::ostream
inline void serialize_jcs_to_stream(const boost::json::value& v, std::ostream& os) {
    detail::write_value_jcs(os, v);
}

// Convenience: return std::string (uses ostringstream)
inline std::string serialize_jcs(const boost::json::value& v) {
    std::ostringstream oss;
    serialize_jcs_to_stream(v, oss);
    return oss.str();
}

} // namespace jcs

int main() {
    // Unordered JSON
    boost::json::value val = boost::json::parse(R"({"z":2,"a":{"c":3,"b":2},"x":1})");

    // JCS serialization
    std::string out = jcs::serialize_jcs(val);

    std::cout << sign_stub(out) << "\n";
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions