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

Serializer: Need more control over how keys are emitted #644

Open
fireflycons opened this issue Oct 21, 2021 · 2 comments
Open

Serializer: Need more control over how keys are emitted #644

fireflycons opened this issue Oct 21, 2021 · 2 comments

Comments

@fireflycons
Copy link

fireflycons commented Oct 21, 2021

Is your feature request related to a problem? Please describe.
Whilst building a serializer for CloudFormation (yeah, that old cookie), I find that mapping keys are always emitted as unquoted strings. In a CF template some keys may be numeric, e.g. an AWS account ID as a key in the Mappings section of the template. CloudFormation insists that these keys are quoted.

Debugging through the code leads me to Emitter.EmitBlockMappingKey. I find that the scalar key to be emitted has the following properties

IsCanonical: false
IsPlainImplicit: true
IsQuotedImplict: false
Style: Any

...thus the mapping key is emitted without quotes.

If I put in the following hack, then the key is emitted with quotes

            if (CheckSimpleKey())
            {
                states.Push(EmitterState.BlockMappingSimpleValue);

                if (evt is Scalar scalar && scalar.Value.All(char.IsDigit))
                {
                    evt = new Scalar(
                        scalar.Anchor,
                        scalar.Tag,
                        scalar.Value,
                        ScalarStyle.SingleQuoted,
                        scalar.IsPlainImplicit,
                        scalar.IsQuotedImplicit);

                }
                EmitNode(evt, true, true);
            }

Describe the solution you'd like
Some mechanism for setting up the behaviour of the emitter to be added to SerializerBuilder such that the strategy for emitting keys can be controlled.

This could also provide at least some of the solution for #591 since it could apply when a key has the string value "null".

Describe alternatives you've considered
None. I see no way to achieve the required behaviour without direct code changes in YamlDotNet.

I may attempt a PR, but would like guidance on how you think a change like this might be implemented.

@fireflycons
Copy link
Author

fireflycons commented Oct 22, 2021

I found a dirty workaround which does not involve modifying any code.

  • Create an extension method on IEmitter using reflection to get at the state and the event queue.
  • Create a new implementation of TypeAssigningEventEmitter with pretty much the same code as the original, replacing the original at the serializer builder, with the following change to leverage the the above extension method
                    case TypeCode.String:
                    case TypeCode.Char:
                        eventInfo.Tag = FailsafeSchema.Tags.Str;
                        eventInfo.RenderedValue = value.ToString()!;

                        // Use dirty hack to determine if a key is being emitted.
                        if (emitter.IsEmittingKey() && eventInfo.RenderedValue.All(char.IsDigit))
                        {
                            // Emit numeric keys quoted.
                            suggestedStyle = ScalarStyle.SingleQuoted;
                        }
                        else
                        {
                            suggestedStyle = ScalarStyle.Any;
                        }

                        break;

Key detection logic does this. I think it's right as I've tried it in various scenarios

            var state = GetState(emitter);

            if (state == EmitterState.BlockMappingFirstKey || state == EmitterState.BlockMappingKey)
            {
                // State indicates it will emit a key next.
                return true;
            }

            // If the next event in the queue is a mapping start, then a key will be emitted next.
            return GetEventQueue(emitter).FirstOrDefault()?.GetType() == typeof(MappingStart);

So it would be nice if IEmitter had a method like IsEmittingKey() that can be used in event emitter implementations. I'm aware that this method if called out of context might return an incorrect result, so not sure how to get round that... but still probably nicer to tell the serializer builder you want numeric keys and/or scalar string "null" quoted.

fireflycons added a commit to fireflycons/Firefly.CloudFormationParser that referenced this issue Oct 23, 2021
@PintTheDragon
Copy link

I would also love to have a way to force all strings to be quoted. In my case, YAML is used for configuration files, but the fact that not every string is quoted means that users who don't know YAML very well frequently put characters in unquoted strings that are only allowed in quoted strings.

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

3 participants