Skip to content

Commit e30c718

Browse files
Copilotbaywet
andcommitted
Fix YamlConverter adding extra quotes to string values
Co-authored-by: baywet <[email protected]>
1 parent 9fefb47 commit e30c718

File tree

2 files changed

+203
-1
lines changed

2 files changed

+203
-1
lines changed

src/Microsoft.OpenApi.YamlReader/YamlConverter.cs

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,30 @@ ScalarStyle.Plain when YamlNullRepresentations.Contains(yaml.Value) => null,
133133

134134
private static YamlScalarNode ToYamlScalar(this JsonValue val)
135135
{
136-
return new YamlScalarNode(val.ToJsonString());
136+
// Try to get the underlying value based on its actual type
137+
// First try to get it as a string
138+
if (val.TryGetValue(out string? stringValue))
139+
{
140+
// For string values, we need to determine if they should be quoted in YAML
141+
// Strings that look like numbers, booleans, or null need to be quoted
142+
// to preserve their string type when round-tripping
143+
var needsQuoting = decimal.TryParse(stringValue, NumberStyles.Float, CultureInfo.InvariantCulture, out _) ||
144+
bool.TryParse(stringValue, out _) ||
145+
YamlNullRepresentations.Contains(stringValue);
146+
147+
return new YamlScalarNode(stringValue)
148+
{
149+
Style = needsQuoting ? ScalarStyle.DoubleQuoted : ScalarStyle.Plain
150+
};
151+
}
152+
153+
// For non-string values (numbers, booleans, null), use their string representation
154+
// These should remain unquoted in YAML
155+
var valueString = val.ToString();
156+
return new YamlScalarNode(valueString)
157+
{
158+
Style = ScalarStyle.Plain
159+
};
137160
}
138161
}
139162
}

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

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
using SharpYaml.Serialization;
33
using Xunit;
44
using Microsoft.OpenApi.YamlReader;
5+
using System.IO;
6+
using System.Text.Json.Nodes;
57

68
namespace Microsoft.OpenApi.Readers.Tests;
79

@@ -26,4 +28,181 @@ public void YamlNullValuesReturnNullJsonNode(string value)
2628
// Then
2729
Assert.Null(jsonNode);
2830
}
31+
32+
[Fact]
33+
public void ToYamlNode_StringValue_NotQuotedInYaml()
34+
{
35+
// Arrange
36+
var json = JsonNode.Parse(@"{""fooString"": ""fooStringValue""}");
37+
38+
// Act
39+
var yamlNode = json!.ToYamlNode();
40+
var yamlOutput = ConvertYamlNodeToString(yamlNode);
41+
42+
// Assert
43+
Assert.Contains("fooString: fooStringValue", yamlOutput);
44+
Assert.DoesNotContain("\"fooStringValue\"", yamlOutput);
45+
Assert.DoesNotContain("'fooStringValue'", yamlOutput);
46+
}
47+
48+
[Fact]
49+
public void ToYamlNode_StringThatLooksLikeNumber_QuotedInYaml()
50+
{
51+
// Arrange
52+
var json = JsonNode.Parse(@"{""fooStringOfNumber"": ""200""}");
53+
54+
// Act
55+
var yamlNode = json!.ToYamlNode();
56+
var yamlOutput = ConvertYamlNodeToString(yamlNode);
57+
58+
// Assert
59+
Assert.Contains("fooStringOfNumber: \"200\"", yamlOutput);
60+
}
61+
62+
[Fact]
63+
public void ToYamlNode_ActualNumber_NotQuotedInYaml()
64+
{
65+
// Arrange
66+
var json = JsonNode.Parse(@"{""actualNumber"": 200}");
67+
68+
// Act
69+
var yamlNode = json!.ToYamlNode();
70+
var yamlOutput = ConvertYamlNodeToString(yamlNode);
71+
72+
// Assert
73+
Assert.Contains("actualNumber: 200", yamlOutput);
74+
Assert.DoesNotContain("\"200\"", yamlOutput);
75+
}
76+
77+
[Fact]
78+
public void ToYamlNode_StringThatLooksLikeDecimal_QuotedInYaml()
79+
{
80+
// Arrange
81+
var json = JsonNode.Parse(@"{""decimalString"": ""123.45""}");
82+
83+
// Act
84+
var yamlNode = json!.ToYamlNode();
85+
var yamlOutput = ConvertYamlNodeToString(yamlNode);
86+
87+
// Assert
88+
Assert.Contains("decimalString: \"123.45\"", yamlOutput);
89+
}
90+
91+
[Fact]
92+
public void ToYamlNode_ActualDecimal_NotQuotedInYaml()
93+
{
94+
// Arrange
95+
var json = JsonNode.Parse(@"{""actualDecimal"": 123.45}");
96+
97+
// Act
98+
var yamlNode = json!.ToYamlNode();
99+
var yamlOutput = ConvertYamlNodeToString(yamlNode);
100+
101+
// Assert
102+
Assert.Contains("actualDecimal: 123.45", yamlOutput);
103+
Assert.DoesNotContain("\"123.45\"", yamlOutput);
104+
}
105+
106+
[Fact]
107+
public void ToYamlNode_StringThatLooksLikeBoolean_QuotedInYaml()
108+
{
109+
// Arrange
110+
var json = JsonNode.Parse(@"{""boolString"": ""true""}");
111+
112+
// Act
113+
var yamlNode = json!.ToYamlNode();
114+
var yamlOutput = ConvertYamlNodeToString(yamlNode);
115+
116+
// Assert
117+
Assert.Contains("boolString: \"true\"", yamlOutput);
118+
}
119+
120+
[Fact]
121+
public void ToYamlNode_ActualBoolean_NotQuotedInYaml()
122+
{
123+
// Arrange
124+
var json = JsonNode.Parse(@"{""actualBool"": true}");
125+
126+
// Act
127+
var yamlNode = json!.ToYamlNode();
128+
var yamlOutput = ConvertYamlNodeToString(yamlNode);
129+
130+
// Assert
131+
Assert.Contains("actualBool: true", yamlOutput);
132+
Assert.DoesNotContain("\"true\"", yamlOutput);
133+
}
134+
135+
[Fact]
136+
public void ToYamlNode_StringThatLooksLikeNull_QuotedInYaml()
137+
{
138+
// Arrange
139+
var json = JsonNode.Parse(@"{""nullString"": ""null""}");
140+
141+
// Act
142+
var yamlNode = json!.ToYamlNode();
143+
var yamlOutput = ConvertYamlNodeToString(yamlNode);
144+
145+
// Assert
146+
Assert.Contains("nullString: \"null\"", yamlOutput);
147+
}
148+
149+
[Fact]
150+
public void ToYamlNode_MixedTypes_CorrectQuoting()
151+
{
152+
// Arrange
153+
var json = JsonNode.Parse(@"{
154+
""str"": ""hello"",
155+
""numStr"": ""42"",
156+
""num"": 42,
157+
""boolStr"": ""false"",
158+
""bool"": false
159+
}");
160+
161+
// Act
162+
var yamlNode = json!.ToYamlNode();
163+
var yamlOutput = ConvertYamlNodeToString(yamlNode);
164+
165+
// Assert
166+
Assert.Contains("str: hello", yamlOutput);
167+
Assert.Contains("numStr: \"42\"", yamlOutput);
168+
Assert.Contains("num: 42", yamlOutput);
169+
Assert.DoesNotContain("num: \"42\"", yamlOutput);
170+
Assert.Contains("boolStr: \"false\"", yamlOutput);
171+
Assert.Contains("bool: false", yamlOutput);
172+
Assert.DoesNotContain("bool: \"false\"", yamlOutput);
173+
}
174+
175+
[Fact]
176+
public void ToYamlNode_FromIssueExample_CorrectOutput()
177+
{
178+
// Arrange - Example from issue #1951
179+
var json = JsonNode.Parse(@"{
180+
""fooString"": ""fooStringValue"",
181+
""fooStringOfNumber"": ""200""
182+
}");
183+
184+
// Act
185+
var yamlNode = json!.ToYamlNode();
186+
var yamlOutput = ConvertYamlNodeToString(yamlNode);
187+
188+
// Assert
189+
Assert.Contains("fooString: fooStringValue", yamlOutput);
190+
Assert.Contains("fooStringOfNumber: \"200\"", yamlOutput);
191+
192+
// Ensure no extra quotes on regular strings
193+
Assert.DoesNotContain("\"fooStringValue\"", yamlOutput);
194+
Assert.DoesNotContain("'fooStringValue'", yamlOutput);
195+
}
196+
197+
private static string ConvertYamlNodeToString(YamlNode yamlNode)
198+
{
199+
using var ms = new MemoryStream();
200+
var yamlStream = new YamlStream(new YamlDocument(yamlNode));
201+
var writer = new StreamWriter(ms);
202+
yamlStream.Save(writer);
203+
writer.Flush();
204+
ms.Seek(0, SeekOrigin.Begin);
205+
var reader = new StreamReader(ms);
206+
return reader.ReadToEnd();
207+
}
29208
}

0 commit comments

Comments
 (0)