libmir / asdf

JSON library
http://asdf.libmir.org
Boost Software License 1.0
20 stars 8 forks source link

How to implement a proxy for array of SumType #15

Closed lempiji closed 3 years ago

lempiji commented 3 years ago

I tried to write the following code, but I could not figure out how to implement the Proxy. Does anyone have any good ideas?

code:

import sumtype;

struct ObjectA {
    string name;
}
struct ObjectB {
    double value;
}
alias MyObject = SumType!(ObjectA, ObjectB);

struct SomeObject {
    @serdeProxy!MyObjectArrayProxy MyObject[] objects;
}

struct MyObjectArrayProxy {
    // ?
}

data:

{
  "objects": [
    { "name": "test" },
    { "value": 1.5 }
  ]
}

I am trying to implement a configuration file with a strict type.

9il commented 3 years ago

It is a potentially buggy object structure. It is much better to have distinct arrays for each of the object types: "objectsA" and "objectsB".

Below are two examples: a proxy for a member of an array of algebraic and a proxy as an algebraic wrapper.

/// Proxy type for array of algebraics
unittest
{
    import mir.algebraic: Variant;

    static struct ObjectA
    {
        string name;
    }
    static struct ObjectB
    {
        double value;
    }

    alias MyObject = Variant!(ObjectA, ObjectB);

    static struct MyObjectArrayProxy
    {
        MyObject[] array;

        this(MyObject[] array) @safe pure nothrow @nogc
        {
            this.array = array;
        }

        T opCast(T : MyObject[])()
        {
            return array;
        }

        void serialize(S)(ref S serializer) const
        {
            auto state = serializer.arrayBegin;
            foreach (ref e; array)
            {
                serializer.elemBegin();
                // mir.algebraic has builtin support for serialization.
                // For other algebraic libraies one can use thier visitor handlers.
                serializeValue(serializer, e);
            }
            serializer.arrayEnd(state);
        }

        auto deserializeFromAsdf(Asdf asdfData)
        {
            import asdf : deserializeValue;
            import std.traits : EnumMembers;

            foreach (e; asdfData.byElement)
            {
                if (e["name"] != Asdf.init)
                {
                    array ~= MyObject(deserialize!ObjectA(e));
                }
                else
                {
                    array ~= MyObject(deserialize!ObjectB(e));
                }
            }

            return SerdeException.init;
        }
    }

    static struct SomeObject
    {
        @serdeProxy!MyObjectArrayProxy MyObject[] objects;
    }

    string data = q{{"objects":[{"name":"test"},{"value":1.5}]}};

    auto value = data.deserialize!SomeObject;
    assert (value.serializeToJson == data);
}
/// $(GMREF mir-core, mir, algebraic) with manual serialization.
unittest
{
    import asdf.asdf;

    static struct Response
    {
        import mir.algebraic: TaggedVariant;

        alias Union = TaggedVariant!(
            ["double_", "string", "array", "table"],
            double,
            string,
            Response[],
            Response[string],
        );

        Union data;
        alias Tag = Union.Kind;
        // propogates opEquals, opAssign, and other primitives
        alias data this;

        static foreach (T; Union.AllowedTypes)
            this(T v) @safe pure nothrow @nogc { data = v; }

        void serialize(S)(ref S serializer) const
        {
            import asdf: serializeValue;
            import mir.algebraic: visit;

            auto o = serializer.objectBegin();
            serializer.putKey("tag");
            serializer.serializeValue(kind);
            serializer.putKey("data");
            data.visit!(
                (double v) => serializer.serializeValue(v), // specialization for double if required
                (const Response[string] v) => serializer.serializeValue(cast(const(Response)[string])v),
                (v) => serializer.serializeValue(v),
            );
            serializer.objectEnd(o);
        }

        SerdeException deserializeFromAsdf(Asdf asdfData)
        {
            import asdf : deserializeValue;
            import std.traits : EnumMembers;

            Tag tag;
            if (auto e = asdfData["tag"].deserializeValue(tag))
                return e;
            final switch (tag)
            {
                foreach (m; EnumMembers!Tag)
                {
                    case m: {
                        alias T = Union.AllowedTypes[m];
                        data = T.init;
                        if (auto e = asdfData["data"].deserializeValue(data.trustedGet!T))
                            return e;
                        break;
                    }
                }
            }
            return null;
        }
    }

    Response v = 3.0;
    assert(v.kind == Response.Tag.double_);
    v = "str";
    assert(v == "str");

    import asdf;
    assert(v.serializeToJson == `{"tag":"string","data":"str"}`);
    v = Response.init;
    v = `{"tag":"array","data":[{"tag":"string","data":"S"}]}`.deserialize!Response;
    assert(v.kind == Response.Tag.array);
    assert(v.get!(Response[])[0] == "S");
}
lempiji commented 3 years ago

Thank you very much!

It seems that what was missing in my implementation was the constructor and opCast.

I omitted too much of the object structure in the example.

A more detailed description of what I want to do is as follows.

// this allow: { "name": "hoge" }
// But I want it to have either type or value.
struct FuzzyParam
{
    string name;
    @serdeOptional string type;
    @serdeOptional string value;
}

/////

struct FixedParam
{
    string name;
    string value;
}

struct RequiredParam
{
    string name;
    string type;
}

struct EditableParam
{
    string name;
    string type;
    string value;
}

// this can not allow: { "name": "test" }
// In this implements, I do not miss any combination at all.
alias StrictParam = SumType!(FixedParam, RequiredParam, EditableParam);