getml / reflect-cpp

A C++20 library for fast serialization, deserialization and validation using reflection. Supports JSON, BSON, CBOR, flexbuffers, msgpack, TOML, XML, YAML / msgpack.org[C++20]
https://getml.github.io/reflect-cpp/
MIT License
982 stars 85 forks source link

`std::optional` for cbor #135

Open dcorbeil opened 3 months ago

dcorbeil commented 3 months ago

The cbor implementation doesn't seem to be handling std::optional as documented (for json at least). Consider the following struct example:

struct optional_test_t {
    int i;
    std::optional<int> opt_i;
};

Reading

const optional_test_t test =
    optional_test_t{
        .i = 42
    };

std::vector<uint8_t> cbor_bytes = {
    0xA1,      // map(1)
    0x61,      // text(1)
        0x69,  // "i"
    0x18, 0x2A // unsigned(42)
};
const std::string json_string = R"({"i":42})";

auto test_from_json_res = rfl::json::read<optional_test_t>(json_string);
auto test_from_cbor_res = rfl::cbor::read<optional_test_t>((char*)cbor_bytes.data(), cbor_bytes.size());

optional_test_t test_from_json = test_from_json_res.value(); // OK
optional_test_t test_from_cbor = test_from_cbor_res.value(); // Field named 'opt_i' not found.

Writing

const optional_test_t test =
    optional_test_t{
        .i = 42
    };

const std::vector<char> expected_cbor = {
    (char)0xA1,            // map(1)
    (char)0x61,            // text(1)
        (char)0x69,        // "i"
    (char)0x18, (char)0x2A // unsigned(42)
};
const std::string expected_json = R"({"i":42})";

const std::string json = rfl::json::write(test);
const std::vector<char> cbor = rfl::cbor::write(test);

assert(expected_json == json); // OK
assert(expected_cbor == cbor); // Fail. The written cbor bytes translates to "{"i": 42, "opt_i": null}"

Just like for rfl::json, I expected rfl::cbor::read() to successfully parse a buffer of bytes that doesn't contain std::optional fields leaving the missing optionals as std::nullopt. I also expected rfl::cbor::write() to not emit std::optional fields in the buffer of bytes.

Long story short, would it be possible for rfl::cbor to behave like rfl::json when it comes to std::optional?

Thanks!

liuzicheng1987 commented 3 months ago

You are right...the reason this is happening is because msgpack and cbor require you to set the size of the object in advance, which makes it difficult, but not impossible to do this.

I think I could change it, and at the very least the behaviour should be documented.

dcorbeil commented 3 months ago

[...] because msgpack and cbor require you to set the size of the object in advance

Is this related to the expectation that maps are of finite length? This issue might not be the right place to talk about this but I was curious as to what the reason is behind that decision.

I think I could change it

That would be awesome if you did :)