YamlDotNet icon indicating copy to clipboard operation
YamlDotNet copied to clipboard

Stream style parsing returns strings for nulls

Open GunArm opened this issue 6 months ago • 2 comments

YamlDotNet 16.3.0

Describe the bug
I'm not very experienced with YAML, but (according to references like https://ref.coddy.tech/yaml/yaml-null-values) my understanding is that a key like foo: should produce a null value, not an empty string.

While the typed deserializer behaves accordingly, YamlStream (and company) interpret these null-like forms as strings, not null. This includes:

  • key:"" (should be null)
  • key: null"null" (should be null)
  • key: ~"~" (should be null)

To Reproduce
The following tests demonstrate the issue. The first test uses the typed deserializer and behaves correctly. The second uses YamlStream and fails:

[Fact]
public void TypedDeserializer_ParsesKeyWithNoValueAsNull()
{
    // Arrange
    var yaml = "Key:";
    var deserializer = new DeserializerBuilder().Build();

    // Act
    var result = deserializer.Deserialize<Dummy>(yaml);

    // Assert
    Assert.Null(result.Key);
}
private class Dummy { public string? Key { get; set; } }
[Fact]
public void YamStream_ParsesKeyWithNoValueAsNull()
{
    // Arrange
    var yaml = "Key:";
    var stream = new YamlStream();
    stream.Load(new StringReader(yaml));

    // Act
    var root = (YamlMappingNode)stream.Documents[0].RootNode;
    var node = root.Children[new YamlScalarNode("Key")];
    var scalar = Assert.IsType<YamlScalarNode>(node);

    // Assert
    // This will fail — scalar.Value is "" instead of null
    Assert.Null(scalar.Value);
}

I'm currently mitigating this in my conversion from YamlDotNet output to my own object by explicitly converting the strings returned with this switch case:

YamlScalarNode scalar when new[] { "", "~", "null" }.Contains(scalar.Value) && scalar.Style == ScalarStyle.Plain => null

This lets actual quoted empty strings through as strings. It handles my known cases, but I might be missing others. I assume the intention was not to return those as strings, particularly since the typed deserializer returns null.

GunArm avatar May 07 '25 04:05 GunArm

That looks almost exactly the same as what we do in the other places we check if it’s null. Those strings and if it’s a plain scalar. Might be worth adding a property that says isnull or something to the scalar class.

EdwardCooke avatar May 26 '25 22:05 EdwardCooke

I was naively expecting that scalar.Value itself would actually be null. But maybe true null just can't "fit" in there? I hadn't considered that.

I have my workaround (or, maybe it's just "doing the necessary") so lifecycle this issue as you see fit. I just wanted to report it since it surprised me.

GunArm avatar May 27 '25 23:05 GunArm