Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How Can I Parse YAML Into a Derived Collection Using YamlDotNet?

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?

like image 902
Matt Houser Avatar asked Mar 21 '17 22:03

Matt Houser


1 Answers

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();
like image 121
Mariusz Jamro Avatar answered Nov 01 '22 08:11

Mariusz Jamro