001/*
002 * Copyright 2012-2018 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.boot.test.json;
018
019import java.io.File;
020import java.io.InputStream;
021import java.nio.charset.Charset;
022import java.util.List;
023import java.util.Map;
024
025import com.jayway.jsonpath.JsonPath;
026import org.assertj.core.api.AbstractAssert;
027import org.assertj.core.api.AbstractBooleanAssert;
028import org.assertj.core.api.AbstractCharSequenceAssert;
029import org.assertj.core.api.AbstractObjectAssert;
030import org.assertj.core.api.Assert;
031import org.assertj.core.api.Assertions;
032import org.assertj.core.api.ListAssert;
033import org.assertj.core.api.MapAssert;
034import org.skyscreamer.jsonassert.JSONCompare;
035import org.skyscreamer.jsonassert.JSONCompareMode;
036import org.skyscreamer.jsonassert.JSONCompareResult;
037import org.skyscreamer.jsonassert.comparator.JSONComparator;
038
039import org.springframework.core.io.Resource;
040import org.springframework.util.ObjectUtils;
041import org.springframework.util.StringUtils;
042
043/**
044 * AssertJ {@link Assert} for {@link JsonContent}.
045 *
046 * @author Phillip Webb
047 * @author Andy Wilkinson
048 * @since 1.4.0
049 */
050public class JsonContentAssert extends AbstractAssert<JsonContentAssert, CharSequence> {
051
052        private final JsonLoader loader;
053
054        /**
055         * Create a new {@link JsonContentAssert} instance that will load resources as UTF-8.
056         * @param resourceLoadClass the source class used to load resources
057         * @param json the actual JSON content
058         */
059        public JsonContentAssert(Class<?> resourceLoadClass, CharSequence json) {
060                this(resourceLoadClass, null, json);
061        }
062
063        /**
064         * Create a new {@link JsonContentAssert} instance that will load resources in the
065         * given {@code charset}.
066         * @param resourceLoadClass the source class used to load resources
067         * @param charset the charset of the JSON resources
068         * @param json the actual JSON content
069         * @since 1.4.1
070         */
071        public JsonContentAssert(Class<?> resourceLoadClass, Charset charset,
072                        CharSequence json) {
073                super(json, JsonContentAssert.class);
074                this.loader = new JsonLoader(resourceLoadClass, charset);
075        }
076
077        /**
078         * Overridden version of {@code isEqualTo} to perform JSON tests based on the object
079         * type.
080         * @see org.assertj.core.api.AbstractAssert#isEqualTo(java.lang.Object)
081         */
082        @Override
083        public JsonContentAssert isEqualTo(Object expected) {
084                if (expected == null || expected instanceof CharSequence) {
085                        return isEqualToJson((CharSequence) expected);
086                }
087                if (expected instanceof byte[]) {
088                        return isEqualToJson((byte[]) expected);
089                }
090                if (expected instanceof File) {
091                        return isEqualToJson((File) expected);
092                }
093                if (expected instanceof InputStream) {
094                        return isEqualToJson((InputStream) expected);
095                }
096                if (expected instanceof Resource) {
097                        return isEqualToJson((Resource) expected);
098                }
099                throw new AssertionError("Unsupport type for JSON assert " + expected.getClass());
100        }
101
102        /**
103         * Verifies that the actual value is {@link JSONCompareMode#LENIENT leniently} equal
104         * to the specified JSON. The {@code expected} value can contain the JSON itself or,
105         * if it ends with {@code .json}, the name of a resource to be loaded using
106         * {@code resourceLoadClass}.
107         * @param expected the expected JSON or the name of a resource containing the expected
108         * JSON
109         * @return {@code this} assertion object
110         * @throws AssertionError if the actual JSON value is not equal to the given one
111         */
112        public JsonContentAssert isEqualToJson(CharSequence expected) {
113                String expectedJson = this.loader.getJson(expected);
114                return assertNotFailed(compare(expectedJson, JSONCompareMode.LENIENT));
115        }
116
117        /**
118         * Verifies that the actual value is {@link JSONCompareMode#LENIENT leniently} equal
119         * to the specified JSON resource.
120         * @param path the name of a resource containing the expected JSON
121         * @param resourceLoadClass the source class used to load the resource
122         * @return {@code this} assertion object
123         * @throws AssertionError if the actual JSON value is not equal to the given one
124         */
125        public JsonContentAssert isEqualToJson(String path, Class<?> resourceLoadClass) {
126                String expectedJson = this.loader.getJson(path, resourceLoadClass);
127                return assertNotFailed(compare(expectedJson, JSONCompareMode.LENIENT));
128        }
129
130        /**
131         * Verifies that the actual value is {@link JSONCompareMode#LENIENT leniently} equal
132         * to the specified JSON bytes.
133         * @param expected the expected JSON bytes
134         * @return {@code this} assertion object
135         * @throws AssertionError if the actual JSON value is not equal to the given one
136         */
137        public JsonContentAssert isEqualToJson(byte[] expected) {
138                String expectedJson = this.loader.getJson(expected);
139                return assertNotFailed(compare(expectedJson, JSONCompareMode.LENIENT));
140        }
141
142        /**
143         * Verifies that the actual value is {@link JSONCompareMode#LENIENT leniently} equal
144         * to the specified JSON file.
145         * @param expected a file containing the expected JSON
146         * @return {@code this} assertion object
147         * @throws AssertionError if the actual JSON value is not equal to the given one
148         */
149        public JsonContentAssert isEqualToJson(File expected) {
150                String expectedJson = this.loader.getJson(expected);
151                return assertNotFailed(compare(expectedJson, JSONCompareMode.LENIENT));
152        }
153
154        /**
155         * Verifies that the actual value is {@link JSONCompareMode#LENIENT leniently} equal
156         * to the specified JSON input stream.
157         * @param expected an input stream containing the expected JSON
158         * @return {@code this} assertion object
159         * @throws AssertionError if the actual JSON value is not equal to the given one
160         */
161        public JsonContentAssert isEqualToJson(InputStream expected) {
162                String expectedJson = this.loader.getJson(expected);
163                return assertNotFailed(compare(expectedJson, JSONCompareMode.LENIENT));
164        }
165
166        /**
167         * Verifies that the actual value is {@link JSONCompareMode#LENIENT leniently} equal
168         * to the specified JSON resource.
169         * @param expected a resource containing the expected JSON
170         * @return {@code this} assertion object
171         * @throws AssertionError if the actual JSON value is not equal to the given one
172         */
173        public JsonContentAssert isEqualToJson(Resource expected) {
174                String expectedJson = this.loader.getJson(expected);
175                return assertNotFailed(compare(expectedJson, JSONCompareMode.LENIENT));
176        }
177
178        /**
179         * Verifies that the actual value is {@link JSONCompareMode#STRICT strictly} equal to
180         * the specified JSON. The {@code expected} value can contain the JSON itself or, if
181         * it ends with {@code .json}, the name of a resource to be loaded using
182         * {@code resourceLoadClass}.
183         * @param expected the expected JSON or the name of a resource containing the expected
184         * JSON
185         * @return {@code this} assertion object
186         * @throws AssertionError if the actual JSON value is not equal to the given one
187         */
188        public JsonContentAssert isStrictlyEqualToJson(CharSequence expected) {
189                String expectedJson = this.loader.getJson(expected);
190                return assertNotFailed(compare(expectedJson, JSONCompareMode.STRICT));
191        }
192
193        /**
194         * Verifies that the actual value is {@link JSONCompareMode#STRICT strictly} equal to
195         * the specified JSON resource.
196         * @param path the name of a resource containing the expected JSON
197         * @param resourceLoadClass the source class used to load the resource
198         * @return {@code this} assertion object
199         * @throws AssertionError if the actual JSON value is not equal to the given one
200         */
201        public JsonContentAssert isStrictlyEqualToJson(String path,
202                        Class<?> resourceLoadClass) {
203                String expectedJson = this.loader.getJson(path, resourceLoadClass);
204                return assertNotFailed(compare(expectedJson, JSONCompareMode.STRICT));
205        }
206
207        /**
208         * Verifies that the actual value is {@link JSONCompareMode#STRICT strictly} equal to
209         * the specified JSON bytes.
210         * @param expected the expected JSON bytes
211         * @return {@code this} assertion object
212         * @throws AssertionError if the actual JSON value is not equal to the given one
213         */
214        public JsonContentAssert isStrictlyEqualToJson(byte[] expected) {
215                return assertNotFailed(
216                                compare(this.loader.getJson(expected), JSONCompareMode.STRICT));
217        }
218
219        /**
220         * Verifies that the actual value is {@link JSONCompareMode#STRICT strictly} equal to
221         * the specified JSON file.
222         * @param expected a file containing the expected JSON
223         * @return {@code this} assertion object
224         * @throws AssertionError if the actual JSON value is not equal to the given one
225         */
226        public JsonContentAssert isStrictlyEqualToJson(File expected) {
227                String expectedJson = this.loader.getJson(expected);
228                return assertNotFailed(compare(expectedJson, JSONCompareMode.STRICT));
229        }
230
231        /**
232         * Verifies that the actual value is {@link JSONCompareMode#STRICT strictly} equal to
233         * the specified JSON input stream.
234         * @param expected an input stream containing the expected JSON
235         * @return {@code this} assertion object
236         * @throws AssertionError if the actual JSON value is not equal to the given one
237         */
238        public JsonContentAssert isStrictlyEqualToJson(InputStream expected) {
239                String expectedJson = this.loader.getJson(expected);
240                return assertNotFailed(compare(expectedJson, JSONCompareMode.STRICT));
241        }
242
243        /**
244         * Verifies that the actual value is {@link JSONCompareMode#STRICT strictly} equal to
245         * the specified JSON resource.
246         * @param expected a resource containing the expected JSON
247         * @return {@code this} assertion object
248         * @throws AssertionError if the actual JSON value is not equal to the given one
249         */
250        public JsonContentAssert isStrictlyEqualToJson(Resource expected) {
251                String expectedJson = this.loader.getJson(expected);
252                return assertNotFailed(compare(expectedJson, JSONCompareMode.STRICT));
253        }
254
255        /**
256         * Verifies that the actual value is equal to the specified JSON. The {@code expected}
257         * value can contain the JSON itself or, if it ends with {@code .json}, the name of a
258         * resource to be loaded using {@code resourceLoadClass}.
259         * @param expected the expected JSON or the name of a resource containing the expected
260         * JSON
261         * @param compareMode the compare mode used when checking
262         * @return {@code this} assertion object
263         * @throws AssertionError if the actual JSON value is not equal to the given one
264         */
265        public JsonContentAssert isEqualToJson(CharSequence expected,
266                        JSONCompareMode compareMode) {
267                String expectedJson = this.loader.getJson(expected);
268                return assertNotFailed(compare(expectedJson, compareMode));
269        }
270
271        /**
272         * Verifies that the actual value is equal to the specified JSON resource.
273         * @param path the name of a resource containing the expected JSON
274         * @param resourceLoadClass the source class used to load the resource
275         * @param compareMode the compare mode used when checking
276         * @return {@code this} assertion object
277         * @throws AssertionError if the actual JSON value is not equal to the given one
278         */
279        public JsonContentAssert isEqualToJson(String path, Class<?> resourceLoadClass,
280                        JSONCompareMode compareMode) {
281                String expectedJson = this.loader.getJson(path, resourceLoadClass);
282                return assertNotFailed(compare(expectedJson, compareMode));
283        }
284
285        /**
286         * Verifies that the actual value is equal to the specified JSON bytes.
287         * @param expected the expected JSON bytes
288         * @param compareMode the compare mode used when checking
289         * @return {@code this} assertion object
290         * @throws AssertionError if the actual JSON value is not equal to the given one
291         */
292        public JsonContentAssert isEqualToJson(byte[] expected, JSONCompareMode compareMode) {
293                String expectedJson = this.loader.getJson(expected);
294                return assertNotFailed(compare(expectedJson, compareMode));
295        }
296
297        /**
298         * Verifies that the actual value is equal to the specified JSON file.
299         * @param expected a file containing the expected JSON
300         * @param compareMode the compare mode used when checking
301         * @return {@code this} assertion object
302         * @throws AssertionError if the actual JSON value is not equal to the given one
303         */
304        public JsonContentAssert isEqualToJson(File expected, JSONCompareMode compareMode) {
305                String expectedJson = this.loader.getJson(expected);
306                return assertNotFailed(compare(expectedJson, compareMode));
307        }
308
309        /**
310         * Verifies that the actual value is equal to the specified JSON input stream.
311         * @param expected an input stream containing the expected JSON
312         * @param compareMode the compare mode used when checking
313         * @return {@code this} assertion object
314         * @throws AssertionError if the actual JSON value is not equal to the given one
315         */
316        public JsonContentAssert isEqualToJson(InputStream expected,
317                        JSONCompareMode compareMode) {
318                return assertNotFailed(compare(this.loader.getJson(expected), compareMode));
319        }
320
321        /**
322         * Verifies that the actual value is equal to the specified JSON resource.
323         * @param expected a resource containing the expected JSON
324         * @param compareMode the compare mode used when checking
325         * @return {@code this} assertion object
326         * @throws AssertionError if the actual JSON value is not equal to the given one
327         */
328        public JsonContentAssert isEqualToJson(Resource expected,
329                        JSONCompareMode compareMode) {
330                String expectedJson = this.loader.getJson(expected);
331                return assertNotFailed(compare(expectedJson, compareMode));
332        }
333
334        /**
335         * Verifies that the actual value is equal to the specified JSON. The {@code expected}
336         * value can contain the JSON itself or, if it ends with {@code .json}, the name of a
337         * resource to be loaded using {@code resourceLoadClass}.
338         * @param expected the expected JSON or the name of a resource containing the expected
339         * JSON
340         * @param comparator the comparator used when checking
341         * @return {@code this} assertion object
342         * @throws AssertionError if the actual JSON value is not equal to the given one
343         */
344        public JsonContentAssert isEqualToJson(CharSequence expected,
345                        JSONComparator comparator) {
346                String expectedJson = this.loader.getJson(expected);
347                return assertNotFailed(compare(expectedJson, comparator));
348        }
349
350        /**
351         * Verifies that the actual value is equal to the specified JSON resource.
352         * @param path the name of a resource containing the expected JSON
353         * @param resourceLoadClass the source class used to load the resource
354         * @param comparator the comparator used when checking
355         * @return {@code this} assertion object
356         * @throws AssertionError if the actual JSON value is not equal to the given one
357         */
358        public JsonContentAssert isEqualToJson(String path, Class<?> resourceLoadClass,
359                        JSONComparator comparator) {
360                String expectedJson = this.loader.getJson(path, resourceLoadClass);
361                return assertNotFailed(compare(expectedJson, comparator));
362        }
363
364        /**
365         * Verifies that the actual value is equal to the specified JSON bytes.
366         * @param expected the expected JSON bytes
367         * @param comparator the comparator used when checking
368         * @return {@code this} assertion object
369         * @throws AssertionError if the actual JSON value is not equal to the given one
370         */
371        public JsonContentAssert isEqualToJson(byte[] expected, JSONComparator comparator) {
372                String expectedJson = this.loader.getJson(expected);
373                return assertNotFailed(compare(expectedJson, comparator));
374        }
375
376        /**
377         * Verifies that the actual value is equal to the specified JSON file.
378         * @param expected a file containing the expected JSON
379         * @param comparator the comparator used when checking
380         * @return {@code this} assertion object
381         * @throws AssertionError if the actual JSON value is not equal to the given one
382         */
383        public JsonContentAssert isEqualToJson(File expected, JSONComparator comparator) {
384                String expectedJson = this.loader.getJson(expected);
385                return assertNotFailed(compare(expectedJson, comparator));
386        }
387
388        /**
389         * Verifies that the actual value is equal to the specified JSON input stream.
390         * @param expected an input stream containing the expected JSON
391         * @param comparator the comparator used when checking
392         * @return {@code this} assertion object
393         * @throws AssertionError if the actual JSON value is not equal to the given one
394         */
395        public JsonContentAssert isEqualToJson(InputStream expected,
396                        JSONComparator comparator) {
397                String expectedJson = this.loader.getJson(expected);
398                return assertNotFailed(compare(expectedJson, comparator));
399        }
400
401        /**
402         * Verifies that the actual value is equal to the specified JSON resource.
403         * @param expected a resource containing the expected JSON
404         * @param comparator the comparator used when checking
405         * @return {@code this} assertion object
406         * @throws AssertionError if the actual JSON value is not equal to the given one
407         */
408        public JsonContentAssert isEqualToJson(Resource expected, JSONComparator comparator) {
409                String expectedJson = this.loader.getJson(expected);
410                return assertNotFailed(compare(expectedJson, comparator));
411        }
412
413        /**
414         * Overridden version of {@code isNotEqualTo} to perform JSON tests based on the
415         * object type.
416         * @see org.assertj.core.api.AbstractAssert#isEqualTo(java.lang.Object)
417         */
418        @Override
419        public JsonContentAssert isNotEqualTo(Object expected) {
420                if (expected == null || expected instanceof CharSequence) {
421                        return isNotEqualToJson((CharSequence) expected);
422                }
423                if (expected instanceof byte[]) {
424                        return isNotEqualToJson((byte[]) expected);
425                }
426                if (expected instanceof File) {
427                        return isNotEqualToJson((File) expected);
428                }
429                if (expected instanceof InputStream) {
430                        return isNotEqualToJson((InputStream) expected);
431                }
432                if (expected instanceof Resource) {
433                        return isNotEqualToJson((Resource) expected);
434                }
435                throw new AssertionError("Unsupport type for JSON assert " + expected.getClass());
436        }
437
438        /**
439         * Verifies that the actual value is not {@link JSONCompareMode#LENIENT leniently}
440         * equal to the specified JSON. The {@code expected} value can contain the JSON itself
441         * or, if it ends with {@code .json}, the name of a resource to be loaded using
442         * {@code resourceLoadClass}.
443         * @param expected the expected JSON or the name of a resource containing the expected
444         * JSON
445         * @return {@code this} assertion object
446         * @throws AssertionError if the actual JSON value is equal to the given one
447         */
448        public JsonContentAssert isNotEqualToJson(CharSequence expected) {
449                String expectedJson = this.loader.getJson(expected);
450                return assertNotPassed(compare(expectedJson, JSONCompareMode.LENIENT));
451        }
452
453        /**
454         * Verifies that the actual value is not {@link JSONCompareMode#LENIENT leniently}
455         * equal to the specified JSON resource.
456         * @param path the name of a resource containing the expected JSON
457         * @param resourceLoadClass the source class used to load the resource
458         * @return {@code this} assertion object
459         * @throws AssertionError if the actual JSON value is equal to the given one
460         */
461        public JsonContentAssert isNotEqualToJson(String path, Class<?> resourceLoadClass) {
462                String expectedJson = this.loader.getJson(path, resourceLoadClass);
463                return assertNotPassed(compare(expectedJson, JSONCompareMode.LENIENT));
464        }
465
466        /**
467         * Verifies that the actual value is not {@link JSONCompareMode#LENIENT leniently}
468         * equal to the specified JSON bytes.
469         * @param expected the expected JSON bytes
470         * @return {@code this} assertion object
471         * @throws AssertionError if the actual JSON value is equal to the given one
472         */
473        public JsonContentAssert isNotEqualToJson(byte[] expected) {
474                String expectedJson = this.loader.getJson(expected);
475                return assertNotPassed(compare(expectedJson, JSONCompareMode.LENIENT));
476        }
477
478        /**
479         * Verifies that the actual value is not {@link JSONCompareMode#LENIENT leniently}
480         * equal to the specified JSON file.
481         * @param expected a file containing the expected JSON
482         * @return {@code this} assertion object
483         * @throws AssertionError if the actual JSON value is equal to the given one
484         */
485        public JsonContentAssert isNotEqualToJson(File expected) {
486                String expectedJson = this.loader.getJson(expected);
487                return assertNotPassed(compare(expectedJson, JSONCompareMode.LENIENT));
488        }
489
490        /**
491         * Verifies that the actual value is not {@link JSONCompareMode#LENIENT leniently}
492         * equal to the specified JSON input stream.
493         * @param expected an input stream containing the expected JSON
494         * @return {@code this} assertion object
495         * @throws AssertionError if the actual JSON value is equal to the given one
496         */
497        public JsonContentAssert isNotEqualToJson(InputStream expected) {
498                String expectedJson = this.loader.getJson(expected);
499                return assertNotPassed(compare(expectedJson, JSONCompareMode.LENIENT));
500        }
501
502        /**
503         * Verifies that the actual value is not {@link JSONCompareMode#LENIENT leniently}
504         * equal to the specified JSON resource.
505         * @param expected a resource containing the expected JSON
506         * @return {@code this} assertion object
507         * @throws AssertionError if the actual JSON value is equal to the given one
508         */
509        public JsonContentAssert isNotEqualToJson(Resource expected) {
510                return assertNotPassed(
511                                compare(this.loader.getJson(expected), JSONCompareMode.LENIENT));
512        }
513
514        /**
515         * Verifies that the actual value is not {@link JSONCompareMode#STRICT strictly} equal
516         * to the specified JSON. The {@code expected} value can contain the JSON itself or,
517         * if it ends with {@code .json}, the name of a resource to be loaded using
518         * {@code resourceLoadClass}.
519         * @param expected the expected JSON or the name of a resource containing the expected
520         * JSON
521         * @return {@code this} assertion object
522         * @throws AssertionError if the actual JSON value is equal to the given one
523         */
524        public JsonContentAssert isNotStrictlyEqualToJson(CharSequence expected) {
525                String expectedJson = this.loader.getJson(expected);
526                return assertNotPassed(compare(expectedJson, JSONCompareMode.STRICT));
527        }
528
529        /**
530         * Verifies that the actual value is not {@link JSONCompareMode#STRICT strictly} equal
531         * to the specified JSON resource.
532         * @param path the name of a resource containing the expected JSON
533         * @param resourceLoadClass the source class used to load the resource
534         * @return {@code this} assertion object
535         * @throws AssertionError if the actual JSON value is equal to the given one
536         */
537        public JsonContentAssert isNotStrictlyEqualToJson(String path,
538                        Class<?> resourceLoadClass) {
539                String expectedJson = this.loader.getJson(path, resourceLoadClass);
540                return assertNotPassed(compare(expectedJson, JSONCompareMode.STRICT));
541        }
542
543        /**
544         * Verifies that the actual value is not {@link JSONCompareMode#STRICT strictly} equal
545         * to the specified JSON bytes.
546         * @param expected the expected JSON bytes
547         * @return {@code this} assertion object
548         * @throws AssertionError if the actual JSON value is equal to the given one
549         */
550        public JsonContentAssert isNotStrictlyEqualToJson(byte[] expected) {
551                String expectedJson = this.loader.getJson(expected);
552                return assertNotPassed(compare(expectedJson, JSONCompareMode.STRICT));
553        }
554
555        /**
556         * Verifies that the actual value is not {@link JSONCompareMode#STRICT strictly} equal
557         * to the specified JSON file.
558         * @param expected a file containing the expected JSON
559         * @return {@code this} assertion object
560         * @throws AssertionError if the actual JSON value is equal to the given one
561         */
562        public JsonContentAssert isNotStrictlyEqualToJson(File expected) {
563                String expectedJson = this.loader.getJson(expected);
564                return assertNotPassed(compare(expectedJson, JSONCompareMode.STRICT));
565        }
566
567        /**
568         * Verifies that the actual value is not {@link JSONCompareMode#STRICT strictly} equal
569         * to the specified JSON input stream.
570         * @param expected an input stream containing the expected JSON
571         * @return {@code this} assertion object
572         * @throws AssertionError if the actual JSON value is equal to the given one
573         */
574        public JsonContentAssert isNotStrictlyEqualToJson(InputStream expected) {
575                String expectedJson = this.loader.getJson(expected);
576                return assertNotPassed(compare(expectedJson, JSONCompareMode.STRICT));
577        }
578
579        /**
580         * Verifies that the actual value is not {@link JSONCompareMode#STRICT strictly} equal
581         * to the specified JSON resource.
582         * @param expected a resource containing the expected JSON
583         * @return {@code this} assertion object
584         * @throws AssertionError if the actual JSON value is equal to the given one
585         */
586        public JsonContentAssert isNotStrictlyEqualToJson(Resource expected) {
587                String expectedJson = this.loader.getJson(expected);
588                return assertNotPassed(compare(expectedJson, JSONCompareMode.STRICT));
589        }
590
591        /**
592         * Verifies that the actual value is not equal to the specified JSON. The
593         * {@code expected} value can contain the JSON itself or, if it ends with
594         * {@code .json}, the name of a resource to be loaded using {@code resourceLoadClass}.
595         * @param expected the expected JSON or the name of a resource containing the expected
596         * JSON
597         * @param compareMode the compare mode used when checking
598         * @return {@code this} assertion object
599         * @throws AssertionError if the actual JSON value is equal to the given one
600         */
601        public JsonContentAssert isNotEqualToJson(CharSequence expected,
602                        JSONCompareMode compareMode) {
603                String expectedJson = this.loader.getJson(expected);
604                return assertNotPassed(compare(expectedJson, compareMode));
605        }
606
607        /**
608         * Verifies that the actual value is not equal to the specified JSON resource.
609         * @param path the name of a resource containing the expected JSON
610         * @param resourceLoadClass the source class used to load the resource
611         * @param compareMode the compare mode used when checking
612         * @return {@code this} assertion object
613         * @throws AssertionError if the actual JSON value is equal to the given one
614         */
615        public JsonContentAssert isNotEqualToJson(String path, Class<?> resourceLoadClass,
616                        JSONCompareMode compareMode) {
617                String expectedJson = this.loader.getJson(path, resourceLoadClass);
618                return assertNotPassed(compare(expectedJson, compareMode));
619        }
620
621        /**
622         * Verifies that the actual value is not equal to the specified JSON bytes.
623         * @param expected the expected JSON bytes
624         * @param compareMode the compare mode used when checking
625         * @return {@code this} assertion object
626         * @throws AssertionError if the actual JSON value is equal to the given one
627         */
628        public JsonContentAssert isNotEqualToJson(byte[] expected,
629                        JSONCompareMode compareMode) {
630                String expectedJson = this.loader.getJson(expected);
631                return assertNotPassed(compare(expectedJson, compareMode));
632        }
633
634        /**
635         * Verifies that the actual value is not equal to the specified JSON file.
636         * @param expected a file containing the expected JSON
637         * @param compareMode the compare mode used when checking
638         * @return {@code this} assertion object
639         * @throws AssertionError if the actual JSON value is equal to the given one
640         */
641        public JsonContentAssert isNotEqualToJson(File expected,
642                        JSONCompareMode compareMode) {
643                String expectedJson = this.loader.getJson(expected);
644                return assertNotPassed(compare(expectedJson, compareMode));
645        }
646
647        /**
648         * Verifies that the actual value is not equal to the specified JSON input stream.
649         * @param expected an input stream containing the expected JSON
650         * @param compareMode the compare mode used when checking
651         * @return {@code this} assertion object
652         * @throws AssertionError if the actual JSON value is equal to the given one
653         */
654        public JsonContentAssert isNotEqualToJson(InputStream expected,
655                        JSONCompareMode compareMode) {
656                String expectedJson = this.loader.getJson(expected);
657                return assertNotPassed(compare(expectedJson, compareMode));
658        }
659
660        /**
661         * Verifies that the actual value is not equal to the specified JSON resource.
662         * @param expected a resource containing the expected JSON
663         * @param compareMode the compare mode used when checking
664         * @return {@code this} assertion object
665         * @throws AssertionError if the actual JSON value is equal to the given one
666         */
667        public JsonContentAssert isNotEqualToJson(Resource expected,
668                        JSONCompareMode compareMode) {
669                String expectedJson = this.loader.getJson(expected);
670                return assertNotPassed(compare(expectedJson, compareMode));
671        }
672
673        /**
674         * Verifies that the actual value is not equal to the specified JSON. The
675         * {@code expected} value can contain the JSON itself or, if it ends with
676         * {@code .json}, the name of a resource to be loaded using {@code resourceLoadClass}.
677         * @param expected the expected JSON or the name of a resource containing the expected
678         * JSON
679         * @param comparator the comparator used when checking
680         * @return {@code this} assertion object
681         * @throws AssertionError if the actual JSON value is equal to the given one
682         */
683        public JsonContentAssert isNotEqualToJson(CharSequence expected,
684                        JSONComparator comparator) {
685                String expectedJson = this.loader.getJson(expected);
686                return assertNotPassed(compare(expectedJson, comparator));
687        }
688
689        /**
690         * Verifies that the actual value is not equal to the specified JSON resource.
691         * @param path the name of a resource containing the expected JSON
692         * @param resourceLoadClass the source class used to load the resource
693         * @param comparator the comparator used when checking
694         * @return {@code this} assertion object
695         * @throws AssertionError if the actual JSON value is equal to the given one
696         */
697        public JsonContentAssert isNotEqualToJson(String path, Class<?> resourceLoadClass,
698                        JSONComparator comparator) {
699                String expectedJson = this.loader.getJson(path, resourceLoadClass);
700                return assertNotPassed(compare(expectedJson, comparator));
701        }
702
703        /**
704         * Verifies that the actual value is not equal to the specified JSON bytes.
705         * @param expected the expected JSON bytes
706         * @param comparator the comparator used when checking
707         * @return {@code this} assertion object
708         * @throws AssertionError if the actual JSON value is equal to the given one
709         */
710        public JsonContentAssert isNotEqualToJson(byte[] expected,
711                        JSONComparator comparator) {
712                String expectedJson = this.loader.getJson(expected);
713                return assertNotPassed(compare(expectedJson, comparator));
714        }
715
716        /**
717         * Verifies that the actual value is not equal to the specified JSON file.
718         * @param expected a file containing the expected JSON
719         * @param comparator the comparator used when checking
720         * @return {@code this} assertion object
721         * @throws AssertionError if the actual JSON value is equal to the given one
722         */
723        public JsonContentAssert isNotEqualToJson(File expected, JSONComparator comparator) {
724                String expectedJson = this.loader.getJson(expected);
725                return assertNotPassed(compare(expectedJson, comparator));
726        }
727
728        /**
729         * Verifies that the actual value is not equal to the specified JSON input stream.
730         * @param expected an input stream containing the expected JSON
731         * @param comparator the comparator used when checking
732         * @return {@code this} assertion object
733         * @throws AssertionError if the actual JSON value is equal to the given one
734         */
735        public JsonContentAssert isNotEqualToJson(InputStream expected,
736                        JSONComparator comparator) {
737                String expectedJson = this.loader.getJson(expected);
738                return assertNotPassed(compare(expectedJson, comparator));
739        }
740
741        /**
742         * Verifies that the actual value is not equal to the specified JSON resource.
743         * @param expected a resource containing the expected JSON
744         * @param comparator the comparator used when checking
745         * @return {@code this} assertion object
746         * @throws AssertionError if the actual JSON value is equal to the given one
747         */
748        public JsonContentAssert isNotEqualToJson(Resource expected,
749                        JSONComparator comparator) {
750                String expectedJson = this.loader.getJson(expected);
751                return assertNotPassed(compare(expectedJson, comparator));
752        }
753
754        /**
755         * Verify that the actual value at the given JSON path produces a non-null result. If
756         * the JSON path expression is not {@linkplain JsonPath#isDefinite() definite}, this
757         * method verifies that the value at the given path is not <em>empty</em>.
758         * @param expression the {@link JsonPath} expression
759         * @param args arguments to parameterize the {@code JsonPath} expression with, using
760         * formatting specifiers defined in {@link String#format(String, Object...)}
761         * @return {@code this} assertion object
762         * @throws AssertionError if the value at the given path is missing
763         */
764        public JsonContentAssert hasJsonPathValue(CharSequence expression, Object... args) {
765                new JsonPathValue(expression, args).assertHasValue(Object.class, "an object");
766                return this;
767        }
768
769        /**
770         * Verify that the actual value at the given JSON path produces a non-null string
771         * result.
772         * @param expression the {@link JsonPath} expression
773         * @param args arguments to parameterize the {@code JsonPath} expression with, using
774         * formatting specifiers defined in {@link String#format(String, Object...)}
775         * @return {@code this} assertion object
776         * @throws AssertionError if the value at the given path is missing or not a string
777         */
778        public JsonContentAssert hasJsonPathStringValue(CharSequence expression,
779                        Object... args) {
780                new JsonPathValue(expression, args).assertHasValue(String.class, "a string");
781                return this;
782        }
783
784        /**
785         * Verify that the actual value at the given JSON path produces a non-null number
786         * result.
787         * @param expression the {@link JsonPath} expression
788         * @param args arguments to parameterize the {@code JsonPath} expression with, using
789         * formatting specifiers defined in {@link String#format(String, Object...)}
790         * @return {@code this} assertion object
791         * @throws AssertionError if the value at the given path is missing or not a number
792         */
793        public JsonContentAssert hasJsonPathNumberValue(CharSequence expression,
794                        Object... args) {
795                new JsonPathValue(expression, args).assertHasValue(Number.class, "a number");
796                return this;
797        }
798
799        /**
800         * Verify that the actual value at the given JSON path produces a non-null boolean
801         * result.
802         * @param expression the {@link JsonPath} expression
803         * @param args arguments to parameterize the {@code JsonPath} expression with, using
804         * formatting specifiers defined in {@link String#format(String, Object...)}
805         * @return {@code this} assertion object
806         * @throws AssertionError if the value at the given path is missing or not a boolean
807         */
808        public JsonContentAssert hasJsonPathBooleanValue(CharSequence expression,
809                        Object... args) {
810                new JsonPathValue(expression, args).assertHasValue(Boolean.class, "a boolean");
811                return this;
812        }
813
814        /**
815         * Verify that the actual value at the given JSON path produces a non-null array
816         * result.
817         * @param expression the {@link JsonPath} expression
818         * @param args arguments to parameterize the {@code JsonPath} expression with, using
819         * formatting specifiers defined in {@link String#format(String, Object...)}
820         * @return {@code this} assertion object
821         * @throws AssertionError if the value at the given path is missing or not an array
822         */
823        public JsonContentAssert hasJsonPathArrayValue(CharSequence expression,
824                        Object... args) {
825                new JsonPathValue(expression, args).assertHasValue(List.class, "an array");
826                return this;
827        }
828
829        /**
830         * Verify that the actual value at the given JSON path produces a non-null map result.
831         * @param expression the {@link JsonPath} expression
832         * @param args arguments to parameterize the {@code JsonPath} expression with, using
833         * formatting specifiers defined in {@link String#format(String, Object...)}
834         * @return {@code this} assertion object
835         * @throws AssertionError if the value at the given path is missing or not a map
836         */
837        public JsonContentAssert hasJsonPathMapValue(CharSequence expression,
838                        Object... args) {
839                new JsonPathValue(expression, args).assertHasValue(Map.class, "a map");
840                return this;
841        }
842
843        /**
844         * Verify that the actual value at the given JSON path produces an
845         * {@link ObjectUtils#isEmpty(Object) empty} result.
846         * @param expression the {@link JsonPath} expression
847         * @param args arguments to parameterize the {@code JsonPath} expression with, using
848         * formatting specifiers defined in {@link String#format(String, Object...)}
849         * @return {@code this} assertion object
850         * @throws AssertionError if the value at the given path is not empty
851         */
852        public JsonContentAssert hasEmptyJsonPathValue(CharSequence expression,
853                        Object... args) {
854                new JsonPathValue(expression, args).assertHasEmptyValue();
855                return this;
856        }
857
858        /**
859         * Verify that the actual value at the given JSON path produces no result. If the JSON
860         * path expression is not {@linkplain JsonPath#isDefinite() definite}, this method
861         * verifies that the value at the given path is <em>empty</em>.
862         * @param expression the {@link JsonPath} expression
863         * @param args arguments to parameterize the {@code JsonPath} expression with, using
864         * formatting specifiers defined in {@link String#format(String, Object...)}
865         * @return {@code this} assertion object
866         * @throws AssertionError if the value at the given path is not missing
867         */
868        public JsonContentAssert doesNotHaveJsonPathValue(CharSequence expression,
869                        Object... args) {
870                new JsonPathValue(expression, args).assertDoesNotHaveValue();
871                return this;
872        }
873
874        /**
875         * Verify that the actual value at the given JSON path does not produce an
876         * {@link ObjectUtils#isEmpty(Object) empty} result.
877         * @param expression the {@link JsonPath} expression
878         * @param args arguments to parameterize the {@code JsonPath} expression with, using
879         * formatting specifiers defined in {@link String#format(String, Object...)}
880         * @return {@code this} assertion object
881         * @throws AssertionError if the value at the given path is empty
882         */
883        public JsonContentAssert doesNotHaveEmptyJsonPathValue(CharSequence expression,
884                        Object... args) {
885                new JsonPathValue(expression, args).assertDoesNotHaveEmptyValue();
886                return this;
887        }
888
889        /**
890         * Extract the value at the given JSON path for further object assertions.
891         * @param expression the {@link JsonPath} expression
892         * @param args arguments to parameterize the {@code JsonPath} expression with, using
893         * formatting specifiers defined in {@link String#format(String, Object...)}
894         * @return a new assertion object whose object under test is the extracted item
895         * @throws AssertionError if the path is not valid
896         */
897        public AbstractObjectAssert<?, Object> extractingJsonPathValue(
898                        CharSequence expression, Object... args) {
899                return Assertions.assertThat(new JsonPathValue(expression, args).getValue(false));
900        }
901
902        /**
903         * Extract the string value at the given JSON path for further object assertions.
904         * @param expression the {@link JsonPath} expression
905         * @param args arguments to parameterize the {@code JsonPath} expression with, using
906         * formatting specifiers defined in {@link String#format(String, Object...)}
907         * @return a new assertion object whose object under test is the extracted item
908         * @throws AssertionError if the path is not valid or does not result in a string
909         */
910        public AbstractCharSequenceAssert<?, String> extractingJsonPathStringValue(
911                        CharSequence expression, Object... args) {
912                return Assertions.assertThat(
913                                extractingJsonPathValue(expression, args, String.class, "a string"));
914        }
915
916        /**
917         * Extract the number value at the given JSON path for further object assertions.
918         * @param expression the {@link JsonPath} expression
919         * @param args arguments to parameterize the {@code JsonPath} expression with, using
920         * formatting specifiers defined in {@link String#format(String, Object...)}
921         * @return a new assertion object whose object under test is the extracted item
922         * @throws AssertionError if the path is not valid or does not result in a number
923         */
924        public AbstractObjectAssert<?, Number> extractingJsonPathNumberValue(
925                        CharSequence expression, Object... args) {
926                return Assertions.assertThat(
927                                extractingJsonPathValue(expression, args, Number.class, "a number"));
928        }
929
930        /**
931         * Extract the boolean value at the given JSON path for further object assertions.
932         * @param expression the {@link JsonPath} expression
933         * @param args arguments to parameterize the {@code JsonPath} expression with, using
934         * formatting specifiers defined in {@link String#format(String, Object...)}
935         * @return a new assertion object whose object under test is the extracted item
936         * @throws AssertionError if the path is not valid or does not result in a boolean
937         */
938        public AbstractBooleanAssert<?> extractingJsonPathBooleanValue(
939                        CharSequence expression, Object... args) {
940                return Assertions.assertThat(
941                                extractingJsonPathValue(expression, args, Boolean.class, "a boolean"));
942        }
943
944        /**
945         * Extract the array value at the given JSON path for further object assertions.
946         * @param expression the {@link JsonPath} expression
947         * @param args arguments to parameterize the {@code JsonPath} expression with, using
948         * formatting specifiers defined in {@link String#format(String, Object...)}
949         * @param <E> element type
950         * @return a new assertion object whose object under test is the extracted item
951         * @throws AssertionError if the path is not valid or does not result in an array
952         */
953        @SuppressWarnings("unchecked")
954        public <E> ListAssert<E> extractingJsonPathArrayValue(CharSequence expression,
955                        Object... args) {
956                return Assertions.assertThat(
957                                extractingJsonPathValue(expression, args, List.class, "an array"));
958        }
959
960        /**
961         * Extract the map value at the given JSON path for further object assertions.
962         * @param expression the {@link JsonPath} expression
963         * @param args arguments to parameterize the {@code JsonPath} expression with, using
964         * formatting specifiers defined in {@link String#format(String, Object...)}
965         * @param <K> key type
966         * @param <V> value type
967         * @return a new assertion object whose object under test is the extracted item
968         * @throws AssertionError if the path is not valid or does not result in a map
969         */
970        @SuppressWarnings("unchecked")
971        public <K, V> MapAssert<K, V> extractingJsonPathMapValue(CharSequence expression,
972                        Object... args) {
973                return Assertions.assertThat(
974                                extractingJsonPathValue(expression, args, Map.class, "a map"));
975        }
976
977        @SuppressWarnings("unchecked")
978        private <T> T extractingJsonPathValue(CharSequence expression, Object[] args,
979                        Class<T> type, String expectedDescription) {
980                JsonPathValue value = new JsonPathValue(expression, args);
981                if (value.getValue(false) != null) {
982                        value.assertHasValue(type, expectedDescription);
983                }
984                return (T) value.getValue(false);
985        }
986
987        private JSONCompareResult compare(CharSequence expectedJson,
988                        JSONCompareMode compareMode) {
989                if (this.actual == null) {
990                        return compareForNull(expectedJson);
991                }
992                try {
993                        return JSONCompare.compareJSON(
994                                        (expectedJson != null) ? expectedJson.toString() : null,
995                                        this.actual.toString(), compareMode);
996                }
997                catch (Exception ex) {
998                        if (ex instanceof RuntimeException) {
999                                throw (RuntimeException) ex;
1000                        }
1001                        throw new IllegalStateException(ex);
1002                }
1003        }
1004
1005        private JSONCompareResult compare(CharSequence expectedJson,
1006                        JSONComparator comparator) {
1007                if (this.actual == null) {
1008                        return compareForNull(expectedJson);
1009                }
1010                try {
1011                        return JSONCompare.compareJSON(
1012                                        (expectedJson != null) ? expectedJson.toString() : null,
1013                                        this.actual.toString(), comparator);
1014                }
1015                catch (Exception ex) {
1016                        if (ex instanceof RuntimeException) {
1017                                throw (RuntimeException) ex;
1018                        }
1019                        throw new IllegalStateException(ex);
1020                }
1021        }
1022
1023        private JSONCompareResult compareForNull(CharSequence expectedJson) {
1024                JSONCompareResult result = new JSONCompareResult();
1025                result.passed();
1026                if (expectedJson != null) {
1027                        result.fail("Expected null JSON");
1028                }
1029                return result;
1030        }
1031
1032        private JsonContentAssert assertNotFailed(JSONCompareResult result) {
1033                if (result.failed()) {
1034                        throw new AssertionError("JSON Comparison failure: " + result.getMessage());
1035                }
1036                return this;
1037        }
1038
1039        private JsonContentAssert assertNotPassed(JSONCompareResult result) {
1040                if (result.passed()) {
1041                        throw new AssertionError("JSON Comparison failure: " + result.getMessage());
1042                }
1043                return this;
1044        }
1045
1046        /**
1047         * A {@link JsonPath} value.
1048         */
1049        private class JsonPathValue {
1050
1051                private final String expression;
1052
1053                private final JsonPath jsonPath;
1054
1055                JsonPathValue(CharSequence expression, Object... args) {
1056                        org.springframework.util.Assert.hasText(
1057                                        (expression != null) ? expression.toString() : null,
1058                                        "expression must not be null or empty");
1059                        this.expression = String.format(expression.toString(), args);
1060                        this.jsonPath = JsonPath.compile(this.expression);
1061                }
1062
1063                public void assertHasEmptyValue() {
1064                        if (ObjectUtils.isEmpty(getValue(false)) || isIndefiniteAndEmpty()) {
1065                                return;
1066                        }
1067                        throw new AssertionError(getExpectedValueMessage("an empty value"));
1068                }
1069
1070                public void assertDoesNotHaveEmptyValue() {
1071                        if (!ObjectUtils.isEmpty(getValue(false))) {
1072                                return;
1073                        }
1074                        throw new AssertionError(getExpectedValueMessage("a non-empty value"));
1075
1076                }
1077
1078                public void assertHasValue(Class<?> type, String expectedDescription) {
1079                        Object value = getValue(true);
1080                        if (value == null || isIndefiniteAndEmpty()) {
1081                                throw new AssertionError(getNoValueMessage());
1082                        }
1083                        if (type != null && !type.isInstance(value)) {
1084                                throw new AssertionError(getExpectedValueMessage(expectedDescription));
1085                        }
1086                }
1087
1088                public void assertDoesNotHaveValue() {
1089                        if (getValue(false) == null || isIndefiniteAndEmpty()) {
1090                                return;
1091                        }
1092                        throw new AssertionError(getExpectedValueMessage("no value"));
1093                }
1094
1095                private boolean isIndefiniteAndEmpty() {
1096                        return !isDefinite() && isEmpty();
1097                }
1098
1099                private boolean isDefinite() {
1100                        return this.jsonPath.isDefinite();
1101                }
1102
1103                private boolean isEmpty() {
1104                        return ObjectUtils.isEmpty(getValue(false));
1105                }
1106
1107                public Object getValue(boolean required) {
1108                        try {
1109                                CharSequence json = JsonContentAssert.this.actual;
1110                                return this.jsonPath.read((json != null) ? json.toString() : null);
1111                        }
1112                        catch (Exception ex) {
1113                                if (!required) {
1114                                        return null;
1115                                }
1116                                throw new AssertionError(getNoValueMessage() + ". " + ex.getMessage());
1117                        }
1118                }
1119
1120                private String getNoValueMessage() {
1121                        return "No value at JSON path \"" + this.expression + "\"";
1122                }
1123
1124                private String getExpectedValueMessage(String expectedDescription) {
1125                        return String.format("Expected %s at JSON path \"%s\" but found: %s",
1126                                        expectedDescription, this.expression, ObjectUtils.nullSafeToString(
1127                                                        StringUtils.quoteIfString(getValue(false))));
1128                }
1129
1130        }
1131
1132}