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
Id love for this to be impolemented