Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
128 changes: 97 additions & 31 deletions components/json/src/main/java/datadog/json/JsonMapper.java
Original file line number Diff line number Diff line change
Expand Up @@ -41,35 +41,57 @@ public static String toJson(Map<String, ?> map) {
return "{}";
}
try (JsonWriter writer = new JsonWriter()) {
writeMap(writer, map);
return writer.toString();
}
}

/**
* Writes the map as JSON value to the given mapper.
*
* @param writer The writer to write the map as value to.
* @param map The map to write.
*/
public static void writeAsJsonValue(JsonWriter writer, Map<String, ?> map) {
if (writer == null) {
throw new NullPointerException("writer cannot be null");
}
if (map == null) {
writer.beginObject();
for (Map.Entry<String, ?> entry : map.entrySet()) {
writer.name(entry.getKey());
Object value = entry.getValue();
if (value == null) {
writer.nullValue();
} else if (value instanceof String) {
writer.value((String) value);
} else if (value instanceof Double) {
writer.value((Double) value);
} else if (value instanceof Float) {
writer.value((Float) value);
} else if (value instanceof Long) {
writer.value((Long) value);
} else if (value instanceof Integer) {
writer.value((Integer) value);
} else if (value instanceof Boolean) {
writer.value((Boolean) value);
} else {
writer.value(value.toString());
}
}
writer.endObject();
return writer.toString();
} else {
writeMap(writer, map);
}
}

private static void writeMap(JsonWriter writer, Map<String, ?> map) {
writer.beginObject();
for (Map.Entry<String, ?> entry : map.entrySet()) {
writer.name(entry.getKey());
Object value = entry.getValue();
if (value == null) {
writer.nullValue();
} else if (value instanceof String) {
writer.value((String) value);
} else if (value instanceof Double) {
writer.value((Double) value);
} else if (value instanceof Float) {
writer.value((Float) value);
} else if (value instanceof Long) {
writer.value((Long) value);
} else if (value instanceof Integer) {
writer.value((Integer) value);
} else if (value instanceof Boolean) {
writer.value((Boolean) value);
} else {
writer.value(value.toString());
}
}
writer.endObject();
}

/**
* Converts a {@code Iterable<String>} to a JSON array.
* Converts a {@code Collection<String>} to a JSON array.
*
* @param items The iterable to convert.
* @return The converted JSON array as Java string.
Expand All @@ -80,15 +102,37 @@ public static String toJson(Collection<String> items) {
return "[]";
}
try (JsonWriter writer = new JsonWriter()) {
writeArray(items, writer);
return writer.toString();
}
}

/**
* Writes the {@code Collection<String>} as a JSON array to the given writer.
*
* @param items The collection to write.
* @param writer The writer to write the collection as JSON array to.
*/
public static void writeAsJsonValue(Collection<String> items, JsonWriter writer) {
if (writer == null) {
throw new NullPointerException("writer cannot be null");
}
if (items == null) {
writer.beginArray();
for (String item : items) {
writer.value(item);
}
writer.endArray();
return writer.toString();
} else {
writeArray(items, writer);
}
}

private static void writeArray(Iterable<String> items, JsonWriter writer) {
writer.beginArray();
for (String item : items) {
writer.value(item);
}
writer.endArray();
}

/**
* Converts a String array to a JSON array.
*
Expand All @@ -101,13 +145,35 @@ public static String toJson(String[] items) {
return "[]";
}
try (JsonWriter writer = new JsonWriter()) {
writeArray(items, writer);
return writer.toString();
}
}

/**
* Writes the String array as a JSON array to the given writer.
*
* @param items The array to write.
* @param writer The writer to write the array as JSON array to.
*/
public static void writeAsJsonValue(String[] items, JsonWriter writer) {
if (writer == null) {
throw new NullPointerException("writer cannot be null");
}
if (items == null) {
writer.beginArray();
for (String item : items) {
writer.value(item);
}
writer.endArray();
return writer.toString();
} else {
writeArray(items, writer);
}
}

private static void writeArray(String[] items, JsonWriter writer) {
writer.beginArray();
for (String item : items) {
writer.value(item);
}
writer.endArray();
}

/**
Expand Down
28 changes: 9 additions & 19 deletions components/json/src/main/java/datadog/json/JsonWriter.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ public final class JsonWriter implements Flushable, AutoCloseable {
private final JsonStructure structure;

private boolean requireComma;
private int bytesWritten;

/** Creates a writer with structure check. */
public JsonWriter() {
Expand Down Expand Up @@ -246,9 +245,15 @@ public void flush() {
}
}

/** Approximate number of bytes written so far. */
public int sizeInBytes() {
return this.bytesWritten;
/**
* Returns the current JSON size in bytes.
*
* @return The JSON size in bytes.
* @see #toByteArray()
*/
public int size() {
flush();
return this.outputStream.size();
Comment thread
PerfectSlayer marked this conversation as resolved.
Comment thread
PerfectSlayer marked this conversation as resolved.
}

@Override
Expand All @@ -274,15 +279,13 @@ private void endsValue() {
private void write(char ch) {
try {
this.writer.write(ch);
this.bytesWritten++;
} catch (IOException ignored) {
}
}

private void writeStringLiteral(String str) {
try {
this.writer.write('"');
int count = 1;

for (int i = 0; i < str.length(); ++i) {
char c = str.charAt(i);
Expand All @@ -294,40 +297,33 @@ private void writeStringLiteral(String str) {
this.writer.write(HEX_DIGITS[(c >>> 8) & 0xF]);
this.writer.write(HEX_DIGITS[(c >>> 4) & 0xF]);
this.writer.write(HEX_DIGITS[c & 0xF]);
count += 6;
} else {
switch (c) {
case '"': // Quotation mark
case '\\': // Reverse solidus
case '/': // Solidus
this.writer.write('\\');
this.writer.write(c);
count += 2;
break;
case '\b': // Backspace
this.writer.write('\\');
this.writer.write('b');
count += 2;
break;
case '\f': // Form feed
this.writer.write('\\');
this.writer.write('f');
count += 2;
break;
case '\n': // Line feed
this.writer.write('\\');
this.writer.write('n');
count += 2;
break;
case '\r': // Carriage return
this.writer.write('\\');
this.writer.write('r');
count += 2;
break;
case '\t': // Horizontal tab
this.writer.write('\\');
this.writer.write('t');
count += 2;
break;
default:
if (c < 0x20) {
Expand All @@ -337,28 +333,22 @@ private void writeStringLiteral(String str) {
this.writer.write('0');
this.writer.write(HEX_DIGITS[(c >>> 4) & 0xF]);
this.writer.write(HEX_DIGITS[c & 0xF]);
count += 6;
} else {
this.writer.write(c);
count += 1;
}
break;
}
}
}

this.writer.write('"');
count += 1;

this.bytesWritten += count;
} catch (IOException ignored) {
}
}

private void writeStringRaw(String str) {
try {
this.writer.write(str);
this.bytesWritten += str.length(); // exact if ASCII, estimate otherwise
} catch (IOException ignored) {
}
}
Expand Down
51 changes: 51 additions & 0 deletions components/json/src/test/java/datadog/json/JsonMapperTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,23 @@ static Stream<Arguments> testMappingToJsonObjectArguments() {
"{\"key1\":null,\"key2\":\"bar\",\"key3\":3,\"key4\":3456789123,\"key5\":3.142,\"key6\":3.141592653589793,\"key7\":true,\"key8\":\"toString\"}"));
}

@TableTest({
"Scenario | Map | Expected ",
"null | | '{}' ",
"empty | [:] | '{}' ",
"default | [key1: value1, key2: value2] | '{\"key1\":\"value1\",\"key2\":\"value2\"}'"
})
void testWritingAsJsonValue(
@Scenario String ignoredScenario, Map<String, Object> map, String expected) {
try (JsonWriter writer = new JsonWriter()) {
writer.beginObject();
writer.name("map");
JsonMapper.writeAsJsonValue(writer, (Map<String, ?>) map);
writer.endObject();
assertEquals("{\"map\":" + expected + "}", writer.toString());
}
}

@ParameterizedTest(name = "test mapping to Map from empty JSON object: {0}")
@NullSource
@ValueSource(strings = {"null", "", "{}"})
Expand Down Expand Up @@ -110,6 +127,23 @@ void testMappingIterableToJsonArray(List<String> input, String expected) throws
assertEquals(input != null ? input : emptyList(), parsed);
}

@TableTest({
"Scenario | Collection | Expected ",
"null | | '[]' ",
"empty | [] | '[]' ",
"default | [value1, value2] | '[\"value1\",\"value2\"]'"
})
void testWritingCollectionAsJsonValue(
@Scenario String ignoredScenario, List<String> collection, String expected) {
try (JsonWriter writer = new JsonWriter()) {
writer.beginObject();
writer.name("collection");
JsonMapper.writeAsJsonValue(collection, writer);
writer.endObject();
assertEquals("{\"collection\":" + expected + "}", writer.toString());
}
}

@TableTest({
"Scenario | Input | Expected ",
"null input | | '[]' ",
Expand All @@ -128,6 +162,23 @@ void testMappingArrayToJsonArray(String ignoredScenario, String[] input, String
assertArrayEquals(input != null ? input : new String[] {}, parsed);
}

@TableTest({
"Scenario | Array | Expected ",
"null | | '[]' ",
"empty | [] | '[]' ",
"default | [value1, value2] | '[\"value1\",\"value2\"]'"
})
void testWritingArrayAsJsonValue(
@Scenario String ignoredScenario, String[] array, String expected) {
try (JsonWriter writer = new JsonWriter()) {
writer.beginObject();
writer.name("array");
JsonMapper.writeAsJsonValue(array, writer);
writer.endObject();
assertEquals("{\"array\":" + expected + "}", writer.toString());
}
}

@ParameterizedTest(name = "test mapping to List from empty JSON object: {0}")
@NullSource
@ValueSource(strings = {"null", "", "[]"})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -166,9 +166,9 @@ void testCompleteObject() {
}

@Test
void testSizeInBytes() {
void testSize() {
try (JsonWriter writer = new JsonWriter()) {
assertEquals(0, writer.sizeInBytes(), "Check empty writer size");
assertEquals(0, writer.size(), "Check empty writer size");

writer.beginArray();
assertSizeInBytes(writer, "Check size after plain ASCII string");
Expand Down Expand Up @@ -210,7 +210,8 @@ void testSizeInBytes() {
}

private static void assertSizeInBytes(JsonWriter writer, String message) {
assertEquals(writer.toByteArray().length, writer.sizeInBytes(), message);
int size = writer.size();
assertEquals(writer.toByteArray().length, size, message);
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@ public class InputAttributeInjector {
private static final String DATADOG_KEY = "_datadog";

public static String buildTraceContext(AgentSpan span) {
String tagsJson = JsonMapper.toJson(span.getTags());
try (JsonWriter writer = new JsonWriter()) {
writer.beginObject();
writer.name("x-datadog-trace-id").value(span.getTraceId().toString());
writer.name("x-datadog-parent-id").value(String.valueOf(span.getSpanId()));
writer.name("x-datadog-tags").jsonValue(tagsJson);
writer.name("x-datadog-tags");
JsonMapper.writeAsJsonValue(writer, span.getTags());
writer.endObject();
return writer.toString();
} catch (Exception e) {
Expand Down
Loading