Skip to content

Commit

Permalink
#44 Normalizer (#787)
Browse files Browse the repository at this point in the history
  • Loading branch information
lukas-krecan authored Jul 4, 2024
1 parent 946832f commit baeb5b6
Show file tree
Hide file tree
Showing 4 changed files with 157 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@ public enum Option {
*/
IGNORING_VALUES,

/**
* Changes the exception thrown, so it's more friendly to IDE diff visualization.
*/
REPORTING_DIFFERENCE_AS_NORMALIZED_STRING,
/**
* Stops comparison at the first difference. Can bring performance boots to use-cases that do not need the full list of all differences.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import static net.javacrumbs.jsonunit.core.Option.IGNORING_EXTRA_ARRAY_ITEMS;
import static net.javacrumbs.jsonunit.core.Option.IGNORING_EXTRA_FIELDS;
import static net.javacrumbs.jsonunit.core.Option.IGNORING_VALUES;
import static net.javacrumbs.jsonunit.core.Option.REPORTING_DIFFERENCE_AS_NORMALIZED_STRING;
import static net.javacrumbs.jsonunit.core.Option.TREATING_NULL_AS_ABSENT;
import static net.javacrumbs.jsonunit.core.internal.ClassUtils.isClassPresent;
import static net.javacrumbs.jsonunit.core.internal.DifferenceContextImpl.differenceContext;
Expand All @@ -34,6 +35,7 @@
import static net.javacrumbs.jsonunit.core.internal.JsonUtils.quoteIfNeeded;
import static net.javacrumbs.jsonunit.core.internal.Node.KeyValue;
import static net.javacrumbs.jsonunit.core.internal.Node.NodeType;
import static net.javacrumbs.jsonunit.core.internal.Normalizer.toNormalizedString;

import java.math.BigDecimal;
import java.util.ArrayList;
Expand All @@ -55,6 +57,7 @@
import net.javacrumbs.jsonunit.core.internal.ArrayComparison.ComparisonResult;
import net.javacrumbs.jsonunit.core.internal.ArrayComparison.NodeWithIndex;
import net.javacrumbs.jsonunit.core.listener.Difference;
import org.opentest4j.AssertionFailedError;

/**
* Compares JSON structures. Mainly for internal use, the API might be more volatile than the rest.
Expand Down Expand Up @@ -684,9 +687,6 @@ private void logDifferences() {

/**
* Returns children of an ObjectNode.
*
* @param node
* @return
*/
private static Map<String, Node> getFields(Node node) {
Map<String, Node> result = new HashMap<>();
Expand Down Expand Up @@ -720,7 +720,18 @@ public void failIfDifferent() {

public void failIfDifferent(String message) {
if (!similar()) {
throw createException(message, differences);
if (!configuration.getOptions().contains(REPORTING_DIFFERENCE_AS_NORMALIZED_STRING)
|| actualRoot.isMissingNode()) {
throw createException(message, differences);
} else {
String normalizedExpected = toNormalizedString(expectedRoot);
String normalizedActual = toNormalizedString(actualRoot);
throw new AssertionFailedError(
"JSON documents are different: expected <" + normalizedExpected + ">" + "but was <"
+ normalizedActual + ">",
normalizedExpected,
normalizedActual);
}
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package net.javacrumbs.jsonunit.core.internal;

import static java.util.Comparator.comparing;

import java.util.Iterator;
import java.util.Spliterators;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import net.javacrumbs.jsonunit.core.internal.Node.KeyValue;

class Normalizer {

private static int depth = 2;

static String toNormalizedString(Node node) {
StringBuilder sb = new StringBuilder();
normalize(node, sb, 0);
return sb.toString();
}

private static void normalize(Node node, StringBuilder sb, int indent) {
switch (node.getNodeType()) {
case OBJECT -> normalizeObject(node, sb, indent);
case ARRAY -> normalizeArray(node, sb, indent);
case STRING -> sb.append('\"').append(node).append('"');
default -> sb.append(node);
}
}

private static void normalizeArray(Node node, StringBuilder sb, int indent) {
sb.append("[\n");
Iterator<Node> elements = node.arrayElements();
while (elements.hasNext()) {
var element = elements.next();
addIndent(sb, indent + depth);
normalize(element, sb, indent + depth);
if (elements.hasNext()) sb.append(",");
sb.append('\n');
}
addIndent(sb, indent);
sb.append("]");
}

private static void normalizeObject(Node node, StringBuilder sb, int indent) {
sb.append("{\n");
Iterator<KeyValue> sortedValues =
stream(node.fields()).sorted(comparing(KeyValue::getKey)).iterator();
while (sortedValues.hasNext()) {
var keyValue = sortedValues.next();
addIndent(sb, indent + depth);
sb.append('"').append(keyValue.getKey()).append("\": ");
normalize(keyValue.getValue(), sb, indent + depth);
if (sortedValues.hasNext()) sb.append(",");
sb.append('\n');
}
addIndent(sb, indent);
sb.append("}");
}

private static void addIndent(StringBuilder sb, int indent) {
for (int i = 0; i < indent; i++) {
sb.append(' ');
}
}

private static <T> Stream<T> stream(Iterator<T> iterator) {
return StreamSupport.stream(Spliterators.spliteratorUnknownSize(iterator, 0), false);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import static net.javacrumbs.jsonunit.core.Option.IGNORING_EXTRA_ARRAY_ITEMS;
import static net.javacrumbs.jsonunit.core.Option.IGNORING_EXTRA_FIELDS;
import static net.javacrumbs.jsonunit.core.Option.IGNORING_VALUES;
import static net.javacrumbs.jsonunit.core.Option.REPORTING_DIFFERENCE_AS_NORMALIZED_STRING;
import static net.javacrumbs.jsonunit.core.Option.TREATING_NULL_AS_ABSENT;
import static net.javacrumbs.jsonunit.core.internal.JsonUtils.jsonSource;
import static net.javacrumbs.jsonunit.test.base.RegexBuilder.regex;
Expand All @@ -54,6 +55,7 @@
import net.javacrumbs.jsonunit.test.base.AbstractJsonAssertTest.DivisionMatcher;
import org.hamcrest.Matcher;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.opentest4j.AssertionFailedError;
import org.opentest4j.MultipleFailuresError;
Expand Down Expand Up @@ -2303,6 +2305,73 @@ void shouldFailFast() {
""");
}

@Nested
class ReportAsString {
@Test
void shouldSortMapKeys() {
assertThatThrownBy(() -> assertThatJson("{\"a\": {\"c\": [{\"e\": 2}, 3]}, \"b\": false}")
.when(REPORTING_DIFFERENCE_AS_NORMALIZED_STRING)
.isEqualTo("{\"b\": true, \"a\": {\"c\": [{\"e\": 3}, 5]}}"))
.hasMessage(
"""
JSON documents are different: expected <{
"a": {
"c": [
{
"e": 3
},
5
]
},
"b": true
}>but was <{
"a": {
"c": [
{
"e": 2
},
3
]
},
"b": false
}>""");
}

@Test
void shouldWorkWithMissingPath() {
assertThatThrownBy(() -> assertThatJson("{\"a\": 1}")
.when(REPORTING_DIFFERENCE_AS_NORMALIZED_STRING)
.inPath("c")
.isEqualTo("{\"b\": true}"))
.hasMessage(
"""
JSON documents are different:
Missing node in path "c".
""");
}

@Test
void shouldWorkWithPaths() {
assertThatThrownBy(() -> assertThatJson("{\"a\": {\"c\": [{\"e\": 2}, 3]}, \"b\": false}")
.when(REPORTING_DIFFERENCE_AS_NORMALIZED_STRING)
.inPath("a.c")
.isEqualTo("[{\"e\": 3}, 5]"))
.hasMessage(
"""
JSON documents are different: expected <[
{
"e": 3
},
5
]>but was <[
{
"e": 2
},
3
]>""");
}
}

private static final String json =
"""
{
Expand Down

0 comments on commit baeb5b6

Please sign in to comment.