YamlDotNet
YamlDotNet copied to clipboard
Combination of EventEmitter and TypeConverter
Is there a way to combine a EventEmitter with a TypeConverter ?
The use case is that all string values should have quotes, and that a DateTime should be displayed without time.
To quote string values I'm using the approach from: https://github.com/aaubry/YamlDotNet/issues/428#issuecomment-525822859
The problem is, that the Custom ChainedEventEmitter isn't invoked when the DateTimeConverter detects a DateTime value, which leads to weird behavior:
Output:
StringValue: "2.0"
Date: 2020-04-02
"IntValue": 3
"Nested":
StringValue1: "abc"
StringArray:
- "1.0"
- "two"
StringValue2: "abc"
"OtherStringValue": test
Desired Output:
StringValue: "2.0"
Date: "2020-04-02"
IntValue: 3
Nested:
StringValue1: "abc"
StringArray:
- "1.0"
- "two"
StringValue2: "abc"
OtherStringValue: "test"
Example Code
using System;
using System.Collections.Generic;
using YamlDotNet.Core;
using YamlDotNet.Serialization;
using YamlDotNet.Serialization.Converters;
using YamlDotNet.Serialization.EventEmitters;
public class Program
{
public static void Main()
{
var serializer = new SerializerBuilder()
.WithEventEmitter(next => new ForceQuotedStringValuesEventEmitter(next))
.WithTypeConverter(new DateTimeConverter(DateTimeKind.Unspecified, null, new[] { "yyyy-MM-dd" }))
.Build();
serializer.Serialize(Console.Out, new
{
StringValue = "2.0",
Date = new DateTime(2020, 4, 3),
IntValue = 3,
Nested = new
{
StringValue1 = "abc",
StringArray = new[] { "1.0", "two" },
StringValue2 = "abc"
},
OtherStringValue = "test"
});
Console.ReadKey();
}
public class ForceQuotedStringValuesEventEmitter : ChainedEventEmitter
{
private class EmitterState
{
private int valuePeriod;
private int currentIndex;
public EmitterState(int valuePeriod)
{
this.valuePeriod = valuePeriod;
}
public bool VisitNext()
{
++currentIndex;
return (currentIndex % valuePeriod) == 0;
}
}
private readonly Stack<EmitterState> state = new Stack<EmitterState>();
public ForceQuotedStringValuesEventEmitter(IEventEmitter nextEmitter) : base(nextEmitter)
{
this.state.Push(new EmitterState(1));
}
public override void Emit(ScalarEventInfo eventInfo, IEmitter emitter)
{
if (this.state.Peek().VisitNext())
{
if (eventInfo.Source.Type == typeof(string))
{
eventInfo.Style = ScalarStyle.DoubleQuoted;
}
}
base.Emit(eventInfo, emitter);
}
public override void Emit(MappingStartEventInfo eventInfo, IEmitter emitter)
{
this.state.Peek().VisitNext();
this.state.Push(new EmitterState(2));
base.Emit(eventInfo, emitter);
}
public override void Emit(MappingEndEventInfo eventInfo, IEmitter emitter)
{
this.state.Pop();
base.Emit(eventInfo, emitter);
}
public override void Emit(SequenceStartEventInfo eventInfo, IEmitter emitter)
{
this.state.Peek().VisitNext();
this.state.Push(new EmitterState(1));
base.Emit(eventInfo, emitter);
}
public override void Emit(SequenceEndEventInfo eventInfo, IEmitter emitter)
{
this.state.Pop();
base.Emit(eventInfo, emitter);
}
}
}
That's not possible because IYamlTypeConverter
does not use IEventEmitter
due to legacy reasons. One option is to register your own implementation of DateTimeConverter
that forces quotes.
The problem remains, even if I register my own implementation of DateTimeConverter
, the quotes will be set at the wrong place (because the EventEmitter isn't invoked).
StringValue: "2.0"
Date: "2020-04-02"
"IntValue": 3
"NextStringValue": anyString
"NextDate": "2020-04-03"
NextStringValue2: "anyString"
Is there a generic solution for this?
I don't know how you got that result. Can you share the code ?
Woops sorry, here is the example https://dotnetfiddle.net/3t83cZ
Oh I see. Basically ForceQuotedStringValuesEventEmitter
is a hack. It has to maintain state to know whether it is emitting a key or a value, and writing to the IEmitter
directly messes with that. As you observed, it could work if DateTimeConverter
could use IEventEmitter, but there's currently no good way to pass it around.
If you're willing to make your code messy, you could capture the IEventEmitter
and pass it to the DateTimeConverter
, but I really don't recommend that:
IEventEmitter eventEmitter = null;
var serializer = new SerializerBuilder()
.WithEventEmitter(next => eventEmitter = new ForceQuotedStringValuesEventEmitter(next))
.WithTypeConverter(new CustomDateTimeConverter(() => eventEmitter))
.Build();