Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Combination of EventEmitter and TypeConverter #473

Open
hoyau opened this issue Mar 4, 2020 · 5 comments
Open

Combination of EventEmitter and TypeConverter #473

hoyau opened this issue Mar 4, 2020 · 5 comments

Comments

@hoyau
Copy link

hoyau commented Mar 4, 2020

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: #428 (comment)

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);
            }
        }

    }
@aaubry
Copy link
Owner

aaubry commented Mar 5, 2020

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.

@hoyau
Copy link
Author

hoyau commented Mar 6, 2020

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?

@aaubry
Copy link
Owner

aaubry commented Mar 6, 2020

I don't know how you got that result. Can you share the code ?

@hoyau
Copy link
Author

hoyau commented Mar 6, 2020

Woops sorry, here is the example https://dotnetfiddle.net/3t83cZ

@aaubry
Copy link
Owner

aaubry commented Mar 6, 2020

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();

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants