forked from skyscreamer/JSONassert
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathArrayValueMatcher.java
More file actions
197 lines (188 loc) · 9.32 KB
/
ArrayValueMatcher.java
File metadata and controls
197 lines (188 loc) · 9.32 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
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
package org.skyscreamer.jsonassert;
import java.text.MessageFormat;
import org.json.JSONArray;
import org.json.JSONException;
import org.skyscreamer.jsonassert.comparator.JSONComparator;
/**
* <p>A value matcher for arrays. This operates like STRICT_ORDER array match,
* however if expected array has less elements than actual array the matching
* process loops through the expected array to get expected elements for the
* additional actual elements. In general the expected array will contain a
* single element which is matched against each actual array element in turn.
* This allows simple verification of constant array element components and
* coupled with RegularExpressionValueMatcher can be used to match specific
* array element components against a regular expression pattern. As a convenience to reduce syntactic complexity of expected string, if the
* expected object is not an array, a one element expected array is created
* containing whatever is provided as the expected value.</p>
*
* <p>Some examples of typical usage idioms listed below.</p>
*
* <p>Assuming JSON to be verified is held in String variable ARRAY_OF_JSONOBJECTS and contains:</p>
*
* <code>{a:[{background:white,id:1,type:row}, {background:grey,id:2,type:row}, {background:white,id:3,type:row}, {background:grey,id:4,type:row}]}</code>
*
* <p>then:</p>
*
* <p>To verify that the 'id' attribute of first element of array 'a' is '1':</p>
*
* <code>
* JSONComparator comparator = new DefaultComparator(JSONCompareMode.LENIENT);<br/>
* Customization customization = new Customization("a", new ArrayValueMatcher<Object>(comparator, 0));<br/>
* JSONAssert.assertEquals("{a:[{id:1}]}", ARRAY_OF_JSONOBJECTS, new CustomComparator(JSONCompareMode.LENIENT, customization));
* </code>
*
* <p>To simplify complexity of expected JSON string, the value <code>"a:[{id:1}]}"</code> may be replaced by <code>"a:{id:1}}"</code></p>
*
* <p>To verify that the 'type' attribute of second and third elements of array 'a' is 'row':</p>
*
* <code>
* JSONComparator comparator = new DefaultComparator(JSONCompareMode.LENIENT);<br/>
* Customization customization = new Customization("a", new ArrayValueMatcher<Object>(comparator, 1, 2));<br/>
* JSONAssert.assertEquals("{a:[{type:row}]}", ARRAY_OF_JSONOBJECTS, new CustomComparator(JSONCompareMode.LENIENT, customization));
* </code>
*
* <p>To verify that the 'type' attribute of every element of array 'a' is 'row':</p>
*
* <code>
* JSONComparator comparator = new DefaultComparator(JSONCompareMode.LENIENT);<br/>
* Customization customization = new Customization("a", new ArrayValueMatcher<Object>(comparator));<br/>
* JSONAssert.assertEquals("{a:[{type:row}]}", ARRAY_OF_JSONOBJECTS, new CustomComparator(JSONCompareMode.LENIENT, customization));
* </code>
*
* <p>To verify that the 'id' attribute of every element of array 'a' matches regular expression '\d+'. This requires a custom comparator to specify regular expression to be used to validate each array element, hence the array of Customization instances:</p>
*
* <code>
* // get length of array we will verify<br/>
* int aLength = ((JSONArray)((JSONObject)JSONParser.parseJSON(ARRAY_OF_JSONOBJECTS)).get("a")).length();<br/>
* // create array of customizations one for each array element<br/>
* RegularExpressionValueMatcher<Object> regExValueMatcher = new RegularExpressionValueMatcher<Object>("\\d+"); // matches one or more digits<br/>
* Customization[] customizations = new Customization[aLength];<br/>
* for (int i=0; i<aLength; i++) {<br/>
* String contextPath = "a["+i+"].id";<br/>
* customizations[i] = new Customization(contextPath, regExValueMatcher);<br/>
* }<br/>
* CustomComparator regExComparator = new CustomComparator(JSONCompareMode.STRICT_ORDER, customizations);<br/>
* ArrayValueMatcher<Object> regExArrayValueMatcher = new ArrayValueMatcher<Object>(regExComparator);<br/>
* Customization regExArrayValueCustomization = new Customization("a", regExArrayValueMatcher);<br/>
* CustomComparator regExCustomArrayValueComparator = new CustomComparator(JSONCompareMode.STRICT_ORDER, new Customization[] { regExArrayValueCustomization });<br/>
* JSONAssert.assertEquals("{a:[{id:X}]}", ARRAY_OF_JSONOBJECTS, regExCustomArrayValueComparator);<br/>
* </code>
*
* <p>To verify that the 'background' attribute of every element of array 'a' alternates between 'white' and 'grey' starting with first element 'background' being 'white':</p>
*
* <code>
* JSONComparator comparator = new DefaultComparator(JSONCompareMode.LENIENT);<br/>
* Customization customization = new Customization("a", new ArrayValueMatcher<Object>(comparator));<br/>
* JSONAssert.assertEquals("{a:[{background:white},{background:grey}]}", ARRAY_OF_JSONOBJECTS, new CustomComparator(JSONCompareMode.LENIENT, customization));
* </code>
*
* <p>Assuming JSON to be verified is held in String variable ARRAY_OF_JSONARRAYS and contains:</p>
*
* <code>{a:[[6,7,8], [9,10,11], [12,13,14], [19,20,21,22]]}</code>
*
* <p>then:</p>
*
* <p>To verify that the first three elements of JSON array 'a' are JSON arrays of length 3:</p>
*
* <code>
* JSONComparator comparator = new ArraySizeComparator(JSONCompareMode.STRICT_ORDER);<br/>
* Customization customization = new Customization("a", new ArrayValueMatcher<Object>(comparator, 0, 2));<br/>
* JSONAssert.assertEquals("{a:[[3]]}", ARRAY_OF_JSONARRAYS, new CustomComparator(JSONCompareMode.LENIENT, customization));
* </code>
*
* <p>NOTE: simplified expected JSON strings are not possible in this case as ArraySizeComparator does not support them.</p>
*
* <p>To verify that the second elements of JSON array 'a' is a JSON array whose first element has the value 9:</p>
*
* <code>
* JSONComparator innerComparator = new DefaultComparator(JSONCompareMode.LENIENT);<br/>
* Customization innerCustomization = new Customization("a[1]", new ArrayValueMatcher<Object>(innerComparator, 0));<br/>
* JSONComparator comparator = new CustomComparator(JSONCompareMode.LENIENT, innerCustomization);<br/>
* Customization customization = new Customization("a", new ArrayValueMatcher<Object>(comparator, 1));<br/>
* JSONAssert.assertEquals("{a:[[9]]}", ARRAY_OF_JSONARRAYS, new CustomComparator(JSONCompareMode.LENIENT, customization));
* </code>
*
* <p>To simplify complexity of expected JSON string, the value <code>"{a:[[9]]}"</code> may be replaced by <code>"{a:[9]}"</code> or <code>"{a:9}"</code></p>
*
* @author Duncan Mackinder
*
*/
public class ArrayValueMatcher<T> implements LocationAwareValueMatcher<T> {
private final JSONComparator comparator;
private final int from;
private final int to;
/**
* Create ArrayValueMatcher to match every element in actual array against
* elements taken in sequence from expected array, repeating from start of
* expected array if necessary.
*
* @param comparator
* comparator to use to compare elements
*/
public ArrayValueMatcher(JSONComparator comparator) {
this(comparator, 0, Integer.MAX_VALUE);
}
/**
* Create ArrayValueMatcher to match specified element in actual array
* against first element of expected array.
*
* @param comparator
* comparator to use to compare elements
* @param index
* index of the array element to be compared
*/
public ArrayValueMatcher(JSONComparator comparator, int index) {
this(comparator, index, index);
}
/**
* Create ArrayValueMatcher to match every element in specified range
* (inclusive) from actual array against elements taken in sequence from
* expected array, repeating from start of expected array if necessary.
*
* @param comparator
* comparator to use to compare elements
* @from first element in actual array to compared
* @to last element in actual array to compared
*/
public ArrayValueMatcher(JSONComparator comparator, int from, int to) {
assert comparator != null : "comparator null";
assert from >= 0 : MessageFormat.format("from({0}) < 0", from);
assert to >= from : MessageFormat.format("to({0}) < from({1})", to,
from);
this.comparator = comparator;
this.from = from;
this.to = to;
}
@Override
/*
* NOTE: method defined as required by ValueMatcher interface but will never
* be called so defined simply to indicate match failure
*/
public boolean equal(T o1, T o2) {
return false;
}
@Override
public boolean equal(String prefix, T actual, T expected, JSONCompareResult result) {
if (!(actual instanceof JSONArray)) {
throw new IllegalArgumentException("ArrayValueMatcher applied to non-array actual value");
}
try {
JSONArray actualArray = (JSONArray) actual;
JSONArray expectedArray = expected instanceof JSONArray ? (JSONArray) expected: new JSONArray(new Object[] { expected });
int first = Math.max(0, from);
int last = Math.min(actualArray.length() - 1, to);
int expectedLen = expectedArray.length();
for (int i = first; i <= last; i++) {
String elementPrefix = MessageFormat.format("{0}[{1}]", prefix, i);
Object actualElement = actualArray.get(i);
Object expectedElement = expectedArray.get((i - first) % expectedLen);
comparator.compareValues(elementPrefix, expectedElement, actualElement, result);
}
// any failures have already been passed to result, so return true
return true;
}
catch (JSONException e) {
return false;
}
}
}