Skip to content

Commit 1f390b7

Browse files
Improve JSON component (#12086)
fix(json): Fix JSON writer size computation Size is now exact and do no longer need separate counting feat(json): Allow mapper to be used with writer This avoids creating intermediate writer to map collection in existing json/writter fix(aws): Fix intermediate writer for tag map Co-authored-by: bruce.bujon <bruce.bujon@datadoghq.com>
1 parent 8930561 commit 1f390b7

6 files changed

Lines changed: 165 additions & 57 deletions

File tree

components/json/src/main/java/datadog/json/JsonMapper.java

Lines changed: 97 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -41,35 +41,57 @@ public static String toJson(Map<String, ?> map) {
4141
return "{}";
4242
}
4343
try (JsonWriter writer = new JsonWriter()) {
44+
writeMap(writer, map);
45+
return writer.toString();
46+
}
47+
}
48+
49+
/**
50+
* Writes the map as JSON value to the given mapper.
51+
*
52+
* @param writer The writer to write the map as value to.
53+
* @param map The map to write.
54+
*/
55+
public static void writeAsJsonValue(JsonWriter writer, Map<String, ?> map) {
56+
if (writer == null) {
57+
throw new NullPointerException("writer cannot be null");
58+
}
59+
if (map == null) {
4460
writer.beginObject();
45-
for (Map.Entry<String, ?> entry : map.entrySet()) {
46-
writer.name(entry.getKey());
47-
Object value = entry.getValue();
48-
if (value == null) {
49-
writer.nullValue();
50-
} else if (value instanceof String) {
51-
writer.value((String) value);
52-
} else if (value instanceof Double) {
53-
writer.value((Double) value);
54-
} else if (value instanceof Float) {
55-
writer.value((Float) value);
56-
} else if (value instanceof Long) {
57-
writer.value((Long) value);
58-
} else if (value instanceof Integer) {
59-
writer.value((Integer) value);
60-
} else if (value instanceof Boolean) {
61-
writer.value((Boolean) value);
62-
} else {
63-
writer.value(value.toString());
64-
}
65-
}
6661
writer.endObject();
67-
return writer.toString();
62+
} else {
63+
writeMap(writer, map);
6864
}
6965
}
7066

67+
private static void writeMap(JsonWriter writer, Map<String, ?> map) {
68+
writer.beginObject();
69+
for (Map.Entry<String, ?> entry : map.entrySet()) {
70+
writer.name(entry.getKey());
71+
Object value = entry.getValue();
72+
if (value == null) {
73+
writer.nullValue();
74+
} else if (value instanceof String) {
75+
writer.value((String) value);
76+
} else if (value instanceof Double) {
77+
writer.value((Double) value);
78+
} else if (value instanceof Float) {
79+
writer.value((Float) value);
80+
} else if (value instanceof Long) {
81+
writer.value((Long) value);
82+
} else if (value instanceof Integer) {
83+
writer.value((Integer) value);
84+
} else if (value instanceof Boolean) {
85+
writer.value((Boolean) value);
86+
} else {
87+
writer.value(value.toString());
88+
}
89+
}
90+
writer.endObject();
91+
}
92+
7193
/**
72-
* Converts a {@code Iterable<String>} to a JSON array.
94+
* Converts a {@code Collection<String>} to a JSON array.
7395
*
7496
* @param items The iterable to convert.
7597
* @return The converted JSON array as Java string.
@@ -80,15 +102,37 @@ public static String toJson(Collection<String> items) {
80102
return "[]";
81103
}
82104
try (JsonWriter writer = new JsonWriter()) {
105+
writeArray(items, writer);
106+
return writer.toString();
107+
}
108+
}
109+
110+
/**
111+
* Writes the {@code Collection<String>} as a JSON array to the given writer.
112+
*
113+
* @param items The collection to write.
114+
* @param writer The writer to write the collection as JSON array to.
115+
*/
116+
public static void writeAsJsonValue(Collection<String> items, JsonWriter writer) {
117+
if (writer == null) {
118+
throw new NullPointerException("writer cannot be null");
119+
}
120+
if (items == null) {
83121
writer.beginArray();
84-
for (String item : items) {
85-
writer.value(item);
86-
}
87122
writer.endArray();
88-
return writer.toString();
123+
} else {
124+
writeArray(items, writer);
89125
}
90126
}
91127

128+
private static void writeArray(Iterable<String> items, JsonWriter writer) {
129+
writer.beginArray();
130+
for (String item : items) {
131+
writer.value(item);
132+
}
133+
writer.endArray();
134+
}
135+
92136
/**
93137
* Converts a String array to a JSON array.
94138
*
@@ -101,13 +145,35 @@ public static String toJson(String[] items) {
101145
return "[]";
102146
}
103147
try (JsonWriter writer = new JsonWriter()) {
148+
writeArray(items, writer);
149+
return writer.toString();
150+
}
151+
}
152+
153+
/**
154+
* Writes the String array as a JSON array to the given writer.
155+
*
156+
* @param items The array to write.
157+
* @param writer The writer to write the array as JSON array to.
158+
*/
159+
public static void writeAsJsonValue(String[] items, JsonWriter writer) {
160+
if (writer == null) {
161+
throw new NullPointerException("writer cannot be null");
162+
}
163+
if (items == null) {
104164
writer.beginArray();
105-
for (String item : items) {
106-
writer.value(item);
107-
}
108165
writer.endArray();
109-
return writer.toString();
166+
} else {
167+
writeArray(items, writer);
168+
}
169+
}
170+
171+
private static void writeArray(String[] items, JsonWriter writer) {
172+
writer.beginArray();
173+
for (String item : items) {
174+
writer.value(item);
110175
}
176+
writer.endArray();
111177
}
112178

113179
/**

components/json/src/main/java/datadog/json/JsonWriter.java

Lines changed: 9 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ public final class JsonWriter implements Flushable, AutoCloseable {
1919
private final JsonStructure structure;
2020

2121
private boolean requireComma;
22-
private int bytesWritten;
2322

2423
/** Creates a writer with structure check. */
2524
public JsonWriter() {
@@ -246,9 +245,15 @@ public void flush() {
246245
}
247246
}
248247

249-
/** Approximate number of bytes written so far. */
250-
public int sizeInBytes() {
251-
return this.bytesWritten;
248+
/**
249+
* Returns the current JSON size in bytes.
250+
*
251+
* @return The JSON size in bytes.
252+
* @see #toByteArray()
253+
*/
254+
public int size() {
255+
flush();
256+
return this.outputStream.size();
252257
}
253258

254259
@Override
@@ -274,15 +279,13 @@ private void endsValue() {
274279
private void write(char ch) {
275280
try {
276281
this.writer.write(ch);
277-
this.bytesWritten++;
278282
} catch (IOException ignored) {
279283
}
280284
}
281285

282286
private void writeStringLiteral(String str) {
283287
try {
284288
this.writer.write('"');
285-
int count = 1;
286289

287290
for (int i = 0; i < str.length(); ++i) {
288291
char c = str.charAt(i);
@@ -294,40 +297,33 @@ private void writeStringLiteral(String str) {
294297
this.writer.write(HEX_DIGITS[(c >>> 8) & 0xF]);
295298
this.writer.write(HEX_DIGITS[(c >>> 4) & 0xF]);
296299
this.writer.write(HEX_DIGITS[c & 0xF]);
297-
count += 6;
298300
} else {
299301
switch (c) {
300302
case '"': // Quotation mark
301303
case '\\': // Reverse solidus
302304
case '/': // Solidus
303305
this.writer.write('\\');
304306
this.writer.write(c);
305-
count += 2;
306307
break;
307308
case '\b': // Backspace
308309
this.writer.write('\\');
309310
this.writer.write('b');
310-
count += 2;
311311
break;
312312
case '\f': // Form feed
313313
this.writer.write('\\');
314314
this.writer.write('f');
315-
count += 2;
316315
break;
317316
case '\n': // Line feed
318317
this.writer.write('\\');
319318
this.writer.write('n');
320-
count += 2;
321319
break;
322320
case '\r': // Carriage return
323321
this.writer.write('\\');
324322
this.writer.write('r');
325-
count += 2;
326323
break;
327324
case '\t': // Horizontal tab
328325
this.writer.write('\\');
329326
this.writer.write('t');
330-
count += 2;
331327
break;
332328
default:
333329
if (c < 0x20) {
@@ -337,28 +333,22 @@ private void writeStringLiteral(String str) {
337333
this.writer.write('0');
338334
this.writer.write(HEX_DIGITS[(c >>> 4) & 0xF]);
339335
this.writer.write(HEX_DIGITS[c & 0xF]);
340-
count += 6;
341336
} else {
342337
this.writer.write(c);
343-
count += 1;
344338
}
345339
break;
346340
}
347341
}
348342
}
349343

350344
this.writer.write('"');
351-
count += 1;
352-
353-
this.bytesWritten += count;
354345
} catch (IOException ignored) {
355346
}
356347
}
357348

358349
private void writeStringRaw(String str) {
359350
try {
360351
this.writer.write(str);
361-
this.bytesWritten += str.length(); // exact if ASCII, estimate otherwise
362352
} catch (IOException ignored) {
363353
}
364354
}

components/json/src/test/java/datadog/json/JsonMapperTest.java

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,23 @@ static Stream<Arguments> testMappingToJsonObjectArguments() {
7979
"{\"key1\":null,\"key2\":\"bar\",\"key3\":3,\"key4\":3456789123,\"key5\":3.142,\"key6\":3.141592653589793,\"key7\":true,\"key8\":\"toString\"}"));
8080
}
8181

82+
@TableTest({
83+
"Scenario | Map | Expected ",
84+
"null | | '{}' ",
85+
"empty | [:] | '{}' ",
86+
"default | [key1: value1, key2: value2] | '{\"key1\":\"value1\",\"key2\":\"value2\"}'"
87+
})
88+
void testWritingAsJsonValue(
89+
@Scenario String ignoredScenario, Map<String, Object> map, String expected) {
90+
try (JsonWriter writer = new JsonWriter()) {
91+
writer.beginObject();
92+
writer.name("map");
93+
JsonMapper.writeAsJsonValue(writer, (Map<String, ?>) map);
94+
writer.endObject();
95+
assertEquals("{\"map\":" + expected + "}", writer.toString());
96+
}
97+
}
98+
8299
@ParameterizedTest(name = "test mapping to Map from empty JSON object: {0}")
83100
@NullSource
84101
@ValueSource(strings = {"null", "", "{}"})
@@ -110,6 +127,23 @@ void testMappingIterableToJsonArray(List<String> input, String expected) throws
110127
assertEquals(input != null ? input : emptyList(), parsed);
111128
}
112129

130+
@TableTest({
131+
"Scenario | Collection | Expected ",
132+
"null | | '[]' ",
133+
"empty | [] | '[]' ",
134+
"default | [value1, value2] | '[\"value1\",\"value2\"]'"
135+
})
136+
void testWritingCollectionAsJsonValue(
137+
@Scenario String ignoredScenario, List<String> collection, String expected) {
138+
try (JsonWriter writer = new JsonWriter()) {
139+
writer.beginObject();
140+
writer.name("collection");
141+
JsonMapper.writeAsJsonValue(collection, writer);
142+
writer.endObject();
143+
assertEquals("{\"collection\":" + expected + "}", writer.toString());
144+
}
145+
}
146+
113147
@TableTest({
114148
"Scenario | Input | Expected ",
115149
"null input | | '[]' ",
@@ -128,6 +162,23 @@ void testMappingArrayToJsonArray(String ignoredScenario, String[] input, String
128162
assertArrayEquals(input != null ? input : new String[] {}, parsed);
129163
}
130164

165+
@TableTest({
166+
"Scenario | Array | Expected ",
167+
"null | | '[]' ",
168+
"empty | [] | '[]' ",
169+
"default | [value1, value2] | '[\"value1\",\"value2\"]'"
170+
})
171+
void testWritingArrayAsJsonValue(
172+
@Scenario String ignoredScenario, String[] array, String expected) {
173+
try (JsonWriter writer = new JsonWriter()) {
174+
writer.beginObject();
175+
writer.name("array");
176+
JsonMapper.writeAsJsonValue(array, writer);
177+
writer.endObject();
178+
assertEquals("{\"array\":" + expected + "}", writer.toString());
179+
}
180+
}
181+
131182
@ParameterizedTest(name = "test mapping to List from empty JSON object: {0}")
132183
@NullSource
133184
@ValueSource(strings = {"null", "", "[]"})

components/json/src/test/java/datadog/json/JsonWriterTest.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -166,9 +166,9 @@ void testCompleteObject() {
166166
}
167167

168168
@Test
169-
void testSizeInBytes() {
169+
void testSize() {
170170
try (JsonWriter writer = new JsonWriter()) {
171-
assertEquals(0, writer.sizeInBytes(), "Check empty writer size");
171+
assertEquals(0, writer.size(), "Check empty writer size");
172172

173173
writer.beginArray();
174174
assertSizeInBytes(writer, "Check size after plain ASCII string");
@@ -210,7 +210,8 @@ void testSizeInBytes() {
210210
}
211211

212212
private static void assertSizeInBytes(JsonWriter writer, String message) {
213-
assertEquals(writer.toByteArray().length, writer.sizeInBytes(), message);
213+
int size = writer.size();
214+
assertEquals(writer.toByteArray().length, size, message);
214215
}
215216

216217
@Test

dd-java-agent/instrumentation/aws-java/aws-java-sfn-2.0/src/main/java/datadog/trace/instrumentation/aws/v2/sfn/InputAttributeInjector.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,12 @@ public class InputAttributeInjector {
88
private static final String DATADOG_KEY = "_datadog";
99

1010
public static String buildTraceContext(AgentSpan span) {
11-
String tagsJson = JsonMapper.toJson(span.getTags());
1211
try (JsonWriter writer = new JsonWriter()) {
1312
writer.beginObject();
1413
writer.name("x-datadog-trace-id").value(span.getTraceId().toString());
1514
writer.name("x-datadog-parent-id").value(String.valueOf(span.getSpanId()));
16-
writer.name("x-datadog-tags").jsonValue(tagsJson);
15+
writer.name("x-datadog-tags");
16+
JsonMapper.writeAsJsonValue(writer, span.getTags());
1717
writer.endObject();
1818
return writer.toString();
1919
} catch (Exception e) {

0 commit comments

Comments
 (0)