diff --git a/src/Microsoft.OpenApi.YamlReader/YamlConverter.cs b/src/Microsoft.OpenApi.YamlReader/YamlConverter.cs index 260e4d971..d76cf8ec8 100644 --- a/src/Microsoft.OpenApi.YamlReader/YamlConverter.cs +++ b/src/Microsoft.OpenApi.YamlReader/YamlConverter.cs @@ -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())); } /// @@ -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 @@ -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) { diff --git a/test/Microsoft.OpenApi.Readers.Tests/YamlConverterTests.cs b/test/Microsoft.OpenApi.Readers.Tests/YamlConverterTests.cs index a40febbbf..9fa456bab 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/YamlConverterTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/YamlConverterTests.cs @@ -1,4 +1,5 @@ -using Microsoft.OpenApi.YamlReader; +using Microsoft.OpenApi.Tests; +using Microsoft.OpenApi.YamlReader; using SharpYaml; using SharpYaml.Serialization; using System.IO; @@ -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(); @@ -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(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);