1

Initial import

This commit is contained in:
2024-10-24 20:59:28 +02:00
commit 63a56286bf
22 changed files with 1769 additions and 0 deletions

View File

@@ -0,0 +1,35 @@
package com.devsoap.json;
import jakarta.json.JsonArray;
import jakarta.json.JsonValue;
import org.assertj.core.api.AbstractListAssert;
import java.util.List;
public class JsonArrayAssert<VALUE_TYPE extends JsonValue> extends AbstractListAssert<JsonArrayAssert<VALUE_TYPE>,
List<VALUE_TYPE>,
VALUE_TYPE, JsonValueAssert<VALUE_TYPE>> {
private final String fieldName;
protected JsonArrayAssert(String fieldName, JsonArray array) {
super(array.getValuesAs(jsonValue -> (VALUE_TYPE) jsonValue), JsonArrayAssert.class);
this.fieldName = fieldName;
}
public JsonArrayAssert(String fieldName, VALUE_TYPE value) {
super(List.of(value), JsonArrayAssert.class);
this.fieldName = fieldName;
}
@Override
protected JsonValueAssert<VALUE_TYPE> toAssert(VALUE_TYPE value, String description) {
return new JsonValueAssert<>(fieldName, (JsonValue) value, value);
}
@Override
protected JsonArrayAssert<VALUE_TYPE> newAbstractIterableAssert(Iterable<? extends VALUE_TYPE> iterable) {
//TODO
return null;
}
}

View File

@@ -0,0 +1,249 @@
package com.devsoap.json;
import jakarta.json.*;
import jakarta.json.stream.JsonParser;
import org.assertj.core.api.AbstractAssert;
import org.assertj.core.api.ListAssert;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
public class JsonAssert extends AbstractAssert<JsonAssert, String> {
protected JsonAssert(String json) {
super(json, JsonAssert.class);
}
public static JsonAssert assertThat(String json) {
return new JsonAssert(json);
}
public static JsonAssert assertThat(Object object) {
return assertThat(object.toString());
}
public JsonValueAssert<?> firstField(String fieldName) {
try (var stream = new ByteArrayInputStream(actual.getBytes(StandardCharsets.UTF_8))) {
var actualParser = Json.createParser(stream);
actualParser.next(); // START_OBJECT
return findFieldInObjectOnAnyDepth(fieldName, actualParser)
.orElseThrow(() -> failure("Field %s not found.", fieldName) );
} catch (IOException e) {
throw failure("Failed to parse field");
}
}
public JsonValueAssert<?> lastField(String fieldName) {
try (var stream = new ByteArrayInputStream(actual.getBytes(StandardCharsets.UTF_8))) {
var scannerParser = Json.createParser(stream);
scannerParser.next(); // START_OBJECT
return findLastFieldInObject(fieldName, scannerParser)
.orElseThrow(() -> failure("Field %s not found.", fieldName) );
} catch (IOException e) {
throw failure("Failed to parse field");
}
}
public JsonValueAssert<?> nthField(String fieldName, int index) {
try (var stream = new ByteArrayInputStream(actual.getBytes(StandardCharsets.UTF_8))) {
var parser = Json.createParser(stream);
parser.next(); // START_OBJECT
return findNthFieldInObject(fieldName, parser, index)
.orElseThrow(() -> failure("%d nth field %s not found.", index, fieldName) );
} catch (IOException e) {
throw failure("Failed to parse field");
}
}
public JsonValueAssert<?> field(String fieldName) {
try (var stream = new ByteArrayInputStream(actual.getBytes(StandardCharsets.UTF_8))) {
var actualParser = Json.createParser(stream);
actualParser.next(); // START_OBJECT
return findFieldInObject(fieldName, actualParser)
.orElseThrow(() -> failure("Field %s not found.", fieldName) );
} catch (IOException e) {
throw failure("Failed to parse field");
}
}
public JsonArrayAssert<?> array(String fieldName) {
try (var stream = new ByteArrayInputStream(actual.getBytes(StandardCharsets.UTF_8))) {
var actualParser = Json.createParser(stream);
actualParser.next(); // START_OBJECT
return findArrayInObject(fieldName, actualParser);
} catch (IOException e) {
throw failure("Failed to parse field");
}
}
public <ITEM> ListAssert<ITEM> array(String fieldName, Class<ITEM> itemClass) {
try (var stream = new ByteArrayInputStream(actual.getBytes(StandardCharsets.UTF_8))) {
var actualParser = Json.createParser(stream);
actualParser.next(); // START_OBJECT
return findArrayInObject(fieldName, actualParser, itemClass);
} catch (IOException e) {
throw failure("Failed to parse field");
}
}
public JsonValueAssert<?> path(String... fields) {
var actualParser = Json.createParser(new ByteArrayInputStream(actual.getBytes(StandardCharsets.UTF_8)));
actualParser.next(); // START_OBJECT
var fieldList = new ArrayList<>(List.of(fields));
var currentField = field(fieldList.removeFirst());
return currentField.path(fieldList.toArray(String[]::new));
}
private static Optional<JsonValueAssert<?>> findFieldInObject(String fieldName, JsonParser parser) {
if (parser.currentEvent() != JsonParser.Event.START_OBJECT) {
throw new IllegalArgumentException("Current event must be START_OBJECT");
}
return typedJsonValue(fieldName, parser.getObject().get(fieldName));
}
private static JsonArrayAssert<?> findArrayInObject(String fieldName, JsonParser parser) {
if (parser.currentEvent() != JsonParser.Event.START_OBJECT) {
throw new IllegalArgumentException("Current event must be START_OBJECT");
}
return typedJsonArray(fieldName, parser.getObject().get(fieldName));
}
private static <ITEM> ListAssert<ITEM> findArrayInObject(String fieldName, JsonParser parser, Class<ITEM> itemClass) {
if (parser.currentEvent() != JsonParser.Event.START_OBJECT) {
throw new IllegalArgumentException("Current event must be START_OBJECT");
}
return typedJsonArray(parser.getObject().get(fieldName));
}
private static Optional<JsonValueAssert<?>> findFieldInObjectOnAnyDepth(String fieldName, JsonParser parser) {
if (parser.currentEvent() != JsonParser.Event.START_OBJECT) {
throw new IllegalArgumentException("Current event must be START_OBJECT");
}
while (parser.hasNext()) {
var keyEvent = parser.next();
if (keyEvent == JsonParser.Event.KEY_NAME ) {
var key = parser.getString();
parser.next(); // VALUE
if (key.equalsIgnoreCase(fieldName)) {
return typedJsonValue(key, parser.getValue());
}
}
}
return Optional.empty();
}
private static Optional<JsonValueAssert<?>> findNthFieldInObject(String fieldName, JsonParser parser, int index) {
if (parser.currentEvent() != JsonParser.Event.START_OBJECT) {
throw new IllegalArgumentException("Current event must be START_OBJECT");
}
var counter = 0;
while (parser.hasNext()) {
var keyEvent = parser.next();
if (keyEvent == JsonParser.Event.KEY_NAME ) {
var key = parser.getString();
parser.next(); // VALUE
if (key.equalsIgnoreCase(fieldName)) {
if (counter == index) {
return typedJsonValue(key, parser.getValue());
}
counter++;
}
}
}
return Optional.empty();
}
private static Optional<JsonValueAssert<?>> findLastFieldInObject(String fieldName, JsonParser scannerParser) {
if (scannerParser.currentEvent() != JsonParser.Event.START_OBJECT) {
throw new IllegalArgumentException("Current event must be START_OBJECT");
}
JsonValue current = null;
while (scannerParser.hasNext()) {
var keyEvent = scannerParser.next();
if (keyEvent == JsonParser.Event.KEY_NAME ) {
var key = scannerParser.getString();
scannerParser.next(); // VALUE
if (key.equalsIgnoreCase(fieldName)) {
current = scannerParser.getValue();
}
}
}
if (current == null) {
return Optional.empty();
}
if (current.getValueType() == JsonValue.ValueType.OBJECT) {
try (var stream = new ByteArrayInputStream(current.toString().getBytes(StandardCharsets.UTF_8))) {
var parser = Json.createParser(stream);
parser.next(); // START_OBJECT
var found = findLastFieldInObject(fieldName, parser);
if (found.isPresent()) {
return found;
}
} catch (IOException e) {
throw new IllegalStateException("Failed to parse json value");
}
}
return Optional.of(current).flatMap(v -> typedJsonValue(fieldName, v));
}
static Optional<JsonValueAssert<?>> typedJsonValue(String fieldName, JsonValue jsonValue) {
return Optional.ofNullable(jsonValue).map(v -> switch (jsonValue.getValueType()) {
case ARRAY -> null;
case OBJECT -> new JsonValueAssert<>(fieldName, jsonValue, jsonValue.asJsonObject());
case STRING -> new JsonValueAssert<>(fieldName, jsonValue, ((JsonString) jsonValue).getString());
case TRUE -> new JsonValueAssert<>(fieldName, jsonValue, true);
case FALSE -> new JsonValueAssert<>(fieldName, jsonValue, false);
case NULL -> new JsonValueAssert<>(fieldName, jsonValue, null);
case NUMBER -> {
Number number = ((JsonNumber) jsonValue).numberValue();
if (number instanceof Integer) {
yield new JsonValueAssert<>(fieldName, jsonValue, (Integer) number);
}
if (number instanceof Short) {
yield new JsonValueAssert<>(fieldName, jsonValue, (Short) number);
}
if (number instanceof Long) {
yield new JsonValueAssert<>(fieldName, jsonValue, (Long) number);
}
if (number instanceof Double) {
yield new JsonValueAssert<>(fieldName, jsonValue, (Double) number);
}
if (number instanceof Float) {
yield new JsonValueAssert<>(fieldName, jsonValue, (Float) number);
}
if (number instanceof BigDecimal) {
yield new JsonValueAssert<>(fieldName, jsonValue, (BigDecimal) number);
}
if (number instanceof BigInteger) {
yield new JsonValueAssert<>(fieldName, jsonValue, (BigInteger) number);
}
yield new JsonValueAssert<>(fieldName, jsonValue, ((JsonNumber) jsonValue).bigDecimalValue());
}
});
}
static JsonArrayAssert<?> typedJsonArray(String fieldName, JsonValue jsonArray) {
return new JsonArrayAssert<>(fieldName, (JsonArray) jsonArray);
}
static <ITEM> ListAssert<ITEM> typedJsonArray(JsonValue jsonArray) {
return new ListAssert<>(((JsonArray)jsonArray).getValuesAs(value -> (ITEM) switch (value.getValueType()) {
case ARRAY -> null;
case OBJECT -> null;
case STRING -> ((JsonString) value).toString();
case NUMBER -> ((JsonNumber) value).intValue();
case TRUE -> Boolean.TRUE;
case FALSE -> Boolean.FALSE;
case NULL -> null;
}));
}
}

View File

@@ -0,0 +1,145 @@
package com.devsoap.json;
import jakarta.json.JsonArray;
import jakarta.json.JsonNumber;
import jakarta.json.JsonString;
import jakarta.json.JsonValue;
import org.assertj.core.api.*;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.regex.Pattern;
import static com.devsoap.json.JsonAssert.typedJsonArray;
public class JsonValueAssert<VALUE_TYPE> extends AbstractAssert<JsonValueAssert<VALUE_TYPE>, VALUE_TYPE> {
private final String fieldName;
private final JsonValue value;
protected JsonValueAssert(String fieldName, JsonValue value, VALUE_TYPE fieldValue) {
super(fieldValue, JsonValueAssert.class);
this.fieldName = fieldName;
this.value = value;
}
public JsonValueAssert<?> field(String fieldName) {
if (value.getValueType() != JsonValue.ValueType.OBJECT) {
failWithActualExpectedAndMessage(value.getValueType(), JsonValue.ValueType.OBJECT,
"Only objects can have fields");
}
return JsonAssert.typedJsonValue(fieldName, value.asJsonObject().get(fieldName))
.orElseGet(() -> this.withFailMessage("Field %s not found", fieldName));
}
public JsonValueAssert<VALUE_TYPE> field(String fieldName, Class<VALUE_TYPE> type) {
if (value.getValueType() != JsonValue.ValueType.OBJECT) {
failWithActualExpectedAndMessage(value.getValueType(), JsonValue.ValueType.OBJECT,
"Only objects can have fields");
}
return (JsonValueAssert<VALUE_TYPE>) JsonAssert.typedJsonValue(fieldName, value.asJsonObject().get(fieldName))
.orElseGet(() -> this.withFailMessage("Field %s not found", fieldName));
}
public JsonArrayAssert<?> array(String fieldName) {
if (value.getValueType() != JsonValue.ValueType.OBJECT) {
failWithActualExpectedAndMessage(value.getValueType(), JsonValue.ValueType.OBJECT,
"Only objects can have fields");
}
JsonValue jsonValue = value.asJsonObject().get(fieldName);
if (jsonValue.getValueType() != JsonValue.ValueType.ARRAY) {
failWithActualExpectedAndMessage(value.getValueType(), JsonValue.ValueType.OBJECT,
"Field must be an array");
}
return typedJsonArray(fieldName, jsonValue);
}
public <ITEM> ListAssert<ITEM> array(String fieldName, Class<ITEM> itemClass) {
if (value.getValueType() != JsonValue.ValueType.OBJECT) {
failWithActualExpectedAndMessage(value.getValueType(), JsonValue.ValueType.OBJECT,
"Only objects can have fields");
}
JsonValue jsonValue = value.asJsonObject().get(fieldName);
if (jsonValue.getValueType() != JsonValue.ValueType.ARRAY) {
failWithActualExpectedAndMessage(value.getValueType(), JsonValue.ValueType.OBJECT,
"Field must be an array");
}
JsonArray array = (JsonArray) jsonValue;
return new ListAssert<>(array.getValuesAs(value -> (ITEM) switch (value.getValueType()) {
case ARRAY -> throw failure("Cannot convert ARRAY to %s", itemClass);
case OBJECT -> throw failure("Cannot convert OBJECT to %s", itemClass);
case STRING -> ((JsonString) value).toString();
case NUMBER -> ((JsonNumber) value).intValue();
case TRUE -> Boolean.TRUE;
case FALSE -> Boolean.FALSE;
case NULL -> null;
}));
}
public JsonValueAssert<?> path(String... fields) {
JsonValueAssert<?> currentField = this;
for (String field : fields) {
currentField = currentField.field(field);
}
return currentField;
}
public IntegerAssert asInteger() {
if (actual instanceof Integer) {
return new IntegerAssert((Integer) actual);
}
return new IntegerAssert(Integer.parseInt(actual.toString()));
}
public BigDecimalAssert asBigDecimal() {
if (actual instanceof BigDecimal) {
return new BigDecimalAssert((BigDecimal) actual);
}
return new BigDecimalAssert(BigDecimal.valueOf(Double.parseDouble(actual.toString())));
}
public BigIntegerAssert asBigInteger() {
if (actual instanceof BigInteger) {
return new BigIntegerAssert((BigInteger) actual);
}
return new BigIntegerAssert(BigInteger.valueOf(Integer.parseInt(actual.toString())));
}
public DoubleAssert asDouble() {
if (actual instanceof Double) {
return new DoubleAssert((Double) actual);
}
return new DoubleAssert(Double.parseDouble(actual.toString()));
}
public AbstractLongAssert<?> asLong() {
if (actual instanceof Long) {
return new LongAssert((Long) actual);
}
return new LongAssert(Long.parseLong(actual.toString()));
}
public JsonArrayAssert<?> asArray() {
if (value.getValueType() == JsonValue.ValueType.ARRAY) {
return new JsonArrayAssert<>(fieldName, (JsonArray) value);
}
return new JsonArrayAssert<>(fieldName, value);
}
public JsonValueAssert<?> matches(String regex) {
return matches(valueType -> Pattern.matches(regex, valueType.toString()));
}
public JsonValueAssert<?> notMatches(String regex) {
return matches(valueType -> !Pattern.matches(regex, valueType.toString()));
}
@Override
public JsonValueAssert<VALUE_TYPE> isEqualTo(Object expected) {
return super.isEqualTo(expected);
}
}

View File

@@ -0,0 +1,117 @@
package com.devsoap.json;
import org.junit.jupiter.api.Test;
import static com.devsoap.json.JsonAssert.assertThat;
public class ArrayTraversalAssertionTest {
private static final String JSON = """
{
"array": [
{ "item": 1 },
{ "item": 2 }
],
"numericArray": [
1,2,3
],
"fieldArray": {
"array": [
{ "item": 3 },
{ "item": 4 }
],
"numericArray": [
1,2,3
]
},
"matrix": [
[1,2],
[3,4]
]
}
""";
@Test
public void rootArray() {
assertThat(JSON)
.array("array")
.first()
.field("item")
.isEqualTo(1);
assertThat(JSON)
.array("array")
.last()
.field("item")
.isEqualTo(2);
}
@Test
public void fieldArray() {
assertThat(JSON)
.field("fieldArray")
.array("array")
.first()
.field("item")
.isEqualTo(3);
assertThat(JSON)
.field("fieldArray")
.array("array")
.last()
.field("item")
.isEqualTo(4);
}
@Test
public void numericArray() {
assertThat(JSON)
.array("numericArray", Integer.class)
.first()
.isEqualTo(1);
assertThat(JSON)
.field("fieldArray")
.array("numericArray", Integer.class)
.first()
.isEqualTo(1);
}
@Test
public void matrix() {
assertThat(JSON)
.array("matrix")
.first()
.asArray()
.first()
.asInteger()
.isEqualTo(1);
assertThat(JSON)
.array("matrix")
.first()
.asArray()
.last()
.asInteger()
.isEqualTo(2);
assertThat(JSON)
.array("matrix")
.last()
.asArray()
.first()
.asInteger()
.isEqualTo(3);
assertThat(JSON)
.array("matrix")
.last()
.asArray()
.last()
.asInteger()
.isEqualTo(4);
}
}

View File

@@ -0,0 +1,64 @@
package com.devsoap.json;
import org.junit.jupiter.api.Test;
import static com.devsoap.json.JsonAssert.assertThat;
public class DoubleAssertionTest {
private static final String JSON = """
{
"positive": 1.2134,
"negative": -1.321,
"zero": 0.0000
}
""";
@Test
public void equals() {
assertThat(JSON)
.field("positive")
.asDouble()
.isEqualTo(1.2134D);
assertThat(JSON)
.field("negative")
.asDouble()
.isEqualTo(-1.321D);
assertThat(JSON)
.field("zero")
.asDouble()
.isEqualTo(0D);
}
@Test
public void greaterThan() {
assertThat(JSON)
.field("positive")
.asDouble()
.isGreaterThan(0);
assertThat(JSON)
.field("negative")
.asDouble()
.isGreaterThan(-2);
assertThat(JSON)
.field("zero")
.asDouble()
.isGreaterThan(-1);
}
@Test
public void lessThan() {
assertThat(JSON)
.field("positive")
.asDouble()
.isLessThan(2);
assertThat(JSON)
.field("negative")
.asDouble()
.isLessThan(0);
assertThat(JSON)
.field("zero")
.asDouble()
.isLessThan(1);
}
}

View File

@@ -0,0 +1,85 @@
package com.devsoap.json;
import org.junit.jupiter.api.Test;
import static com.devsoap.json.JsonAssert.assertThat;
public class FieldTraversalAssertionTest {
private static final String JSON = """
{
"level": 0,
"root": {
"level": 1,
"child": {
"level": 2,
"child": {
"level": 3,
"last": 42
}
}
}
}
""";
@Test
public void root() {
assertThat(JSON).field("level").isEqualTo(0);
assertThat(JSON).field("root").isNotNull();
}
@Test
public void fieldTraversal() {
assertThat(JSON)
.field("root")
.field("child")
.field("level")
.isEqualTo(2);
}
@Test
public void pathTraversal() {
assertThat(JSON)
.path("root", "child", "level")
.isEqualTo(2);
}
@Test
public void firstFieldMatching() {
assertThat(JSON)
.firstField("child")
.field("level")
.isEqualTo(2);
assertThat(JSON)
.firstField("last")
.isEqualTo(42);
}
@Test
public void lastFieldMatching() {
assertThat(JSON)
.lastField("child")
.field("level")
.isEqualTo(3);
assertThat(JSON)
.lastField("level")
.isEqualTo(3);
}
@Test
public void nthFieldMatching() {
assertThat(JSON)
.nthField("child", 1)
.field("level")
.isEqualTo(3);
for (var level=0; level<=3; level++) {
assertThat(JSON)
.nthField("level", level)
.isEqualTo(level);
}
}
}

View File

@@ -0,0 +1,61 @@
package com.devsoap.json;
import org.junit.jupiter.api.Test;
import static com.devsoap.json.JsonAssert.assertThat;
public class IntegerAssertionTest {
private static final String JSON = """
{
"positive": 1,
"negative": -1,
"zero": 0
}
""";
@Test
public void equals() {
assertThat(JSON)
.field("positive")
.isEqualTo(1);
assertThat(JSON)
.field("negative")
.isEqualTo(-1);
assertThat(JSON)
.field("zero")
.isEqualTo(0);
}
@Test
public void greaterThan() {
assertThat(JSON)
.field("positive")
.asInteger()
.isGreaterThan(0);
assertThat(JSON)
.field("negative")
.asInteger()
.isGreaterThan(-2);
assertThat(JSON)
.field("zero")
.asInteger()
.isGreaterThan(-1);
}
@Test
public void lessThan() {
assertThat(JSON)
.field("positive")
.asInteger()
.isLessThan(2);
assertThat(JSON)
.field("negative")
.asInteger()
.isLessThan(0);
assertThat(JSON)
.field("zero")
.asInteger()
.isLessThan(1);
}
}

View File

@@ -0,0 +1,64 @@
package com.devsoap.json;
import org.junit.jupiter.api.Test;
import static com.devsoap.json.JsonAssert.assertThat;
public class LongAssertionTest {
private static final String JSON = """
{
"positive": 100000000012,
"negative": -100000000032,
"zero": 0
}
""";
@Test
public void equals() {
assertThat(JSON)
.field("positive")
.asLong()
.isEqualTo(100000000012L);
assertThat(JSON)
.field("negative")
.asLong()
.isEqualTo(-100000000032L);
assertThat(JSON)
.field("zero")
.asLong()
.isEqualTo(0L);
}
@Test
public void greaterThan() {
assertThat(JSON)
.field("positive")
.asLong()
.isGreaterThan(10000);
assertThat(JSON)
.field("negative")
.asLong()
.isGreaterThan(-100000000052L);
assertThat(JSON)
.field("zero")
.asLong()
.isGreaterThan(-1);
}
@Test
public void lessThan() {
assertThat(JSON)
.field("positive")
.asLong()
.isLessThan(2100000000012L);
assertThat(JSON)
.field("negative")
.asLong()
.isLessThan(0);
assertThat(JSON)
.field("zero")
.asLong()
.isLessThan(1);
}
}

View File

@@ -0,0 +1,57 @@
package com.devsoap.json;
import org.junit.jupiter.api.Test;
import java.math.BigDecimal;
import java.math.BigInteger;
import static com.devsoap.json.JsonAssert.assertThat;
public class StringAssertionTest {
private static final String JSON = """
{
"foo": "bar",
"number": "1234"
}
""";
@Test
public void equals() {
assertThat(JSON)
.field("foo")
.isEqualTo("bar")
.isNotEqualTo("baz");
}
@Test
public void matches() {
assertThat(JSON)
.field("foo")
.matches("b+a+r+")
.notMatches("baz");
}
@Test
public void stringToNumber() {
assertThat(JSON)
.field("number")
.asInteger()
.isEqualTo(1234);
assertThat(JSON)
.field("number")
.asDouble()
.isEqualTo(1234.0);
assertThat(JSON)
.field("number")
.asLong()
.isEqualTo(1234L);
assertThat(JSON)
.field("number")
.asBigDecimal()
.isEqualTo(BigDecimal.valueOf(1234.0));
assertThat(JSON)
.field("number")
.asBigInteger()
.isEqualTo(BigInteger.valueOf(1234)); }
}