Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 10 additions & 4 deletions src/Microsoft.OpenApi.YamlReader/YamlConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,10 @@ public static JsonObject ToJsonObject(this YamlMappingNode yaml)

private static YamlMappingNode ToYamlMapping(this JsonObject obj)
{
return new YamlMappingNode(obj.ToDictionary(x => (YamlNode)new YamlScalarNode(x.Key), x => x.Value!.ToYamlNode()));
return new YamlMappingNode(obj.ToDictionary(x => (YamlNode)new YamlScalarNode(x.Key)
{
Style = NeedsQuoting(x.Key) ? ScalarStyle.DoubleQuoted : ScalarStyle.Plain
}, x => x.Value!.ToYamlNode()));
}

/// <summary>
Expand Down Expand Up @@ -132,6 +135,11 @@ ScalarStyle.Plain when YamlNullRepresentations.Contains(yaml.Value) => (JsonValu
};
}

private static bool NeedsQuoting(string value) =>
decimal.TryParse(value, NumberStyles.Float, CultureInfo.InvariantCulture, out _) ||
bool.TryParse(value, out _) ||
YamlNullRepresentations.Contains(value);

private static YamlScalarNode ToYamlScalar(this JsonValue val)
{
// Try to get the underlying value based on its actual type
Expand All @@ -142,9 +150,7 @@ private static YamlScalarNode ToYamlScalar(this JsonValue val)
// For string values, we need to determine if they should be quoted in YAML
// Strings that look like numbers, booleans, or null need to be quoted
// to preserve their string type when round-tripping
var needsQuoting = decimal.TryParse(stringValue, NumberStyles.Float, CultureInfo.InvariantCulture, out _) ||
bool.TryParse(stringValue, out _) ||
YamlNullRepresentations.Contains(stringValue);
var needsQuoting = NeedsQuoting(stringValue);

return new YamlScalarNode(stringValue)
{
Expand Down
78 changes: 72 additions & 6 deletions test/Microsoft.OpenApi.Readers.Tests/YamlConverterTests.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Microsoft.OpenApi.YamlReader;
using Microsoft.OpenApi.Tests;
using Microsoft.OpenApi.YamlReader;
using SharpYaml;
using SharpYaml.Serialization;
using System.IO;
Expand Down Expand Up @@ -208,10 +209,7 @@ public void ToYamlNode_StringWithLineBreaks_PreservesLineBreaks()
var yamlOutput = ConvertYamlNodeToString(yamlNode);

// Convert back to JSON to verify round-tripping
var yamlStream = new YamlStream();
using var sr = new StringReader(yamlOutput);
yamlStream.Load(sr);
var jsonBack = yamlStream.Documents[0].ToJsonNode();
var jsonBack = ConvertYamlStringToJsonNode(yamlOutput);

// Assert - line breaks should be preserved during round-trip
var originalMultiline = json["multiline"]?.GetValue<string>();
Expand All @@ -225,12 +223,80 @@ public void ToYamlNode_StringWithLineBreaks_PreservesLineBreaks()
Assert.Contains("\n", roundTripDescription);
}

[Fact]
public void NumericPropertyNamesShouldRemainStringsFromJson()
{
// Given
var yamlInput =
"""
"123": value1
"456": value2
""";

// Given
var jsonNode = Assert.IsType<JsonObject>(JsonNode.Parse(@"{
""123"": ""value1"",
""456"": ""value2""
}"));

// When
var convertedBack = jsonNode.ToYamlNode();
var convertedBackOutput = ConvertYamlNodeToString(convertedBack);

// Then
Assert.Equal(yamlInput.MakeLineBreaksEnvironmentNeutral(), convertedBackOutput.MakeLineBreaksEnvironmentNeutral());
}

[Fact]
public void NumericPropertyNamesShouldRemainStringsFromYaml()
{
// Given
var yamlInput =
"""
"123": value1
"456": value2
""";

var jsonNode = ConvertYamlStringToJsonNode(yamlInput);

var convertedBack = jsonNode.ToYamlNode();
var convertedBackOutput = ConvertYamlNodeToString(convertedBack);
// Then
Assert.Equal(yamlInput.MakeLineBreaksEnvironmentNeutral(), convertedBackOutput.MakeLineBreaksEnvironmentNeutral());
}

[Fact]
public void BooleanPropertyNamesShouldRemainStringsFromYaml()
{
// Given
var yamlInput =
"""
"true": value1
"false": value2
""";

var jsonNode = ConvertYamlStringToJsonNode(yamlInput);

var convertedBack = jsonNode.ToYamlNode();
var convertedBackOutput = ConvertYamlNodeToString(convertedBack);
// Then
Assert.Equal(yamlInput.MakeLineBreaksEnvironmentNeutral(), convertedBackOutput.MakeLineBreaksEnvironmentNeutral());
}
private static JsonNode ConvertYamlStringToJsonNode(string yamlInput)
{
var yamlDocument = new YamlStream();
using var sr = new StringReader(yamlInput);
yamlDocument.Load(sr);
var yamlRoot = yamlDocument.Documents[0].RootNode;
return yamlRoot.ToJsonNode();
}

private static string ConvertYamlNodeToString(YamlNode yamlNode)
{
using var ms = new MemoryStream();
var yamlStream = new YamlStream(new YamlDocument(yamlNode));
var writer = new StreamWriter(ms);
yamlStream.Save(writer);
yamlStream.Save(writer, isLastDocumentEndImplicit: true);
writer.Flush();
ms.Seek(0, SeekOrigin.Begin);
var reader = new StreamReader(ms);
Expand Down
Loading