Using YamlDotNet, I am attempting to deserialize the following YAML:
Collection:
- Type: TypeA
TypeAProperty: value1
- Type: TypeB
TypeBProperty: value2
The Type
property is a required property for all objects under Collection
. The rest of the properties are dependent on the type.
This is my ideal object model:
public class Document
{
public IEnumerable<IBaseObject> Collection { get; set; }
}
public interface IBaseObject
{
public string Type { get; }
}
public class TypeAClass : IBaseObject
{
public string Type { get; set; }
public string TypeAProperty { get; set; }
}
public class TypeBClass : IBaseObject
{
public string Type { get; set; }
public string TypeBProperty { get; set; }
}
Based on my reading, I think my best bet is to use a custom node deserializer, derived from INodeDeserializer
. As a proof of concept, I can do this:
public class MyDeserializer : INodeDeserializer
{
public bool Deserialize(IParser parser, Type expectedType, Func<IParser, Type, object> nestedObjectDeserializer, out object value)
{
if (expectedType == typeof(IBaseObject))
{
Type type = typeof(TypeAClass);
value = nestedObjectDeserializer(parser, type);
return true;
}
value = null;
return false;
}
}
My issue now is how to dynamically determine the Type
to choose before calling nestedObjectDeserializer
.
When using JSON.Net, I was able to use a CustomCreationConverter
, read the sub-JSON into a JObject
, determine my type, then create a new JsonReader
from the JObject
and re-parse the object.
Is there a way I can read, roll-back, then re-read nestedObjectDeserializer
?
Is there another object type I can call on nestedObjectDeserializer
, then from that read the Type
property, finally proceed through normal YamlDotNet parsing of the derived type?
It's not easy. Here is an GitHub issue explaining how to do polymorphic serialization using YamlDotNet.
A simple solution in your case is to to 2-step deserialization. First you deserialize into some intermediary form and than convert it to your models. That's relatively easy as you limit digging in the internals of YamlDotNet:
public class Step1Document
{
public List<Step1Element> Collection { get; set; }
public Document Upcast()
{
return new Document
{
Collection = Collection.Select(m => m.Upcast()).ToList()
};
}
}
public class Step1Element
{
// Fields from TypeA and TypeB
public string Type { get; set; }
public string TypeAProperty { get; set; }
public string TypeBProperty { get; set; }
internal IBaseObject Upcast()
{
if(Type == "TypeA")
{
return new TypeAClass
{
Type = Type,
TypeAProperty = TypeAProperty
};
}
if (Type == "TypeB")
{
return new TypeBClass
{
Type = Type,
TypeBProperty = TypeBProperty
};
}
throw new NotImplementedException(Type);
}
}
And that to deserialize:
var serializer = new DeserializerBuilder().Build();
var document = serializer.Deserialize<Step1Document>(data).Upcast();
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With