Skip to content

Commit

Permalink
fail discrimination more softly, add xml comments
Browse files Browse the repository at this point in the history
  • Loading branch information
JJ11teen committed Mar 10, 2023
1 parent 48ad5a8 commit db00544
Showing 1 changed file with 27 additions and 19 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,28 @@

namespace YamlDotNet.Serialization.BufferedDeserialization.TypeDiscriminators
{
/// <summary>
/// A TypeDiscriminator that discriminates which type to deserialize a yaml stream into by checking the value
/// of a known key.
/// </summary>
public class KeyValueTypeDiscriminator : ITypeDiscriminator
{
public Type BaseType { get; private set; }
private readonly string targetKey;
private readonly IDictionary<string, Type> typeMapping;

/// <summary>
/// Initializes a new instance of the <see cref="KeyValueTypeDiscriminator"/> class.
/// The KeyValueTypeDiscriminator will check the target key specified, and if it's value is contained within the
/// type mapping dictionary, the coresponding type will be discriminated.
/// </summary>
/// <param name="baseType">The base type which all discriminated types will implement. Use object if you're discriminating
/// unrelated types. Note the less specific you are with the base type the more yaml will need to be buffered.</param>
/// <param name="targetKey">The known key to check the value of when discriminating.</param>
/// <param name="typeMapping">A mapping dictionary of string to types.</param>
/// <exception cref="ArgumentOutOfRangeException">If any of the target types do not implement the base type.</exception>
public KeyValueTypeDiscriminator(Type baseType, string targetKey, IDictionary<string, Type> typeMapping)
{

foreach (var keyValuePair in typeMapping)
{
if (!baseType.IsAssignableFrom(keyValuePair.Value))
Expand All @@ -28,6 +41,17 @@ public KeyValueTypeDiscriminator(Type baseType, string targetKey, IDictionary<st
this.typeMapping = typeMapping;
}

/// <summary>
/// Checks if the current parser contains the target key, and that it's value matches one of the type mappings.
/// If so, return true, and the matching type.
/// Otherwise, return false.
/// This will consume the parser, so you will usually need the parser to be a buffer so an instance
/// of the discriminated type can be deserialized later.
/// </summary>
/// <param name="parser">The IParser to consume and discriminate a type from.</param>
/// <param name="suggestedType">The output type discriminated. Null if there target key was not present of if the value
/// of the target key was not within the type mapping.</param>
/// <returns>Returns true if the discriminator matched the yaml stream.</returns>
public bool TryDiscriminate(IParser parser, out Type? suggestedType)
{
if (parser.TryFindMappingEntry(
Expand All @@ -36,32 +60,16 @@ public bool TryDiscriminate(IParser parser, out Type? suggestedType)
out ParsingEvent? value))
{
// read the value of the discriminator key
if (value is Scalar valueScalar)
if (value is Scalar valueScalar && typeMapping.TryGetValue(valueScalar.Value, out var childType))
{
suggestedType = CheckName(valueScalar.Value);
suggestedType = childType;
return true;
}
else
{
throw new Exception($"Could not determine {BaseType} to deserialize to, {targetKey} has an empty value");
}
}

// we could not find our key, thus we could not determine correct child type
suggestedType = null;
return false;
}

private Type CheckName(string value)
{
if (typeMapping.TryGetValue(value, out var childType))
{
return childType;
}

var known = string.Join(",", typeMapping.Keys.ToArray());

throw new Exception($"Could not determine {BaseType} to deserialize to, expecting '{targetKey}' to be one of: {known}, but got '{value}'");
}
}
}

0 comments on commit db00544

Please sign in to comment.