summaryrefslogtreecommitdiffstats
path: root/testutil
diff options
context:
space:
mode:
authorHåkon Hallingstad <hakon@yahooinc.com>2022-10-27 14:30:17 +0200
committerHåkon Hallingstad <hakon@yahooinc.com>2022-10-27 14:30:17 +0200
commit170f6d98a806b77827538697171d70a180278972 (patch)
treee570d6220a0fe4d2fa428834e70806d6d56d895a /testutil
parent043af29b961841228dcd95e752327568a76db0db (diff)
Add JSON normalize method to test helper
Diffstat (limited to 'testutil')
-rw-r--r--testutil/src/main/java/com/yahoo/test/json/JsonBuilder.java75
-rw-r--r--testutil/src/main/java/com/yahoo/test/json/JsonNodeFormatter.java81
-rw-r--r--testutil/src/main/java/com/yahoo/test/json/JsonTestHelper.java25
-rw-r--r--testutil/src/test/java/com/yahoo/test/json/JsonTestHelperTest.java45
4 files changed, 226 insertions, 0 deletions
diff --git a/testutil/src/main/java/com/yahoo/test/json/JsonBuilder.java b/testutil/src/main/java/com/yahoo/test/json/JsonBuilder.java
new file mode 100644
index 00000000000..bbb8586a290
--- /dev/null
+++ b/testutil/src/main/java/com/yahoo/test/json/JsonBuilder.java
@@ -0,0 +1,75 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.test.json;
+
+import com.fasterxml.jackson.core.io.JsonStringEncoder;
+
+/**
+ * String buffer for building a formatted JSON.
+ *
+ * @author hakonhall
+ */
+class JsonBuilder {
+ private final JsonStringEncoder jsonStringEncoder = JsonStringEncoder.getInstance();
+ private final StringBuilder builder = new StringBuilder();
+ private final String indentation;
+ private final boolean multiLine;
+ private final String colon;
+
+ private boolean bol = true;
+ private int level = 0;
+
+ static JsonBuilder forCompactJson() { return new JsonBuilder(0, true); }
+ static JsonBuilder forMultiLineJson(int spacesPerIndent) { return new JsonBuilder(spacesPerIndent, false); }
+
+ JsonBuilder(int spacesPerIndent, boolean compact) {
+ this.indentation = compact ? "" : " ".repeat(spacesPerIndent);
+ this.multiLine = !compact;
+ this.colon = compact ? ":" : ": ";
+ }
+
+ void appendLineAndIndent(String text) { appendLineAndIndent(text, 0); }
+
+ void newLineIndentAndAppend(int levelShift, String text) {
+ appendNewLine();
+ indent(levelShift);
+ append(text);
+ }
+
+ void appendLineAndIndent(String text, int levelShift) {
+ appendLine(text);
+ indent(levelShift);
+ append("");
+ }
+
+ void appendColon() { builder.append(colon); }
+
+ void appendStringValue(String rawString) {
+ builder.append('"');
+ jsonStringEncoder.quoteAsString(rawString, builder);
+ builder.append('"');
+ }
+
+ void append(String textWithoutNewline) {
+ if (bol) {
+ builder.append(indentation.repeat(level));
+ bol = false;
+ }
+
+ builder.append(textWithoutNewline);
+ }
+
+ private void indent(int levelShift) { level += levelShift; }
+
+ private void appendLine(String text) {
+ append(text);
+ appendNewLine();
+ }
+
+ private void appendNewLine() {
+ if (multiLine) builder.append('\n');
+ bol = true;
+ }
+
+ @Override
+ public String toString() { return builder.toString(); }
+}
diff --git a/testutil/src/main/java/com/yahoo/test/json/JsonNodeFormatter.java b/testutil/src/main/java/com/yahoo/test/json/JsonNodeFormatter.java
new file mode 100644
index 00000000000..d0e07e66e0b
--- /dev/null
+++ b/testutil/src/main/java/com/yahoo/test/json/JsonNodeFormatter.java
@@ -0,0 +1,81 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.test.json;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+
+/**
+ * Formats a {@link JsonNode} to a normalized JSON string, see {@link JsonTestHelper}.
+ *
+ * @author hakonhall
+ */
+class JsonNodeFormatter {
+ private final JsonBuilder builder;
+
+ /** See {@link JsonTestHelper}. */
+ static String toNormalizedJson(JsonNode jsonNode, boolean compact) {
+ JsonNodeFormatter formatter = new JsonNodeFormatter(compact);
+ formatter.appendValue(jsonNode);
+ return formatter.toString();
+ }
+
+ private JsonNodeFormatter(boolean compact) {
+ builder = compact ? JsonBuilder.forCompactJson() : JsonBuilder.forMultiLineJson(2);
+ }
+
+ private void appendValue(JsonNode jsonNode) {
+ switch (jsonNode.getNodeType()) {
+ case OBJECT -> {
+ ObjectNode objectNode = (ObjectNode) jsonNode;
+ ArrayList<String> fieldNames = new ArrayList<>();
+ objectNode.fieldNames().forEachRemaining(fieldNames::add);
+ Collections.sort(fieldNames);
+ if (fieldNames.isEmpty()) {
+ builder.append("{}");
+ } else {
+ boolean firstIteration = true;
+ for (var fieldName : fieldNames) {
+ if (firstIteration) {
+ builder.appendLineAndIndent("{", +1);
+ firstIteration = false;
+ } else {
+ builder.appendLineAndIndent(",");
+ }
+
+ builder.appendStringValue(fieldName);
+ builder.appendColon();
+ appendValue(objectNode.get(fieldName));
+ }
+
+ builder.newLineIndentAndAppend(-1, "}");
+ }
+ }
+ case ARRAY -> {
+ Iterator<JsonNode> elements = jsonNode.elements();
+ if (elements.hasNext()) {
+ builder.appendLineAndIndent("[", +1);
+ appendValue(elements.next());
+
+ while (elements.hasNext()) {
+ builder.appendLineAndIndent(",");
+ appendValue(elements.next());
+ }
+
+ builder.newLineIndentAndAppend(-1, "]");
+ } else {
+ builder.append("[]");
+ }
+ }
+ case BOOLEAN, NUMBER, NULL -> builder.append(jsonNode.asText());
+ case STRING -> builder.appendStringValue(jsonNode.asText());
+ case BINARY, MISSING, POJO -> throw new IllegalStateException(jsonNode.getNodeType().toString());
+ }
+ }
+
+ @Override
+ public String toString() { return builder.toString(); }
+}
diff --git a/testutil/src/main/java/com/yahoo/test/json/JsonTestHelper.java b/testutil/src/main/java/com/yahoo/test/json/JsonTestHelper.java
index f7112ee9379..589cc3ab4ef 100644
--- a/testutil/src/main/java/com/yahoo/test/json/JsonTestHelper.java
+++ b/testutil/src/main/java/com/yahoo/test/json/JsonTestHelper.java
@@ -17,6 +17,31 @@ public class JsonTestHelper {
private static final ObjectMapper mapper = new ObjectMapper();
/**
+ * Returns a normalized JSON String.
+ *
+ * <ol>
+ * <li>A JSON string with each object's names in sorted order.</li>
+ * <li>Two JSONs are equal iff their normalized JSON strings are equal.*</li>
+ * <li>The normalized JSON is (by default) an indented multi-line string to facilitate
+ * readability and line-based diff tools.</li>
+ * <li>The normalized string does not end with a newline (\n).</li>
+ * </ol>
+ *
+ * <p>*) No effort is done to normalize decimals and may cause false non-equality,
+ * e.g. 1.2e1 is not equal to 12. This may be fixed at a later time if needed.</p>
+ */
+ public static String normalize(String json) {
+ JsonNode jsonNode;
+ try {
+ jsonNode = mapper.readTree(json);
+ } catch (JsonProcessingException e) {
+ throw new IllegalArgumentException("Invalid JSON", e);
+ }
+
+ return JsonNodeFormatter.toNormalizedJson(jsonNode, false);
+ }
+
+ /**
* Convenience method to input JSON without escaping double quotes and newlines
* Each parameter represents a line of JSON encoded data
* The lines are joined with newline and single quotes are replaced with double quotes
diff --git a/testutil/src/test/java/com/yahoo/test/json/JsonTestHelperTest.java b/testutil/src/test/java/com/yahoo/test/json/JsonTestHelperTest.java
new file mode 100644
index 00000000000..d0798284da1
--- /dev/null
+++ b/testutil/src/test/java/com/yahoo/test/json/JsonTestHelperTest.java
@@ -0,0 +1,45 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.test.json;
+
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+/**
+ * @author hakonhall
+ */
+public class JsonTestHelperTest {
+ @Test
+ public void normalize() {
+ verifyNormalization("""
+ {"a": 1, "c": 2,
+ "b": [ {"n": 3, "m": 4}, 5 ]
+ }
+ """,
+ """
+ {
+ "a": 1,
+ "b": [
+ {
+ "m": 4,
+ "n": 3
+ },
+ 5
+ ],
+ "c": 2
+ }""");
+
+ verifyNormalization("[1,2]", """
+ [
+ 1,
+ 2
+ ]""");
+
+ verifyNormalization("null", "null");
+ verifyNormalization("{ \n}", "{}");
+ }
+
+ private static void verifyNormalization(String json, String normalizedJson) {
+ assertEquals(normalizedJson, JsonTestHelper.normalize(json));
+ }
+} \ No newline at end of file