DEPRECATED
This feature is now part of YAML.NET.
See aaubry/YamlDotNet#774 and https://github.com/aaubry/YamlDotNet/wiki/Deserialization---Type-Discriminators.
Addresses aaubry/YamlDotNet#343:
Determining the type from a field
This collection of classes allows one to deserialize abstract classes or interfaces by inspecting the parser's parsing events to choose an appropriate type for the YamlDotNet derserializer to use.
AbstractNodeNodeTypeResolver
does must of the heavy work by buffering parse events when it encounters an abstract type or interface that we've registered a type resolver for. It then checks if any registered type discriminators can work with the sub-graph of parsing events.IParserExtensions.cs
adds a handy extension toIParser
that allows for the easy inspection of keys and values in only the current mapping, ignoring any nested mappings.ITypeDiscriminator
is a provider interface. For each abstract/interface type you should create a type discrimnator that inspects parsing events and returns an appropriate type when its conditions are met.ParsingEventBuffer
is a utility used byAbstractNodeResolver
to buffer the streaming parsing events from YamlDotNet's parser. The buffer allows for the events to be replayed which:- allows for multiple
ITypeDicriminators
to be used - each recieving a replay of the events they can inspect - and once a type has been selected, replays the parsing events to the standard object node serializer - as if nothing ever happened
- allows for multiple
- Finally two example type discriminators (specific to my project) are included:
ExpectationTypeResolver
looks for the presence of mapping keys that are unique to the descendent/concrete type. IF a key is found the appropriate Type is returnedAggregateExpectationTypeResolver
looks for a key that is common that has the desired type encoded in its value. This is similar to a kind key on a TypeScript discriminated union.
This solution does slow down the serializer (technically) and add memory pressure (technically). For small file sizes the difference should be trival (I haven't noticed). For large file sizes, try to only resolve types on leaf nodes (the most nested mappings). This will kepp the number of events stored in the buffer small and allow the parser to continue to operate in a mostly streaming fashion.
Usage example:
NamingConvention = UnderscoredNamingConvention.Instance;
// these resolvers allow us to deserialize to an abstract class or interface
var aggregateExpectationResolver = new AggregateExpectationTypeResolver(NamingConvention);
var expectationResolver = new ExpectationTypeResolver(NamingConvention);
YamlDeserializer = new DeserializerBuilder()
.WithNamingConvention(NamingConvention)
.WithNodeDeserializer(
inner => new AbstractNodeNodeTypeResolver(inner, aggregateExpectationResolver, expectationResolver),
s => s.InsteadOf<ObjectNodeDeserializer>())
.Build();
// my domain models
public class Model {
IExpectation[] Expectation {get; set;}
}
public abstract class Expectation : IExpectation { /** blah blah blah **/ }
public class BoundsExpectation : Expectation { /** blah blah blah **/ }
public class CentroidExpectation : Expectation { /** blah blah blah **/ }
public class TimeExpectation : Expectation { /** blah blah blah **/ }
public abstract class AggregateExpectation : IExpectation {
/// <summary>
/// Essentially a `kind` property - determines which child type to instantiate.
/// </summary>
public string SegmentWith { get; init; }
}
public class EventCount : AggregateExpectation { /** blah blah blah **/ }
public class NoEvents : AggregateExpectation { /** blah blah blah **/ }
And then this yaml can be deserialzied:
- segment_with: event_count # deserializes to EventCount
- segment_with: no_events # deserializes to NoEvents
- bounds: abc # deserializes to BoundsExpectation
- centroid: null # deserializes to CentroidExpectation
- time: # deserializes to TimeExpectation
LICENSE: MIT
This is extensive work, well done, learned me a lot. I seem to know to little still.... I could not get it to work. I was notsure how to kick off the Deserialization, so I did this:
where "Model" is the class as you supplied above.
When I step through the code the issue I found was that in the class "AbstractNodeNodeTypeResolver" method "Deserialize" at these lines:
The Deserialization "expectedType" is the "Model", therefore it is not one of the "supportedTypes" and goes into calling original.Deserialize and then attempts to Deserialze the example as you gave above and starts with and exception that proeprty "segment_with" is not part of "Model". I am probably doing something wrng, but I cannot see a way around this, unless I add another discriminator for the root "Model" object. I will appreciate you expert advise on this, and thank you for all your work, I can see a lot of work in the above code.
I have the Yaml I supply as the following, since this relates to the problem I need to solve:
My Model looks like this: