12

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?

Matt Houser
  • 33,983
  • 6
  • 70
  • 88

1 Answers1

1

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();
Mariusz Jamro
  • 30,615
  • 24
  • 120
  • 162