Skip to content

Commit d152b19

Browse files
Rob-Haguegbirchmeier
authored andcommitted
Use Utf8JsonWriter in ToJSON
This deals with escaping/tokens automatically. PR #981 to fix issue #980
1 parent 06c7aa6 commit d152b19

File tree

3 files changed

+55
-31
lines changed

3 files changed

+55
-31
lines changed

QuickFIXn/Message/Message.cs

Lines changed: 40 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using QuickFix.DataDictionary;
77
using DD = QuickFix.DataDictionary.DataDictionary;
88
using QuickFix.ObjectPooling;
9+
using System.IO;
910

1011
namespace QuickFix;
1112

@@ -888,8 +889,8 @@ private static string FieldMapToXml(DD? dd, FieldMap fields)
888889
/// <summary>
889890
/// ToJSON() helper method.
890891
/// </summary>
891-
/// <returns>an XML string</returns>
892-
private static StringBuilder FieldMapToJson(StringBuilder sb, DD? dd, FieldMap fields, bool humanReadableValues, List<int>? tagsToMask, string maskText)
892+
/// <returns>a JSON string</returns>
893+
private static void FieldMapToJson(Utf8JsonWriter writer, DD? dd, FieldMap fields, bool humanReadableValues, List<int>? tagsToMask, string maskText)
893894
{
894895
IList<int> numInGroupTagList = fields.GetGroupTags();
895896
IList<IField> numInGroupFieldList = new List<IField>();
@@ -908,27 +909,28 @@ private static StringBuilder FieldMapToJson(StringBuilder sb, DD? dd, FieldMap f
908909

909910
if (dd is not null && dd.FieldsByTag.TryGetValue(field.Tag, out DDField? ddField))
910911
{
911-
sb.Append("\"" + ddField.Name + "\":");
912+
writer.WritePropertyName(ddField.Name);
913+
912914
if (tagsToMask != null && tagsToMask.Contains(field.Tag))
913915
{
914-
sb.Append("\"" + maskText + "\",");
916+
writer.WriteStringValue(maskText);
915917
}
916918
else if (humanReadableValues)
917919
{
918920
if (dd.FieldsByTag[field.Tag].EnumDict.TryGetValue(field.ToString(), out var valueDescription))
919-
sb.Append("\"" + valueDescription + "\",");
921+
writer.WriteStringValue(valueDescription);
920922
else
921-
sb.Append("\"" + field + "\",");
923+
writer.WriteStringValue(field.ToString());
922924
}
923925
else
924926
{
925-
sb.Append("\"" + field + "\",");
927+
writer.WriteStringValue(field.ToString());
926928
}
927929
}
928930
else
929931
{
930-
sb.Append("\"" + field.Tag + "\":");
931-
sb.Append("\"" + field + "\",");
932+
writer.WritePropertyName(field.Tag.ToString());
933+
writer.WriteStringValue(field.ToString());
932934
}
933935
}
934936

@@ -937,29 +939,20 @@ private static StringBuilder FieldMapToJson(StringBuilder sb, DD? dd, FieldMap f
937939
{
938940
// The name of the NumInGroup field is the key of the JSON list containing the Group items
939941
if (dd is not null && dd.FieldsByTag.TryGetValue(numInGroupField.Tag, out DDField? field))
940-
sb.Append("\"" + field.Name + "\":[");
942+
writer.WriteStartArray(field.Name);
941943
else
942-
sb.Append("\"" + numInGroupField.Tag + "\":[");
944+
writer.WriteStartArray(numInGroupField.Tag.ToString());
943945

944946
// Populate the JSON list with the Group items
945947
for (int counter = 1; counter <= fields.GroupCount(numInGroupField.Tag); counter++)
946948
{
947-
sb.Append('{');
948-
FieldMapToJson(sb, dd, fields.GetGroup(counter, numInGroupField.Tag), humanReadableValues, tagsToMask, maskText);
949-
sb.Append("},");
949+
writer.WriteStartObject();
950+
FieldMapToJson(writer, dd, fields.GetGroup(counter, numInGroupField.Tag), humanReadableValues, tagsToMask, maskText);
951+
writer.WriteEndObject();
950952
}
951953

952-
// Remove trailing comma
953-
if (sb.Length > 0 && sb[^1] == ',')
954-
sb.Remove(sb.Length - 1, 1);
955-
956-
sb.Append("],");
954+
writer.WriteEndArray();
957955
}
958-
// Remove trailing comma
959-
if (sb.Length > 0 && sb[^1] == ',')
960-
sb.Remove(sb.Length - 1, 1);
961-
962-
return sb;
963956
}
964957

965958
/// <summary>
@@ -1007,11 +1000,28 @@ public string ToJSON(DD? dataDictionary = null, bool convertEnumsToDescriptions
10071000
$"Must be non-null if '{nameof(convertEnumsToDescriptions)}' is true.");
10081001
}
10091002

1010-
using PooledStringBuilder pooledSb = new PooledStringBuilder();
1011-
StringBuilder sb = pooledSb.Builder.Append('{').Append("\"Header\":{");
1012-
FieldMapToJson(sb, dataDictionary, Header, convertEnumsToDescriptions, tagsToMask, maskText).Append("},\"Body\":{");
1013-
FieldMapToJson(sb, dataDictionary, this, convertEnumsToDescriptions, tagsToMask, maskText).Append("},\"Trailer\":{");
1014-
FieldMapToJson(sb, dataDictionary, Trailer, convertEnumsToDescriptions, tagsToMask, maskText).Append("}}");
1015-
return sb.ToString();
1003+
using MemoryStream stream = new();
1004+
using Utf8JsonWriter writer = new(stream);
1005+
1006+
writer.WriteStartObject();
1007+
writer.WriteStartObject("Header");
1008+
1009+
FieldMapToJson(writer, dataDictionary, Header, convertEnumsToDescriptions, tagsToMask, maskText);
1010+
1011+
writer.WriteEndObject();
1012+
writer.WriteStartObject("Body");
1013+
1014+
FieldMapToJson(writer, dataDictionary, this, convertEnumsToDescriptions, tagsToMask, maskText);
1015+
1016+
writer.WriteEndObject();
1017+
writer.WriteStartObject("Trailer");
1018+
1019+
FieldMapToJson(writer, dataDictionary, Trailer, convertEnumsToDescriptions, tagsToMask, maskText);
1020+
1021+
writer.WriteEndObject();
1022+
writer.WriteEndObject();
1023+
writer.Flush();
1024+
1025+
return Encoding.UTF8.GetString(stream.GetBuffer(), 0, (int)stream.Length);
10161026
}
10171027
}

RELEASE_NOTES.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ What's New
2929
* **There are breaking changes between 1.11 and 1.12! Please review the 1.12.0 notes below.**
3030
* **There are breaking changes between 1.10 and 1.11! Please review the 1.11.0 notes below.**
3131

32-
### next release
32+
### next release (v1.14.0)
3333

3434
**Breaking changes**
3535
* #627 - rename message packages to get rid of superfluous period (gbirchmeier)
@@ -56,6 +56,7 @@ What's New
5656
* #964 - Reduce unnecessary ContainsKey function calls to avoid multiple duplicate key lookups (VAllens)
5757
* #969 - correct LinesOfText in DDs to not be required; make ATs not auto-echo News (gbirchmeier)
5858
* #965 - Reusing StringBuilder with Object Pooling (VAllens)
59+
* #980 - fix: ToJSON returns invalid json when content contains newlines/tabs/etc (Rob-Hague)
5960

6061
### v1.13.1
6162
* backport #951 to 1.13

UnitTests/MessageToXmlTests.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,5 +113,18 @@ public void ToJSONWithGroupsTest()
113113
Assert.That(ex.Message,
114114
Does.Contain("Must be non-null if 'convertEnumsToDescriptions' is true. (Parameter 'dataDictionary')"));
115115
}
116+
117+
[Test]
118+
public void ToJSONWithNewLineTest()
119+
{
120+
var dd = new QuickFix.DataDictionary.DataDictionary();
121+
dd.Load(Path.Combine(TestContext.CurrentContext.TestDirectory, "spec", "fix", "FIX44.xml"));
122+
123+
var msg = new Message();
124+
msg.SetField(new QuickFix.Fields.Text("Some text.\r\n With some more text"));
125+
126+
const string expected = "{\"Header\":{},\"Body\":{\"Text\":\"Some text.\\r\\n With some more text\"},\"Trailer\":{}}";
127+
Assert.That(msg.ToJSON(dd), Is.EqualTo(expected));
128+
}
116129
}
117130
}

0 commit comments

Comments
 (0)