0

I'm trying to use Microsoft Bond to serialize nested objects. But Bond throws internal errors (such KeyNotFoundException).

My classes:

interface IFoo
{
}

[Bond.Schema]
class Foo1 : IFoo
{
     [Bond.Id(0)]
     public string Foo1Field { get; set; }
}

[Bond.Schema]
class Bar
{
     [Bond.Id(0)]
     public IFoo SomeFooInstance { get; set; }
}

Then I create an instance and serialize:

var src = new Bar() { SomeFooInstance = new Foo1() { Foo1Field = "Str" }};

var output = new OutputBuffer();
var writer = new CompactBinaryWriter<OutputBuffer>(output);

Serialize.To(writer, src);

var input = new InputBuffer(output.Data);
var reader = new CompactBinaryReader<InputBuffer>(input);

var dst = Deserialize<Bar>.From(reader);

But I'm getting exceptions (such KeyNotFoundException) at Serialize.To(writer, src);.

I also tried to add [Bond.Schema] to IFoo, but then the Deserialize<Bar>.From(reader); fails...

How can I serialize Bar class that contains some Foo class with Bond without getting exceptions like that?

nrofis
  • 8,975
  • 14
  • 58
  • 113

1 Answers1

0

If you want to use interfaces with behavioral differences (and not structural differences), the trick is to provide the deserializer with a factory so that it knows how to create a concrete instance of IFoo when it needs to. Notice that the implementations do not have the the [Bond.Schema] attribute, as they both implement the IFoo schema.

namespace NS
{       
    using System;
    using Bond.IO.Unsafe;
    using Bond.Protocols;

    internal static class Program
    {
        [Bond.Schema]
        interface IFoo
        {
            [Bond.Id(10)]
            string FooField { get; set; }
        }

        [Bond.Schema]
        class Bar
        {
            [Bond.Id(20)]
            public IFoo SomeFooInstance { get; set; }
        }

        class AlwaysUppercaseFoo : IFoo
        {
            private string fooField;

            public string FooField
            {
                get
                {
                    return fooField;
                }

                set
                {
                    fooField = value.ToUpperInvariant();
                }
            }
        }

        class IdentityFoo : IFoo
        {
            public string FooField { get; set; }
        }

        public static Expression NewAlwaysUppercaseFoo(Type type, Type schemaType, params Expression[] arguments)
        {
            if (schemaType == typeof(IFoo))
            {
                return Expression.New(typeof(AlwaysUppercaseFoo));
            }

            // tell Bond we don't handle the requested type, so it should use it's default behavior
            return null;
        }

        public static Expression NewIdentityFoo(Type type, Type schemaType, params Expression[] arguments)
        {
            if (schemaType == typeof(IFoo))
            {
                return Expression.New(typeof(IdentityFoo));
            }

            // tell Bond we don't handle the requested type, so it should use it's default behavior
            return null;
        }

        public static void Main(string[] args)
        {
            var src = new Bar() { SomeFooInstance = new IdentityFoo() { FooField = "Str" } };

            var output = new OutputBuffer();
            var writer = new CompactBinaryWriter<OutputBuffer>(output);

            Bond.Serialize.To(writer, src);

            {
                var input = new InputBuffer(output.Data);
                var deserializer = new Bond.Deserializer<CompactBinaryReader<InputBuffer>>(typeof(Bar), NewAlwaysUppercaseFoo);
                var reader = new CompactBinaryReader<InputBuffer>(input);
                var dst = deserializer.Deserialize<Bar>(reader);
                Debug.Assert(dst.SomeFooInstance.FooField == "STR");
            }

            {
                var input = new InputBuffer(output.Data);
                var deserializer = new Bond.Deserializer<CompactBinaryReader<InputBuffer>>(typeof(Bar), NewIdentityFoo);
                var reader = new CompactBinaryReader<InputBuffer>(input);
                var dst = deserializer.Deserialize<Bar>(reader);
                Debug.Assert(dst.SomeFooInstance.FooField == "Str");
            }
        }
    }
}

If you need both behavioral and structural differences, then you'll need to pair this with polymorphism and a bonded<IFoo> field so that you can delay deserialization until you have enough type information to select the proper implementation. (Polymorphism is explicit and opt-in in Bond.)

I'd show an example of this, but while writing up this answer on 2018-02-21, I found a bug in the handling of classes with [Bond.Schema] that implement interfaces with [Bond.Schema]: the fields from the interface are omitted.

For now, the workaround would be to use inheritance with classes and use virtual properties. For example:

namespace NS
{
    using System;
    using Bond.IO.Unsafe;
    using Bond.Protocols;

    internal static class Program
    {
        enum FooKind
        {
            Unknown = 0,
            AlwaysUppercase = 1,
            Identity = 2,
        }

        // intentionally a class to work around https://github.com/Microsoft/bond/issues/801 but simulate an interface somewhat
        [Bond.Schema]
        class IFoo
        {
            [Bond.Id(0)]
            public virtual string FooField { get; set; }
        }

        [Bond.Schema]
        class Bar
        {
            [Bond.Id(0)]
            public Bond.IBonded<IFoo> SomeFooInstance { get; set; }

            [Bond.Id(1)]
            public FooKind Kind { get; set; }
        }

        [Bond.Schema]
        class AlwaysUppercaseFoo : IFoo
        {
            private string fooField;

            public override string FooField
            {
                get
                {
                    return fooField;
                }

                set
                {
                    fooField = value.ToUpperInvariant();
                }
            }

            [Bond.Id(0)]
            public string JustAlwaysUppercaseFooField { get; set; }
        }

        [Bond.Schema]
        class IdentityFoo : IFoo
        {
            [Bond.Id(42)]
            public string JustIdentityFooField { get; set; }
        }

        static void RoundTripAndPrint(Bar src)
        {
            var output = new OutputBuffer();
            var writer = new CompactBinaryWriter<OutputBuffer>(output);

            Bond.Serialize.To(writer, src);

            var input = new InputBuffer(output.Data);
            var reader = new CompactBinaryReader<InputBuffer>(input);
            var dst = Bond.Deserialize<Bar>.From(reader);

            switch (dst.Kind)
            {
                case FooKind.Identity:
                    {
                        var fooId = dst.SomeFooInstance.Deserialize<IdentityFoo>();
                        Console.WriteLine($"IdFoo: \"{fooId.FooField}\", \"{fooId.JustIdentityFooField}\"");
                    }
                    break;

                case FooKind.AlwaysUppercase:
                    {
                        var fooUc = dst.SomeFooInstance.Deserialize<AlwaysUppercaseFoo>();
                        Console.WriteLine($"UcFoo: \"{fooUc.FooField}\", \"{fooUc.JustAlwaysUppercaseFooField}\"");
                    }
                    break;

                default:
                    Console.WriteLine($"Unknown Kind: {dst.Kind}");
                    break;
            }
        }

        public static void Main(string[] args)
        {
            var o = new OutputBuffer();
            var w = new CompactBinaryWriter<OutputBuffer>(o);
            Bond.Serialize.To(w, new IdentityFoo() { FooField = "Str", JustIdentityFooField = "id" });

            var src_id = new Bar()
            {
                SomeFooInstance = new Bond.Bonded<IdentityFoo>(new IdentityFoo() { FooField = "Str", JustIdentityFooField = "id" }),
                Kind = FooKind.Identity
            };

            var src_uc = new Bar()
            {
                SomeFooInstance = new Bond.Bonded<AlwaysUppercaseFoo>(new AlwaysUppercaseFoo() { FooField = "Str", JustAlwaysUppercaseFooField = "I LIKE TO YELL!" }),
                Kind = FooKind.AlwaysUppercase
            };

            RoundTripAndPrint(src_id);
            RoundTripAndPrint(src_uc);
        }
    }
}

This prints:

IdFoo: "Str", "id"
UcFoo: "STR", "I LIKE TO YELL!"
chwarr
  • 6,777
  • 1
  • 30
  • 57
  • Thank you so much, but the IFoo is an empty interface because Foo classes have structural differences – nrofis Feb 22 '18 at 11:28
  • If you need both structural and behavioral differences, you'll need to use a base class for now until the bug with interface fields not being serialized is resolved. The second code block shows an example of this. – chwarr Feb 22 '18 at 19:01