-
Notifications
You must be signed in to change notification settings - Fork 10
Expand file tree
/
Copy pathLenientJsonArrayPartialMatcher.java
More file actions
145 lines (124 loc) · 6.26 KB
/
LenientJsonArrayPartialMatcher.java
File metadata and controls
145 lines (124 loc) · 6.26 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
package com.deblock.jsondiff.matcher;
import com.deblock.jsondiff.diff.JsonArrayDiff;
import com.deblock.jsondiff.diff.JsonDiff;
import tools.jackson.databind.JsonNode;
import tools.jackson.databind.node.ArrayNode;
import java.util.*;
import java.util.stream.Collectors;
public class LenientJsonArrayPartialMatcher implements PartialJsonMatcher<ArrayNode> {
@Override
public JsonDiff jsonDiff(Path path, ArrayNode expectedArrayNode, ArrayNode recievedArrayNode, JsonMatcher jsonMatcher) {
final var diff = new JsonArrayDiff(path);
var mismatches = processMatchingArrayNodesAndReportMismatches(expectedArrayNode, recievedArrayNode, diff, path, jsonMatcher);
if (!mismatches.expectedMissing.isEmpty() || !mismatches.actualMissing.isEmpty()) {
final var diffMap = new HashMap<Integer, Map<Integer, JsonDiff>>();
for (var expectedMissing: mismatches.expectedMissing) {
final var map = new HashMap<Integer, JsonDiff>();
for (var actualMissing: mismatches.actualMissing) {
map.put(
actualMissing.index,
jsonMatcher.diff(
path.add(Path.PathItem.of(expectedMissing.index)), expectedMissing.jsonNode, actualMissing.jsonNode
)
);
}
diffMap.put(expectedMissing.index, map);
}
final var entrySortedByBestMatch =
diffMap.entrySet().stream()
.sorted(Comparator.comparingDouble(this::maxSimilarityRate).reversed())
.toList();
final var alreadyMatchedIndex = new HashSet<Integer>();
for (final var entry : entrySortedByBestMatch) {
final var matchedItem = entry.getValue().entrySet().stream()
.filter(e -> !alreadyMatchedIndex.contains(e.getKey()))
.max(Comparator.comparingDouble(e -> e.getValue().similarityRate()));
if (matchedItem.isEmpty()) {
diff.addNoMatch(entry.getKey(), expectedArrayNode.get(entry.getKey()));
} else {
diff.addDiff(entry.getKey(), matchedItem.get().getValue());
alreadyMatchedIndex.add(matchedItem.get().getKey());
}
}
if (alreadyMatchedIndex.size() < recievedArrayNode.size()) {
final var receivedIndex = mismatches.actualMissing.stream().map(it -> it.index).collect(Collectors.toList());
receivedIndex.removeAll(alreadyMatchedIndex);
receivedIndex.forEach(index -> diff.addExtraItem(index, recievedArrayNode.get(index)));
}
}
return diff;
}
@Override
public boolean manage(Path path, JsonNode received, JsonNode expected) {
return expected.isArray() && received.isArray();
}
private double maxSimilarityRate(Map.Entry<Integer, Map<Integer, JsonDiff>> entry) {
return entry.getValue().values().stream().mapToDouble(JsonDiff::similarityRate).max().orElse(0);
}
private MismatchPair<List<IndexedJsonNode>, List<IndexedJsonNode>> processMatchingArrayNodesAndReportMismatches(ArrayNode expectedArrayNode, ArrayNode actualArrayNode, JsonArrayDiff diff, Path path, JsonMatcher jsonMatcher) {
if (actualArrayNode.equals(expectedArrayNode)) {
for (int i = 0; i < expectedArrayNode.size(); i++) {
diff.addDiff(i, jsonMatcher.diff(path.add(Path.PathItem.of(i)), expectedArrayNode.get(i), expectedArrayNode.get(i)));
}
return new MismatchPair<>(List.of(), List.of());
}
List<IndexedJsonNode> expectedMissing = new ArrayList<>();
List<IndexedJsonNode> actualMissing = new ArrayList<>();
NodeCounter expectedNodeCounter = getElementsWithCount(expectedArrayNode.elements());
NodeCounter actualNodeCounter = getElementsWithCount(actualArrayNode.elements());
for (int i = 0; i < expectedArrayNode.size(); i++) {
var expectedElement = expectedArrayNode.get(i);
if (actualNodeCounter.containsNode(expectedElement)) {
actualNodeCounter.removeNode(expectedElement);
diff.addDiff(i, jsonMatcher.diff(path.add(Path.PathItem.of(i)), expectedElement, expectedElement));
} else {
expectedMissing.add(new IndexedJsonNode(i, expectedArrayNode.get(i)));
}
}
for (int i = 0; i < actualArrayNode.size(); i++) {
var actualElement = actualArrayNode.get(i);
if (expectedNodeCounter.containsNode(actualElement)) {
expectedNodeCounter.removeNode(actualElement);
} else {
actualMissing.add(new IndexedJsonNode(i, actualArrayNode.get(i)));
}
}
return new MismatchPair<>(expectedMissing, actualMissing);
}
private NodeCounter getElementsWithCount(Collection<JsonNode> elements) {
var nodeCounter = new NodeCounter();
elements.forEach(nodeCounter::addNode);
return nodeCounter;
}
private static class MismatchPair<K, V> {
private final K expectedMissing;
private final V actualMissing;
public MismatchPair(K expectedMissing, V actualMissing) {
this.expectedMissing = expectedMissing;
this.actualMissing = actualMissing;
}
}
private static class IndexedJsonNode {
private final int index;
private final JsonNode jsonNode;
public IndexedJsonNode(int index, JsonNode jsonNode) {
this.index = index;
this.jsonNode = jsonNode;
}
}
private static class NodeCounter {
private Map<JsonNode, Integer> nodeCounter = new HashMap<>();
public void addNode(JsonNode node) {
nodeCounter.compute(node, (key, prevValue) -> (prevValue == null ? 0 : prevValue) + 1);
}
public void removeNode(JsonNode node) {
nodeCounter.put(node, nodeCounter.get(node) - 1);
if (nodeCounter.get(node) == 0) {
nodeCounter.remove(node);
}
}
public boolean containsNode(JsonNode node) {
return nodeCounter.containsKey(node);
}
}
}