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?
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!"