Search code examples
c++msgpack

msgpack: Unpack custom class without knowing the type


This is my code snippet to pack and unpack a known class:

MessageCoffeeIsReady input(1, "Black coffee is ready");

// ---- Serialize to buffer
msgpack::sbuffer sbuf;
msgpack::pack(sbuf, input);

// ---- Send over line...

// ---- Unpack received buffer
msgpack::object_handle oh = msgpack::unpack(sbuf.data(), sbuf.size());
msgpack::object        obj = oh.get();

// ---- Convert to message
MessageCoffeIsReady result; // <- How do i know the msgpack::object contains `MessageCoffeIsReady`?
obj.convert(result);

My class looks like this:

class MessageCoffeeIsReady : public MessageBase
{
    protected:

        std::string m_name;
        int         m_id;

    public:

        MessageCoffeeIsReady(int id, std::string name)
        { m_id = id; m_name = name; }

        MSGPACK_DEFINE (m_name, m_id);
};

My question is: How do i know i have a received a message of type MessageCoffeIsReady?

Is there any internal typeid which i could use to convert to the specific class?


Solution

  • MsgPack is a JSON-like format without support for user-defined types in its protocol. When you use the MSGPACK_DEFINE macro, it just maps

    But, you can still add your own type tag to support something like a discriminated union. That wouldn't let you identify any arbitrary object, but if you only plan to send one of a few types of objects, it's a good fit.

    struct Any {
        std::string type;
        msgpack::object data;
        MSGPACK_DEFINE(type, data);
    };
    

    This stored an object, and a string (type) to identify that object. Then you can pack objects into it:

    struct Foo {
        int a;
        std::string b;
        MSGPACK_DEFINE(a, b);
    };
    
    struct Bar {
        double c;
        MSGPACK_DEFINE(c);
    };
    
    /* ... */
    
    auto foo = Any { "foo", msgpack::object(Foo { 42, "hi" }, z) };
    auto foo_obj = msgpack::object(foo, z);
    auto bar = Any { "bar", msgpack::object(Bar { 25.5 }, z) };
    auto bar_obj = msgpack::object(bar, z);
    

    And to get things out, convert first to an any. Then convert to different types based on the tag:

    Any any;
    obj.convert(any);
    if (any.type == "foo") {
        Foo foo;
        any.data.convert(foo);
        std::cout << "foo(a=" << foo.a << ", b=" << foo.b << ")\n";
    } else if (any.type == "bar") {
        Bar bar;
        any.data.convert(bar);
        std::cout << "bar(c=" << bar.c << ")\n";
    }
    

    Of course, that's only if you have some control over what objects you're seeing. If you really want to get granular, you can inspect the underlying JSON-like data manually. Here's a sample from the docs:

    if (o.type != msgpack::type::ARRAY) throw msgpack::type_error();
    if (o.via.array.size != 2) throw msgpack::type_error();
    v = my_class(
        o.via.array.ptr[0].as<std::string>(),
        o.via.array.ptr[1].as<int>());
    

    Then it would be up to you to validate if the object you're inspecting is valid.