I'm trying to parse this YAML document using YamlDotNet:
title: Document with dynamic properties
/a:
description: something
/b:
description: other something
Into this object:
public class SimpleDoc
{
[YamlMember(Alias = "title")]
public string Title { get; set;}
public Dictionary<string, Path> Paths { get; set; }
}
public class Path
{
[YamlMember(Alias = "description")]
public string Description {get;set;}
}
I wish that the /a /b or any other unmatched properties end up inside the Paths dictionary. How can I configure YamlDotNet to support this scenario?
The deserialization setup is:
// file paths is the path to the specified doc :)
var deserializer = new Deserializer();
var doc = deserializer.Deserialize<SimpleDoc>(File.OpenText(filePath));
However it fails given that there's no property /a on SimpleDoc. And if I set up the setting ignoreUnmatched in the constructor to true it is ignored as expected.
I needed this for a project where the YAML schema is defined as JSON schema, which I used to generate classes (via
NJsonSchema). But, the actual data comes as YAML. So, I need to deserialize the YAML into the classes.I exploit the mechanism where, if a class implements
IDictionary<string, objectorIDictionary<object, object>, YamlDotNet will put all keys/values from that node into the dictionary. If I had derived the class fromDictionary<string, object>, I wouldn't have access to modify how values are added to that dictionary, so I was forced to implement the interface.In this example,
Languageis the class I'm trying to deserialize. I have other properties on this class as part of my generated classes (via the JSON schema generator). Since they are partial, I can implement this workaround.Basically, I created
AddAndMapand applied it to anywhere in the interface that adds dictionary entries. I also created theAdditionalPropertiesdictionary to hold our values. Keep in mind that we need all the values in theIDictionaryfor serializing this class properly.Next, I always add the entry to the backing
_dictionaryand then determine if I have a property to provide the value to. If I don't have a property that this value should map to, I put the value into theAdditionalPropertiesdictionary instead.To determine if I have an available property for deserization, I had to write a few extension methods.
Main thing to note is that this only works on properties of the class that are primitives or
Dictionary<object, object>. The reason is because it usesTypeConverter.ChangeTypeto return the appropriate object when setting the property. If you made a way to interpret how custom classes would be created, you would just replace theTypeConverter.ChangeTypecall with that solution.To get the deserializable properties of a class, I use reflection to get the
YamlMemberAttributeof the properties on that class. Then, I put thePropertyInfoof that property into a dictionary where the key is the value ofAliasfrom theYamlMemberAttribute. In my solution here, all properties require an alias to be defined for the property.Then, I find the property with an alias that matches the key that I got from the
AddAndMapcall. This recursively happens so that (in the future) it would be possible to have this work with custom class properties. It checks forDictionary<object, object>as the value type becauseYamlDotNet, when deserializing something into a dictionary, will always deserialize sub-classes intoDictionary<object, object>.Overall
This solution works and could be simplified if you only have flat (no custom class properties) of the class you are trying to deserialize. Or, it could be extended to support custom class properties. The solution could also be made into a base type that your other classes derive from, and you'd do
GetType()inDeserializablePropertiesinstead oftypeof(Language). It is an overly verbose solution, but untilYamlDotNethas a cleaner/simpler/proper solution, this was the best I could come up with after exploring their source code.Lastly, keep in mind that because your class derives of
IDictionary,YamlDotNetwill not serialize any properties from your class, even if they have theYamlMemberattributes on them. IfYamlDotNetwould provide that functionality, I had a cleaner solution in mind originally. Alas, that is not the case.