Skip to content

Serialization.TypeConverters

Chris Yarbrough edited this page Dec 22, 2023 · 2 revisions

Custom type conversion

Implement the IYamlTypeConverter interface to control the serialization process for a type that requires special conversion logic.

For example:

public System.Drawing.Color color { get; set; } = Color.Green;

Without a converter, this produces:

  color: {}

To serialize/deserialize the byte components of the color struct, let's start with a basic outline:

public class ColorConverter : IYamlTypeConverter
{
    public static readonly IYamlTypeConverter Instance = new ColorConverter();

    public bool Accepts(Type type)
    {
        // Can the converter handle this type?
    }

    public object? ReadYaml(IParser parser, Type type)
    {
        // Convert from text to an object during deserialization.
    }

    public void WriteYaml(IEmitter emitter, object? value, Type type)
    {
        // Convert from an object to text during serialization.
    }
}

The converter only works for this specific color type:

public bool Accepts(Type type)
{
    return type == typeof(System.Drawing.Color);
}

Group the color components in a mapping and write key-value-pairs for them:

public void WriteYaml(IEmitter emitter, object? value, Type type)
{
    emitter.Emit(new MappingStart());

    Color color = (Color)value!;

    emitter.Emit(new Scalar(nameof(color.R)));
    emitter.Emit(new Scalar(color.R.ToString()));

    emitter.Emit(new Scalar(nameof(color.G)));
    emitter.Emit(new Scalar(color.G.ToString()));

    emitter.Emit(new Scalar(nameof(color.B)));
    emitter.Emit(new Scalar(color.B.ToString()));

    emitter.Emit(new MappingEnd());
}

This produces:

  color:
    R: 0
    G: 128
    B: 0

Be aware, that this simple example ignores naming conventions or other settings that might be set on the serializer/deserializer.

Next, to convert the text back to a color struct, process each event respectively:

public object ReadYaml(IParser parser, Type type)
{
    parser.Consume<MappingStart>();

    parser.Consume<Scalar>();
    byte r = byte.Parse(parser.Consume<Scalar>().Value);
    
    parser.Consume<Scalar>();
    byte g = byte.Parse(parser.Consume<Scalar>().Value);

    parser.Consume<Scalar>();
    byte b = byte.Parse(parser.Consume<Scalar>().Value);

    parser.Consume<MappingEnd>();
    return Color.FromArgb(r, g, b);;
}

We can skip over the keys (property names) by calling parser.Consume<Scalar>(); for each component. Again, this example does not respect any custom settings and omits error handling.

Finally, configure the converter when creating a serializer and deserializer:

var serializer = new SerializerBuilder()
    .WithTypeConverter(ColorConverter.Instance)
    .Build();
string yaml = serializer.Serialize(components);
testOutputHelper.WriteLine(yaml);