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)