From 063bcc032004eb6217a91dc8e8d7a7104cf42867 Mon Sep 17 00:00:00 2001 From: gaoflow Date: Tue, 28 Jul 2026 21:39:20 +0200 Subject: [PATCH] Quote string scalars that resolve to null (#493) --- .../Serialization/SerializationTests.cs | 45 +++++++++++++++++++ .../TypeAssigningEventEmitter.cs | 9 ++++ 2 files changed, 54 insertions(+) diff --git a/YamlDotNet.Test/Serialization/SerializationTests.cs b/YamlDotNet.Test/Serialization/SerializationTests.cs index e0ff5c293..5a2aa971a 100644 --- a/YamlDotNet.Test/Serialization/SerializationTests.cs +++ b/YamlDotNet.Test/Serialization/SerializationTests.cs @@ -2506,6 +2506,51 @@ public void StringsThatMatchKeywordsAreQuoted(string input) Assert.Equal($"text: \"{input}\"{Environment.NewLine}", yaml); } + [Theory] + [InlineData("~")] + [InlineData("null")] + [InlineData("Null")] + [InlineData("NULL")] + public void StringsThatResolveToNullAreQuotedByDefault(string input) + { + // Without quoting these round-trip back to null even for a string target (#493). + var yaml = Serializer.Serialize(input); + Assert.Equal($"\"{input}\"{Environment.NewLine}", yaml); + Assert.Equal(input, Deserializer.Deserialize(yaml)); + } + + [Theory] + [InlineData("nUll")] + [InlineData("nul")] + [InlineData("nullish")] + [InlineData("hello")] + public void StringsThatOnlyResembleNullAreNotQuoted(string input) + { + var yaml = Serializer.Serialize(input); + Assert.Equal($"{input}{Environment.NewLine}", yaml); + Assert.Equal(input, Deserializer.Deserialize(yaml)); + } + + [Fact] + public void ActualNullStillSerializesAsBareNull() + { + string value = null; + var yaml = Serializer.Serialize(value); + Assert.Null(Deserializer.Deserialize(yaml)); + } + + [Theory] + [InlineData("~")] + [InlineData("null")] + [InlineData("Null")] + [InlineData("NULL")] + public void NullTokenDictionaryValuesRoundtripAsStrings(string input) + { + var data = new Dictionary { { "k", input } }; + var result = DoRoundtripFromObjectTo>(data); + Assert.Equal(input, result["k"]); + } + public static IEnumerable Yaml1_1SpecialStringsData = new[] { "-.inf", "-.Inf", "-.INF", "-0", "-0100_200", "-0b101", "-0x30", "-190:20:30", "-23", "-3.14", diff --git a/YamlDotNet/Serialization/EventEmitters/TypeAssigningEventEmitter.cs b/YamlDotNet/Serialization/EventEmitters/TypeAssigningEventEmitter.cs index 2e61e1391..50d91325f 100644 --- a/YamlDotNet/Serialization/EventEmitters/TypeAssigningEventEmitter.cs +++ b/YamlDotNet/Serialization/EventEmitters/TypeAssigningEventEmitter.cs @@ -176,6 +176,11 @@ public override void Emit(ScalarEventInfo eventInfo, IEmitter emitter) { suggestedStyle = ScalarStyle.DoubleQuoted; } + else if (ResolvesToNull(eventInfo.RenderedValue)) + { + // A plain null token would deserialize back to null, losing the string (#493). + suggestedStyle = ScalarStyle.DoubleQuoted; + } else { suggestedStyle = defaultScalarStyle; @@ -242,5 +247,9 @@ private bool IsSpecialStringValue(string value) return isSpecialStringValue_Regex?.IsMatch(value) ?? false; } + + // Mirrors the plain tokens NullNodeDeserializer resolves to null (empty is already quoted). + private static bool ResolvesToNull(string value) + => value == "~" || value == "null" || value == "Null" || value == "NULL"; } }