From 7aafd26560ad952bab484c80e28f488017b5027f Mon Sep 17 00:00:00 2001 From: Eva Balint <eva.balint@ericsson.com> Date: Tue, 14 Nov 2023 23:55:23 +0100 Subject: [PATCH] Process inline comments with DictionaryDeserializer. --- YamlDotNet.Samples/DeserializeWithComment.cs | 84 +++++++++ .../Serialization/SerializationTests.cs | 36 ++++ YamlDotNet/Basic/ValueWithComment.cs | 35 ++++ YamlDotNet/Core/IParser.cs | 10 + YamlDotNet/Core/IScanner.cs | 5 + YamlDotNet/Core/MergingParser.cs | 9 + YamlDotNet/Core/Parser.cs | 10 + .../BufferedDeserialization/ParserBuffer.cs | 10 + .../Serialization/DeserializerBuilder.cs | 3 +- .../CommentNodeDeserializer.cs | 45 +++++ .../DictionaryDeserializer.cs | 174 +++++++++++++----- .../StaticDeserializerBuilder.cs | 1 + .../ReadableFieldsTypeInspector.cs | 1 + .../ReadablePropertiesTypeInspector.cs | 1 + .../WritablePropertiesTypeInspector.cs | 1 + .../NodeValueDeserializer.cs | 20 +- 16 files changed, 402 insertions(+), 43 deletions(-) create mode 100644 YamlDotNet.Samples/DeserializeWithComment.cs create mode 100644 YamlDotNet/Basic/ValueWithComment.cs create mode 100644 YamlDotNet/Serialization/NodeDeserializers/CommentNodeDeserializer.cs diff --git a/YamlDotNet.Samples/DeserializeWithComment.cs b/YamlDotNet.Samples/DeserializeWithComment.cs new file mode 100644 index 000000000..3f7911f3b --- /dev/null +++ b/YamlDotNet.Samples/DeserializeWithComment.cs @@ -0,0 +1,84 @@ +// This file is part of YamlDotNet - A .NET library for YAML. +// Copyright (c) Antoine Aubry and contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the "Software"), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +// of the Software, and to permit persons to whom the Software is furnished to do +// so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +using System; +using System.IO; +using Xunit.Abstractions; +using YamlDotNet.Samples.Helpers; +using YamlDotNet.Serialization; +using YamlDotNet.Serialization.NamingConventions; + + +namespace YamlDotNet.Samples +{ + public class DeserializeWithComment + { + private readonly ITestOutputHelper output; + + public DeserializeWithComment(ITestOutputHelper output) + { + this.output = output; + } + + [Sample( + DisplayName = "Deserializing with comments", + Description = "Shows how to process comments" + )] + public void Main() + { + var deserializer = new DeserializerBuilder() + .WithNamingConvention(CamelCaseNamingConvention.Instance) + .Build(); + var serializer = new SerializerBuilder() + .WithNamingConvention(CamelCaseNamingConvention.Instance) + .JsonCompatible() + .Build(); + + var content = WithInlineCommentsAndList; + + var parser = new Core.Parser(new Core.Scanner(new StringReader(content), false)); + var yamlObject = deserializer.Deserialize<object>(parser); + + var json = serializer.Serialize(yamlObject); + + output.WriteLine(json); + Console.WriteLine(json); + + } + + private const string WithInlineCommentsAndList = +@"# document starts with comment +valuelist: + - string1 #{1st comment} + - string2 +# block comment + - string3 #{2nd comment} +simplevalue: 12 +objectlist: + - att1: 12 + att2: v1 + - att1: 13 + att2: v2 + - att1: 14 #3rd comment + att2: v3 #4th comment +"; + } +} diff --git a/YamlDotNet.Test/Serialization/SerializationTests.cs b/YamlDotNet.Test/Serialization/SerializationTests.cs index 28a502f95..a88bb8acc 100644 --- a/YamlDotNet.Test/Serialization/SerializationTests.cs +++ b/YamlDotNet.Test/Serialization/SerializationTests.cs @@ -1647,6 +1647,42 @@ public void YamlConvertiblesAreAbleToEmitAndParseComments() Assert.Equal("The value", parsed.Value); } +[Fact] + public void ChildrenWithInlineComments() + { + var input = @"# document starts with comment +valuelist: + - string1 #{1st comment} + - string2 +# block comment + - string3 #{2nd comment} +simplevalue: 12 +objectlist: + - att1: 12 + att2: v1 + - att1: 13 + att2: v2 + - att1: 14 #3rd comment + att2: v3 #4th comment +"; + + var expected = @"{""valuelist"": [{""value"": ""string1"", ""comment"": ""{1st comment}""}, {""value"": ""string2"", ""comment"": """"}, {""value"": ""string3"", ""comment"": ""{2nd comment}""}], ""simplevalue"": {""value"": ""12"", ""comment"": """"}, ""objectlist"": [{""att1"": {""value"": ""12"", ""comment"": """"}, ""att2"": {""value"": ""v1"", ""comment"": """"}}, {""att1"": {""value"": ""13"", ""comment"": """"}, ""att2"": {""value"": ""v2"", ""comment"": """"}}, {""att1"": {""value"": ""14"", ""comment"": ""3rd comment""}, ""att2"": {""value"": ""v3"", ""comment"": ""4th comment""}}]} +"; + + var deserializer = new DeserializerBuilder() + .WithNamingConvention(CamelCaseNamingConvention.Instance) + .Build(); + var serializer = new SerializerBuilder() + .WithNamingConvention(CamelCaseNamingConvention.Instance) + .JsonCompatible() + .Build(); + var parser = new Parser(new Scanner(new StringReader(input), false)); + + var yamlObject = deserializer.Deserialize<object>(parser); + var actual = serializer.Serialize(yamlObject); + + Assert.Equal(expected, actual); + } public class CommentWrapper<T> : IYamlConvertible { public string Comment { get; set; } diff --git a/YamlDotNet/Basic/ValueWithComment.cs b/YamlDotNet/Basic/ValueWithComment.cs new file mode 100644 index 000000000..7bda9c573 --- /dev/null +++ b/YamlDotNet/Basic/ValueWithComment.cs @@ -0,0 +1,35 @@ +// This file is part of YamlDotNet - A .NET library for YAML. +// Copyright (c) Antoine Aubry and contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the "Software"), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +// of the Software, and to permit persons to whom the Software is furnished to do +// so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +namespace YamlDotNet.Basic +{ + public class ValueWithComment + { + public ValueWithComment(object? value, string comment) + { + this.Value = value; + this.Comment = comment; + } + public object? Value { get; } + + public string Comment { get; } + } +} diff --git a/YamlDotNet/Core/IParser.cs b/YamlDotNet/Core/IParser.cs index 84c53ebac..cbf1feda1 100644 --- a/YamlDotNet/Core/IParser.cs +++ b/YamlDotNet/Core/IParser.cs @@ -39,5 +39,15 @@ public interface IParser /// </summary> /// <returns>Returns true if there are more events available, otherwise returns false.</returns> bool MoveNext(); + + /// <summary> + /// Skips following comment events. + /// </summary> + void SkipFollowingComments(); + + /// <summary> + /// Gets the SkipComments value from its scanner. + /// </summary> + bool SkipComments { get; } } } diff --git a/YamlDotNet/Core/IScanner.cs b/YamlDotNet/Core/IScanner.cs index 8636f477c..72f0600cb 100644 --- a/YamlDotNet/Core/IScanner.cs +++ b/YamlDotNet/Core/IScanner.cs @@ -54,5 +54,10 @@ public interface IScanner /// Consumes the current token. /// </summary> void ConsumeCurrent(); + + /// <summary> + /// Gets the SkipComments setting. + /// </summary> + bool SkipComments { get; } } } diff --git a/YamlDotNet/Core/MergingParser.cs b/YamlDotNet/Core/MergingParser.cs index 4c8ee97f6..3fc507cd9 100644 --- a/YamlDotNet/Core/MergingParser.cs +++ b/YamlDotNet/Core/MergingParser.cs @@ -47,6 +47,8 @@ public MergingParser(IParser innerParser) public ParsingEvent? Current => iterator.Current?.Value; + public bool SkipComments => innerParser.SkipComments; + public bool MoveNext() { if (!merged) @@ -60,6 +62,13 @@ public bool MoveNext() return iterator.MoveNext(); } + public void SkipFollowingComments() + { + while (this.TryConsume<Comment>(out var _)) + { + } + } + private void Merge() { while (innerParser.MoveNext()) diff --git a/YamlDotNet/Core/Parser.cs b/YamlDotNet/Core/Parser.cs index df877ce71..ef16024f8 100644 --- a/YamlDotNet/Core/Parser.cs +++ b/YamlDotNet/Core/Parser.cs @@ -55,6 +55,7 @@ public class Parser : IParser { pendingEvents.Enqueue(new Events.Comment(commentToken.Value, commentToken.IsInline, commentToken.Start, commentToken.End)); scanner.ConsumeCurrent(); + currentToken = scanner.Current; } else { @@ -87,6 +88,8 @@ public Parser(IScanner scanner) /// </summary> public ParsingEvent? Current { get; private set; } + public bool SkipComments => scanner.SkipComments; + private readonly EventQueue pendingEvents = new EventQueue(); /// <summary> @@ -111,6 +114,13 @@ public bool MoveNext() return true; } + public void SkipFollowingComments() + { + while (this.TryConsume<Events.Comment>(out var _)) + { + } + } + private ParsingEvent StateMachine() { switch (state) diff --git a/YamlDotNet/Serialization/BufferedDeserialization/ParserBuffer.cs b/YamlDotNet/Serialization/BufferedDeserialization/ParserBuffer.cs index 2128f7f51..10403527b 100644 --- a/YamlDotNet/Serialization/BufferedDeserialization/ParserBuffer.cs +++ b/YamlDotNet/Serialization/BufferedDeserialization/ParserBuffer.cs @@ -44,6 +44,7 @@ public class ParserBuffer : IParser /// <exception cref="ArgumentOutOfRangeException">If parser does not fit within the max depth and length specified.</exception> public ParserBuffer(IParser parserToBuffer, int maxDepth, int maxLength) { + SkipComments = parserToBuffer.SkipComments; buffer = new LinkedList<ParsingEvent>(); buffer.AddLast(parserToBuffer.Consume<MappingStart>()); var depth = 0; @@ -71,6 +72,8 @@ public ParserBuffer(IParser parserToBuffer, int maxDepth, int maxLength) /// </summary> public ParsingEvent? Current => current?.Value; + public bool SkipComments { get; } + /// <summary> /// Moves to the next event. /// </summary> @@ -88,5 +91,12 @@ public void Reset() { current = buffer.First; } + + public void SkipFollowingComments() + { + while (this.TryConsume<Comment>(out var _)) + { + } + } } } \ No newline at end of file diff --git a/YamlDotNet/Serialization/DeserializerBuilder.cs b/YamlDotNet/Serialization/DeserializerBuilder.cs index d1faf54d7..6de57754f 100755 --- a/YamlDotNet/Serialization/DeserializerBuilder.cs +++ b/YamlDotNet/Serialization/DeserializerBuilder.cs @@ -97,7 +97,8 @@ public DeserializerBuilder() { typeof(DictionaryNodeDeserializer), _ => new DictionaryNodeDeserializer(objectFactory.Value, duplicateKeyChecking) }, { typeof(CollectionNodeDeserializer), _ => new CollectionNodeDeserializer(objectFactory.Value) }, { typeof(EnumerableNodeDeserializer), _ => new EnumerableNodeDeserializer() }, - { typeof(ObjectNodeDeserializer), _ => new ObjectNodeDeserializer(objectFactory.Value, BuildTypeInspector(), ignoreUnmatched, duplicateKeyChecking, typeConverter) } + { typeof(CommentNodeDeserializer), _ => new CommentNodeDeserializer() }, + { typeof(ObjectNodeDeserializer), _ => new ObjectNodeDeserializer(objectFactory.Value, BuildTypeInspector(), ignoreUnmatched, duplicateKeyChecking, typeConverter) }, }; nodeTypeResolverFactories = new LazyComponentRegistrationList<Nothing, INodeTypeResolver> diff --git a/YamlDotNet/Serialization/NodeDeserializers/CommentNodeDeserializer.cs b/YamlDotNet/Serialization/NodeDeserializers/CommentNodeDeserializer.cs new file mode 100644 index 000000000..fc8979ccf --- /dev/null +++ b/YamlDotNet/Serialization/NodeDeserializers/CommentNodeDeserializer.cs @@ -0,0 +1,45 @@ +// This file is part of YamlDotNet - A .NET library for YAML. +// Copyright (c) Antoine Aubry and contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the "Software"), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +// of the Software, and to permit persons to whom the Software is furnished to do +// so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +using System; +using YamlDotNet.Core; +using YamlDotNet.Core.Events; + +namespace YamlDotNet.Serialization.NodeDeserializers +{ + internal class CommentNodeDeserializer : INodeDeserializer + { + public bool Deserialize(IParser parser, Type expectedType, Func<IParser, Type, object?> nestedObjectDeserializer, out object? value) + { + value = null; + if (parser.Accept<Comment>(out var _)) + { + if (parser.TryConsume<Comment>(out var comment)) + { + value = comment; + return true; + } + } + + return false; + } + } +} diff --git a/YamlDotNet/Serialization/NodeDeserializers/DictionaryDeserializer.cs b/YamlDotNet/Serialization/NodeDeserializers/DictionaryDeserializer.cs index 4bd459503..b472f26ac 100644 --- a/YamlDotNet/Serialization/NodeDeserializers/DictionaryDeserializer.cs +++ b/YamlDotNet/Serialization/NodeDeserializers/DictionaryDeserializer.cs @@ -21,6 +21,9 @@ using System; using System.Collections; +using System.Collections.Generic; +using System.Linq; +using YamlDotNet.Basic; using YamlDotNet.Core; using YamlDotNet.Core.Events; @@ -46,71 +49,160 @@ private void TryAssign(IDictionary result, object key, object value, MappingStar protected virtual void Deserialize(Type tKey, Type tValue, IParser parser, Func<IParser, Type, object?> nestedObjectDeserializer, IDictionary result) { + if (!parser.SkipComments) + { + DeserializeWithComments(tKey, tValue, parser, nestedObjectDeserializer, result); + return; + } + var property = parser.Consume<MappingStart>(); while (!parser.TryConsume<MappingEnd>(out var _)) { var key = nestedObjectDeserializer(parser, tKey); var value = nestedObjectDeserializer(parser, tValue); var valuePromise = value as IValuePromise; + AddKeyValue(result, property, key, value, valuePromise); + } + } - if (key is IValuePromise keyPromise) + private void AddKeyValue(IDictionary result, MappingStart property, object? key, object? value, IValuePromise? valuePromise) + { + if (key is IValuePromise keyPromise) + { + if (valuePromise == null) { - if (valuePromise == null) - { - // Key is pending, value is known - keyPromise.ValueAvailable += v => result[v!] = value!; - } - else - { - // Both key and value are pending. We need to wait until both of them become available. - var hasFirstPart = false; + // Key is pending, value is known + keyPromise.ValueAvailable += v => result[v!] = value!; + } + else + { + // Both key and value are pending. We need to wait until both of them become available. + var hasFirstPart = false; - keyPromise.ValueAvailable += v => + keyPromise.ValueAvailable += v => + { + if (hasFirstPart) { - if (hasFirstPart) - { - TryAssign(result, v!, value!, property); - } - else - { - key = v!; - hasFirstPart = true; - } - }; + TryAssign(result, v!, value!, property); + } + else + { + key = v!; + hasFirstPart = true; + } + }; - valuePromise.ValueAvailable += v => + valuePromise.ValueAvailable += v => + { + if (hasFirstPart) { - if (hasFirstPart) - { - TryAssign(result, key, v!, property); - } - else - { - value = v; - hasFirstPart = true; - } - }; - } + TryAssign(result, key, v!, property); + } + else + { + value = v; + hasFirstPart = true; + } + }; + } + } + else + { + if (key == null) + { + throw new ArgumentException("Empty key names are not supported yet.", "key"); + } + + if (valuePromise == null) + { + // Happy path: both key and value are known + TryAssign(result, key, value!, property); } else { - if (key == null) + // Key is known, value is pending + valuePromise.ValueAvailable += v => result[key!] = v!; + } + } + } + + private void DeserializeWithComments(Type tKey, Type tValue, IParser parser, Func<IParser, Type, object?> nestedObjectDeserializer, IDictionary result) + { + parser.TryConsume<Comment>(out var fileStartComment); + var property = parser.Consume<MappingStart>(); + while (!parser.TryConsume<MappingEnd>(out var _)) + { + parser.SkipFollowingComments(); + var key = nestedObjectDeserializer(parser, tKey); + parser.SkipFollowingComments(); + var originalValue = nestedObjectDeserializer(parser, tValue); + var valueWithComment = ParseStringWithComment(parser, originalValue); + var listValueWithComment = ParseListWithComment(valueWithComment); + var valuePromise = listValueWithComment as IValuePromise; + AddKeyValue(result, property, key, listValueWithComment, valuePromise); + } + } + + private static object? ParseListWithComment(object? value) + { + if (value is List<object> list) + { + var newValue = new List<ValueWithComment>(); + var stringValue = string.Empty; + var comment = string.Empty; + + foreach (var listItem in list) + { + if (listItem is string) { - throw new ArgumentException("Empty key names are not supported yet.", "key"); + if (!string.IsNullOrEmpty(stringValue)) + { + newValue.Add(new ValueWithComment(stringValue, comment)); + } + stringValue = (string)listItem; + comment = string.Empty; } - - if (valuePromise == null) + if (listItem is Comment && ((Comment)listItem).IsInline) + { + comment = ((Comment)listItem).Value; + } + } + if (!string.IsNullOrEmpty(stringValue)) + { + newValue.Add(new ValueWithComment(stringValue, comment)); + } + if (newValue.Any()) + { + if (newValue.Any(v => !string.IsNullOrEmpty(v.Comment))) { - // Happy path: both key and value are known - TryAssign(result, key, value!, property); + value = newValue; } else { - // Key is known, value is pending - valuePromise.ValueAvailable += v => result[key!] = v!; + value = newValue.Select(v => v.Value).ToList(); + } + } + } + + return value; + } + + private static object? ParseStringWithComment(IParser parser, object? value) + { + if (value is string) + { + var comment = string.Empty; + if (parser.TryConsume<Comment>(out var valueComment)) + { + if (value is string && valueComment.IsInline) + { + comment = valueComment.Value; } + parser.SkipFollowingComments(); } + value = new ValueWithComment(value, comment); } + return value; } } } diff --git a/YamlDotNet/Serialization/StaticDeserializerBuilder.cs b/YamlDotNet/Serialization/StaticDeserializerBuilder.cs index 502291343..6bacc337b 100644 --- a/YamlDotNet/Serialization/StaticDeserializerBuilder.cs +++ b/YamlDotNet/Serialization/StaticDeserializerBuilder.cs @@ -91,6 +91,7 @@ public StaticDeserializerBuilder(StaticContext context) { typeof(StaticArrayNodeDeserializer), _ => new StaticArrayNodeDeserializer(factory) }, { typeof(StaticDictionaryNodeDeserializer), _ => new StaticDictionaryNodeDeserializer(factory, duplicateKeyChecking) }, { typeof(StaticCollectionNodeDeserializer), _ => new StaticCollectionNodeDeserializer(factory) }, + { typeof(CommentNodeDeserializer), _ => new CommentNodeDeserializer() }, { typeof(ObjectNodeDeserializer), _ => new ObjectNodeDeserializer(factory, BuildTypeInspector(), ignoreUnmatched, duplicateKeyChecking, typeConverter) }, }; diff --git a/YamlDotNet/Serialization/TypeInspectors/ReadableFieldsTypeInspector.cs b/YamlDotNet/Serialization/TypeInspectors/ReadableFieldsTypeInspector.cs index e7f77c2e1..2c6bdbecd 100644 --- a/YamlDotNet/Serialization/TypeInspectors/ReadableFieldsTypeInspector.cs +++ b/YamlDotNet/Serialization/TypeInspectors/ReadableFieldsTypeInspector.cs @@ -64,6 +64,7 @@ public ReflectionFieldDescriptor(FieldInfo fieldInfo, ITypeResolver typeResolver public int Order { get; set; } public bool CanWrite { get { return !fieldInfo.IsInitOnly; } } public ScalarStyle ScalarStyle { get; set; } + public string? Comment { get; set; } public void Write(object target, object? value) { diff --git a/YamlDotNet/Serialization/TypeInspectors/ReadablePropertiesTypeInspector.cs b/YamlDotNet/Serialization/TypeInspectors/ReadablePropertiesTypeInspector.cs index 90c3f2482..cb08ddb83 100644 --- a/YamlDotNet/Serialization/TypeInspectors/ReadablePropertiesTypeInspector.cs +++ b/YamlDotNet/Serialization/TypeInspectors/ReadablePropertiesTypeInspector.cs @@ -78,6 +78,7 @@ public ReflectionPropertyDescriptor(PropertyInfo propertyInfo, ITypeResolver typ public int Order { get; set; } public bool CanWrite => propertyInfo.CanWrite; public ScalarStyle ScalarStyle { get; set; } + public string? Comment { get; set; } public void Write(object target, object? value) { diff --git a/YamlDotNet/Serialization/TypeInspectors/WritablePropertiesTypeInspector.cs b/YamlDotNet/Serialization/TypeInspectors/WritablePropertiesTypeInspector.cs index 88ebee051..603bc6fe2 100644 --- a/YamlDotNet/Serialization/TypeInspectors/WritablePropertiesTypeInspector.cs +++ b/YamlDotNet/Serialization/TypeInspectors/WritablePropertiesTypeInspector.cs @@ -79,6 +79,7 @@ public ReflectionPropertyDescriptor(PropertyInfo propertyInfo, ITypeResolver typ public int Order { get; set; } public bool CanWrite => propertyInfo.CanWrite; public ScalarStyle ScalarStyle { get; set; } + public string? Comment { get; set; } public void Write(object target, object? value) { diff --git a/YamlDotNet/Serialization/ValueDeserializers/NodeValueDeserializer.cs b/YamlDotNet/Serialization/ValueDeserializers/NodeValueDeserializer.cs index 2248d27b3..31ca2912e 100644 --- a/YamlDotNet/Serialization/ValueDeserializers/NodeValueDeserializer.cs +++ b/YamlDotNet/Serialization/ValueDeserializers/NodeValueDeserializer.cs @@ -42,7 +42,7 @@ public NodeValueDeserializer(IList<INodeDeserializer> deserializers, IList<INode public object? DeserializeValue(IParser parser, Type expectedType, SerializerState state, IValueDeserializer nestedObjectDeserializer) { - parser.Accept<NodeEvent>(out var nodeEvent); + var nodeEvent = GetNodeEvent(parser, expectedType); var nodeType = GetTypeFromEvent(nodeEvent, expectedType); try @@ -76,6 +76,24 @@ public NodeValueDeserializer(IList<INodeDeserializer> deserializers, IList<INode ); } + private static NodeEvent? GetNodeEvent(IParser parser, Type expectedType) + { + parser.Accept<NodeEvent>(out var nodeEvent); + if (nodeEvent == null + && !parser.SkipComments + && !typeof(IYamlConvertible).IsAssignableFrom(expectedType)) + { + if (parser.Current is YamlDotNet.Core.Events.Comment cmt && cmt.IsInline) + { + return nodeEvent; + } + parser.SkipFollowingComments(); + parser.Accept<NodeEvent>(out nodeEvent); + } + + return nodeEvent; + } + private Type GetTypeFromEvent(NodeEvent? nodeEvent, Type currentType) { foreach (var typeResolver in typeResolvers)