Skip to content

Commit da43c98

Browse files
authored
Merge pull request #2565 from microsoft/fix/quote-numeric-property-names
fix: an issue where numeric property names would be missing quotes in yaml conversion
2 parents 537dc98 + 974ab34 commit da43c98

File tree

2 files changed

+82
-10
lines changed

2 files changed

+82
-10
lines changed

src/Microsoft.OpenApi.YamlReader/YamlConverter.cs

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,10 @@ public static JsonObject ToJsonObject(this YamlMappingNode yaml)
8787

8888
private static YamlMappingNode ToYamlMapping(this JsonObject obj)
8989
{
90-
return new YamlMappingNode(obj.ToDictionary(x => (YamlNode)new YamlScalarNode(x.Key), x => x.Value!.ToYamlNode()));
90+
return new YamlMappingNode(obj.ToDictionary(x => (YamlNode)new YamlScalarNode(x.Key)
91+
{
92+
Style = NeedsQuoting(x.Key) ? ScalarStyle.DoubleQuoted : ScalarStyle.Plain
93+
}, x => x.Value!.ToYamlNode()));
9194
}
9295

9396
/// <summary>
@@ -132,6 +135,11 @@ ScalarStyle.Plain when YamlNullRepresentations.Contains(yaml.Value) => (JsonValu
132135
};
133136
}
134137

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

149155
return new YamlScalarNode(stringValue)
150156
{

test/Microsoft.OpenApi.Readers.Tests/YamlConverterTests.cs

Lines changed: 72 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using Microsoft.OpenApi.YamlReader;
1+
using Microsoft.OpenApi.Tests;
2+
using Microsoft.OpenApi.YamlReader;
23
using SharpYaml;
34
using SharpYaml.Serialization;
45
using System.IO;
@@ -208,10 +209,7 @@ public void ToYamlNode_StringWithLineBreaks_PreservesLineBreaks()
208209
var yamlOutput = ConvertYamlNodeToString(yamlNode);
209210

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

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

226+
[Fact]
227+
public void NumericPropertyNamesShouldRemainStringsFromJson()
228+
{
229+
// Given
230+
var yamlInput =
231+
"""
232+
"123": value1
233+
"456": value2
234+
""";
235+
236+
// Given
237+
var jsonNode = Assert.IsType<JsonObject>(JsonNode.Parse(@"{
238+
""123"": ""value1"",
239+
""456"": ""value2""
240+
}"));
241+
242+
// When
243+
var convertedBack = jsonNode.ToYamlNode();
244+
var convertedBackOutput = ConvertYamlNodeToString(convertedBack);
245+
246+
// Then
247+
Assert.Equal(yamlInput.MakeLineBreaksEnvironmentNeutral(), convertedBackOutput.MakeLineBreaksEnvironmentNeutral());
248+
}
249+
250+
[Fact]
251+
public void NumericPropertyNamesShouldRemainStringsFromYaml()
252+
{
253+
// Given
254+
var yamlInput =
255+
"""
256+
"123": value1
257+
"456": value2
258+
""";
259+
260+
var jsonNode = ConvertYamlStringToJsonNode(yamlInput);
261+
262+
var convertedBack = jsonNode.ToYamlNode();
263+
var convertedBackOutput = ConvertYamlNodeToString(convertedBack);
264+
// Then
265+
Assert.Equal(yamlInput.MakeLineBreaksEnvironmentNeutral(), convertedBackOutput.MakeLineBreaksEnvironmentNeutral());
266+
}
267+
268+
[Fact]
269+
public void BooleanPropertyNamesShouldRemainStringsFromYaml()
270+
{
271+
// Given
272+
var yamlInput =
273+
"""
274+
"true": value1
275+
"false": value2
276+
""";
277+
278+
var jsonNode = ConvertYamlStringToJsonNode(yamlInput);
279+
280+
var convertedBack = jsonNode.ToYamlNode();
281+
var convertedBackOutput = ConvertYamlNodeToString(convertedBack);
282+
// Then
283+
Assert.Equal(yamlInput.MakeLineBreaksEnvironmentNeutral(), convertedBackOutput.MakeLineBreaksEnvironmentNeutral());
284+
}
285+
private static JsonNode ConvertYamlStringToJsonNode(string yamlInput)
286+
{
287+
var yamlDocument = new YamlStream();
288+
using var sr = new StringReader(yamlInput);
289+
yamlDocument.Load(sr);
290+
var yamlRoot = yamlDocument.Documents[0].RootNode;
291+
return yamlRoot.ToJsonNode();
292+
}
293+
228294
private static string ConvertYamlNodeToString(YamlNode yamlNode)
229295
{
230296
using var ms = new MemoryStream();
231297
var yamlStream = new YamlStream(new YamlDocument(yamlNode));
232298
var writer = new StreamWriter(ms);
233-
yamlStream.Save(writer);
299+
yamlStream.Save(writer, isLastDocumentEndImplicit: true);
234300
writer.Flush();
235301
ms.Seek(0, SeekOrigin.Begin);
236302
var reader = new StreamReader(ms);

0 commit comments

Comments
 (0)