Skip to content

Checking JSON

Vladimir Turov edited this page Feb 20, 2021 · 2 revisions

About

The testing library provides a powerful tool to test JSON objects based on static expect method described in the Presentation error section. To test JSON, you need to write expect(string).asJson().check(template). After checking you are guaranteed to have correct JSON because otherwise testing library will throw Wrong answer automatically.

Creating templates

All the static methods you need are located in org.hyperskill.hstest.testing.expect.json.JsonChecker class. It is recommended to statically import all you need from there. If you want to use regular expression patterns to check string values, you may want to statically import method compile as well.

import static java.util.regex.Pattern.compile;
import static org.hyperskill.hstest.testing.expect.json.JsonChecker.*;

You can create templates with the following methods:

  1. any() matches any object, array, number, or null.
  2. isObject(...) matches JSON object
  3. isArray(...) matches JSON array
  4. isString(...) matches JSON string
  5. isNumber(...) matches JSON number
  6. isInteger(...) matches JSON number that can be represented as an integer
  7. isDouble(...) matches JSON number that can be represented as a double
  8. isBoolean(...) matches JSON bool
  9. isNull() matches JSON null

All the methods return objects that inherit JsonBaseBuilder so you can use them to check objects' values and arrays' items.

Building template for an object

isObject(...) returns JsonObjectBuilder and can be configured using .value(...) methods described below.

Available methods to create object template:

  1. isObject() without configuration matches an empty object
  2. isObject(JsonAnyBuilder unused) is only used to write isObject(any()) which matches an object with any key-value pairs. Cannot be configured.

Available methods to configure object template:

  1. .value(String key, int value) matches object that contains key key with value value
  2. .value(String key, double value) same as 1
  3. .value(String key, boolean value) same as 1
  4. .value(String key, String value) same as 1
  5. .value(String key, Pattern valueRegex) matches object that contains key key with string value that matches pattern valueRegex
  6. .value(String key, int... values) matches object that contains key key with value as an array of values
  7. .value(String key, double... value) same as 6
  8. .value(String key, boolean... value) same as 6
  9. .value(String key, String... value) same as 6
  10. .value(Pattern keyRegex, int value) matches object that contains key that matches pattern keyRegex with value value
  11. .value(Pattern keyRegex, double value) same as 10
  12. .value(Pattern keyRegex, boolean value) same as 10
  13. .value(Pattern keyRegex, String value) same as 10
  14. .value(Pattern keyRegex, Pattern valueRegex) matches object that contains key that matches pattern keyRegexwith string value that matches pattern valueRegex
  15. .value(Pattern keyRegex, int... values) matches object that contains key that matches pattern keyRegex with value as an array of values
  16. .value(Pattern keyRegex, double... value) same as 15
  17. .value(Pattern keyRegex, boolean... value) same as 15
  18. .value(Pattern keyRegex, String... value) same as 15
  19. .value(StringChecker key, int value) matches object that contains key that matches checker key with value value
  20. .value(StringChecker key, double value) same as 19
  21. .value(StringChecker key, boolean value) same as 19
  22. .value(StringChecker key, String value) same as 19
  23. .value(StringChecker key, Pattern valueRegex) matches object that contains key that matches checker key with value that matches pattern valueRegex
  24. .value(StringChecker key, int... values) matches object that contains key that matches checker key with value as an array of values
  25. .value(StringChecker key, double... values) same as 24
  26. .value(StringChecker key, boolean... values) same as 24
  27. .value(StringChecker key, String... values) same as 24
  28. .value(String key, JsonBaseBuilder value) matches object that contains key key with value that matches template value
  29. .value(Pattern regex, JsonBaseBuilder value) matches object that contains key that matches pattern keyRegex with value that matches template value
  30. .value(StringChecker key, JsonBaseBuilder value) matches object that contains key that matches checker key with value that matches template value

StringChecker is a functional interface that takes a String key and returns a boolean. If it returns true, the match is considered successful. For example, checker key -> key.length() < 10 would match a key that has a length fewer than 10 characters.

To test the following JSON object:

{
    "date": "2019-10-11",
    "time": "20:11",
    "cost": 12.32,
    "pic": "qwe.png",
    "all_years": [2011, 2013, 2014, 2020]
}

You can use the following template:

isObject()
    .value("date", compile("\\d{4}-\\d{2}-\\d{2}"))
    .value("time", compile("\\d{2}:\\d{2}"))
    .value("cost", isNumber())
    .value(compile("pic|img"), isString(s -> s.endsWith(".png"))
    .value(key -> key.endsWith("years"), 2011, 2013, 2014, 2020)

Building template for an array

isArray(...) returns JsonArrayBuilder and can be configured using .item(...) methods described below.

Available methods to create array template:

  1. isArray() without configuration matches an empty array
  2. isArray(int length) matches an array of length elements
  3. isArray(JsonBaseBuilder itemsTemplate) matches an array whose elements match itemsTemplate. Example: isArray(isInteger()) matches an array of any integers, isArray(any()) matches any array.
  4. isArray(int length, JsonBaseBuilder itemsTemplate) matches an array of length elements and all of them should match itemsTemplate
  5. isArray(int... values) matches an array of values
  6. isArray(double... values) same as 5
  7. isArray(boolean... values) same as 5
  8. isArray(String... values) same as 5
  9. isArray(ArrayLengthChecker lengthChecker) matches an array whose length matches lengthChecker
  10. isArray(ArrayLengthChecker lengthChecker, JsonBaseBuilder itemsTemplate) matches an array whose length matches lengthChecker and items match itemsTemplate template

ArrayLengthChecker is a functional interface that takes an integer key and returns a boolean. If it returns true, the match is considered successful. For example, checker len -> len < 10 would match an array with less than 10 elements.

Available methods to configure array template:

  1. .item(int value) matches array whose next element is equal to value
  2. .item(double value) same as 1
  3. .item(boolean value) same as 1
  4. .item(String value) same as 1
  5. .item(Pattern regex) matches array whose next element is a string that matches pattern regex.
  6. .item(JsonBaseBuilder value) matches array whose next element matches template value. Example: .item(isInteger()) matches any integer item, .item(any()) matches any item.
  7. .anyOtherItems() in case you allow any other values in a JSON object besides those that you have already configured. After calling this method you can't configure other values so this method should be last to call.

To test the following JSON array:

[
    {
        "num": 12,
        "why": "flew high"
    },
    {
        "number": 23,
        "reason": "flew fast"
    },
    45,
    null,
    false
]

You can use the following template:

isArray()
    .item(isObject()
        .value("num", isInteger(i -> i == 12 || i == 13))
        .value(compile("..y"), "flew high"))
    .item(isObject()
        .value("number", 23)
        .value("reason", "flew fast"))
    .item(45)
    .item(isNull())
    .anyOtherItems())

Checking strings

isString(...) returns `JsonStringBuilder.

Available methods to create string template:

  1. isString() matches any string
  2. isString(String value) matches string that is equal to value
  3. isString(Pattern regex) matches string that matches pattern regex
  4. isString(StringChecker checker) matched string that matches checker

StringChecker is a functional interface that takes a String and returns a boolean. If it returns true, the match is considered successful. For example, checker str -> str.length() < 10 would match a string whose length is less than 10.

Checking numbers

isNumber(...) returns JsonNumberBuilder or JsonIntegerBuilder or JsonDoubleBuilder based on arguments passed to it.

isInteger(...) returns JsonIntegerBuilder

isDouble(...) returns JsonDoubleBuilder

Available methods to create number template:

  1. isNumber() matches any number, integer or double
  2. isNumber(int value) matches integer value
  3. isNumber(double value) matches double value
  4. isInteger() matches any integer
  5. isInteger(int value) same as 2
  6. isInteger(IntegerChecker checker) matches integer that matches checker
  7. isDouble() matches any double
  8. isDouble(double value) same as 3
  9. isDouble(DoubleChecker checker) matches double that matches checker

IntegerChecker is a functional interface that takes an integer and returns a boolean. If it returns true, the match is considered successful. For example, checker num -> num < 10 would match an integer that is less than 10.

DoubleChecker is a functional interface that takes a double and returns a boolean. If it returns true, the match is considered successful. For example, checker num -> num < 0.5 would match a double that is less than 0.5.

Checking boolean

isBoolean(...) returns JsonBooleanBuilder.

Available methods to create boolean template:

  1. isBoolean() matches any boolean
  2. isBoolean(boolean value) matches boolean value
  3. isBoolean(BooleanChecker checker) matches boolean that matches checker

BooleanChecker is a functional interface that takes a boolean and returns a boolean. If it returns true, the match is considered successful. For example bool -> bool == computeOtherBool().

Complex example

For a given JSON:

{
    "name": "123",
    "car": {
        "model": "Tesla Roadster",
        "year": 2018,
        "repairs": [
            {
                "date": "2019-10-11",
                "time": "20:11",
                "cost": 12.32,
                "pic": "qwe.png",
                "all_years": [2011, 2013, 2014, 2020]
            },
            {
                "date": "2019-11-12",
                "time": "21:12",
                "cost": 34,
                "img": "wer.png",
                "all_years": [2011, 2013, 2014, 2020]
            }
        ]
    },
    "rocket": {
        "name": "Falcon 9",
        "launches": "89+",
        "mentioned": [
            12, 34, 56
        ],
        "memorable": [
            {
                "num": 12,
                "why": "flew high"
            },
            {
                "number": 23,
                "reason": "flew fast"
            },
            45,
            null,
            false
        ]
    },
    "options": {
        "12": null
    }
}

You can check it with the following template:

isObject()
    .value("name", isString())
    .value("car", isObject()
        .value("model", "Tesla Roadster")
        .value("year", 2018)
        .value("repairs", isArray(2, 
            isObject()
                .value("date", compile("\\d{4}-\\d{2}-\\d{2}"))
                .value("time", compile("\\d{2}:\\d{2}"))
                .value("cost", isNumber())
                .value(compile("pic|img"), isString(s -> s.endsWith(".png")))
                .value(key -> key.endsWith("years"), 2011, 2013, 2014, 2020))))
    .value("rocket", isObject()
        .value("name", "Falcon 9")
        .value("launches", compile("[0-9]+\\+"))
        .value("mentioned", isArray(12, 34, 56))
        .value("memorable", isArray()
            .item(isObject()
                .value("num", isInteger(i -> i == 12 || i == 13))
                .value(compile("..y"), "flew high"))
            .item(isObject()
                .value("number", 23)
                .value("reason", "flew fast"))
            .item(45)
            .item(isNull())
            .anyOtherItems()))
    .value("options", any())