Skip to content

Commit

Permalink
Merge pull request #197 from harrel56/feature/kotlinx-json-provider
Browse files Browse the repository at this point in the history
Feature/kotlinx json provider
  • Loading branch information
harrel56 authored Jun 4, 2024
2 parents 38706c6 + 52bd2a3 commit 57174dd
Show file tree
Hide file tree
Showing 8 changed files with 273 additions and 17 deletions.
25 changes: 16 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ Supported JSON providers:
- `com.fasterxml.jackson.core:jackson-databind` (default),
- `com.google.code.gson:gson`,
- `jakarta.json:jakarta.json-api`,
- `org.jetbrains.kotlinx:kotlinx-serialization-json`,
- `org.json:json`,
- `new.minidev:json-smart`,
- `org.codehouse.jettison:jettison`.
Expand All @@ -93,15 +94,16 @@ All adapter classes for JSON provider libs can be found in this [package](https:

### Changing JSON/YAML provider

| Provider | Factory class | Provider node class |
|---------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------|
| com.fasterxml.jackson.core:jackson-databind | [JacksonNode.Factory](https://javadoc.io/doc/dev.harrel/json-schema/latest/dev/harrel/jsonschema/providers/JacksonNode.Factory.html) | com.fasterxml.jackson.databind.JsonNode |
| com.google.code.gson:gson | [GsonNode.Factory](https://javadoc.io/doc/dev.harrel/json-schema/latest/dev/harrel/jsonschema/providers/GsonNode.Factory.html) | com.google.gson.JsonElement |
| jakarta.json:jakarta.json-api | [JakartaJsonNode.Factory](https://javadoc.io/doc/dev.harrel/json-schema/latest/dev/harrel/jsonschema/providers/JakartaJsonNode.Factory.html) | jakarta.json.JsonValue |
| org.json:json | [OrgJsonNode.Factory](https://javadoc.io/doc/dev.harrel/json-schema/latest/dev/harrel/jsonschema/providers/OrgJsonNode.Factory.html) | <ul><li>org.json.JSONObject,</li><li>org.json.JSONArray,</li><li>[literal types](#provider-literal-types).</li></ul> |
| new.minidev:json-smart | [JsonSmartNode.Factory](https://javadoc.io/doc/dev.harrel/json-schema/latest/dev/harrel/jsonschema/providers/JsonSmartNode.Factory.html) | <ul><li>net.minidev.json.JSONObject,</li><li>net.minidev.json.JSONArray,</li><li>[literal types](#provider-literal-types).</li></ul> |
| org.codehouse.jettison:jettison | [JettisonNode.Factory](https://javadoc.io/doc/dev.harrel/json-schema/latest/dev/harrel/jsonschema/providers/JettisonNode.Factory.html) | <ul><li>org.codehaus.jettison.json.JSONObject,</li><li>org.codehaus.jettison.json.JSONArray,</li><li>[literal types](#provider-literal-types).</li></ul> |
| org.yaml:snakeyaml | [SnakeYamlNode.Factory](https://javadoc.io/doc/dev.harrel/json-schema/latest/dev/harrel/jsonschema/providers/SnakeYamlNode.Factory.html) | org.yaml.snakeyaml.nodes.Node |
| Provider | Factory class | Provider node class |
|--------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------|
| com.fasterxml.jackson.core:jackson-databind | [JacksonNode.Factory](https://javadoc.io/doc/dev.harrel/json-schema/latest/dev/harrel/jsonschema/providers/JacksonNode.Factory.html) | com.fasterxml.jackson.databind.JsonNode |
| com.google.code.gson:gson | [GsonNode.Factory](https://javadoc.io/doc/dev.harrel/json-schema/latest/dev/harrel/jsonschema/providers/GsonNode.Factory.html) | com.google.gson.JsonElement |
| jakarta.json:jakarta.json-api | [JakartaJsonNode.Factory](https://javadoc.io/doc/dev.harrel/json-schema/latest/dev/harrel/jsonschema/providers/JakartaJsonNode.Factory.html) | jakarta.json.JsonValue |
| org.jetbrains.kotlinx:kotlinx-serialization-json | [KotlinxJsonFactory.Factory](https://javadoc.io/doc/dev.harrel/json-schema/latest/dev/harrel/jsonschema/providers/KotlinxJson.Factory.html) | kotlinx.serialization.json.JsonElement |
| org.json:json | [OrgJsonNode.Factory](https://javadoc.io/doc/dev.harrel/json-schema/latest/dev/harrel/jsonschema/providers/OrgJsonNode.Factory.html) | <ul><li>org.json.JSONObject,</li><li>org.json.JSONArray,</li><li>[literal types](#provider-literal-types).</li></ul> |
| new.minidev:json-smart | [JsonSmartNode.Factory](https://javadoc.io/doc/dev.harrel/json-schema/latest/dev/harrel/jsonschema/providers/JsonSmartNode.Factory.html) | <ul><li>net.minidev.json.JSONObject,</li><li>net.minidev.json.JSONArray,</li><li>[literal types](#provider-literal-types).</li></ul> |
| org.codehouse.jettison:jettison | [JettisonNode.Factory](https://javadoc.io/doc/dev.harrel/json-schema/latest/dev/harrel/jsonschema/providers/JettisonNode.Factory.html) | <ul><li>org.codehaus.jettison.json.JSONObject,</li><li>org.codehaus.jettison.json.JSONArray,</li><li>[literal types](#provider-literal-types).</li></ul> |
| org.yaml:snakeyaml | [SnakeYamlNode.Factory](https://javadoc.io/doc/dev.harrel/json-schema/latest/dev/harrel/jsonschema/providers/SnakeYamlNode.Factory.html) | org.yaml.snakeyaml.nodes.Node |

#### com.fasterxml.jackson.core:jackson-databind
```java
Expand All @@ -120,6 +122,11 @@ Although, it was tested with newest `jakarta.json-api` version, it should be com
new ValidatorFactory().withJsonNodeFactory(new JakartaJsonNode.Factory());
```

#### org.jetbrains.kotlinx:kotlinx-serialization-json
```java
new ValidatorFactory().withJsonNodeFactory(new KotlinxJsonNode.Factory());
```

#### org.json:json
```java
new ValidatorFactory().withJsonNodeFactory(new OrgJsonNode.Factory());
Expand Down
3 changes: 2 additions & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,9 @@ def jsonProviders = [
jackson : 'com.fasterxml.jackson.core:jackson-databind:2.17.1',
gson : 'com.google.code.gson:gson:2.11.0',
orgJson : 'org.json:json:20240303',
jettison : 'org.codehaus.jettison:jettison:1.5.4',
kotlinxJson: 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3',
jakartaJson: 'jakarta.json:jakarta.json-api:2.1.3',
jettison : 'org.codehaus.jettison:jettison:1.5.4',
jsonSmart : 'net.minidev:json-smart:2.5.1',
snakeYaml : 'org.yaml:snakeyaml:2.2'
]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import dev.harrel.jsonschema.JsonNode;
import dev.harrel.jsonschema.SimpleType;

import java.math.BigDecimal;
import java.util.List;
import java.util.Map;
import java.util.Objects;
Expand Down Expand Up @@ -54,4 +55,8 @@ public final Map<String, JsonNode> asObject() {
abstract List<JsonNode> createArray();
abstract Map<String, JsonNode> createObject();
abstract SimpleType computeNodeType(T node);

static boolean canConvertToInteger(BigDecimal bigDecimal) {
return bigDecimal.scale() <= 0 || bigDecimal.stripTrailingZeros().scale() <= 0;
}
}
10 changes: 6 additions & 4 deletions src/main/java/dev/harrel/jsonschema/providers/GsonNode.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
import java.util.*;

public final class GsonNode extends AbstractJsonNode<JsonElement> {
private BigDecimal asNumber;

private GsonNode(JsonElement node, String jsonPointer) {
super(Objects.requireNonNull(node), jsonPointer);
}
Expand All @@ -32,12 +34,12 @@ public String asString() {

@Override
public BigInteger asInteger() {
return node.getAsBigDecimal().toBigInteger();
return asNumber.toBigInteger();
}

@Override
public BigDecimal asNumber() {
return node.getAsBigDecimal();
return asNumber;
}

@Override
Expand Down Expand Up @@ -84,8 +86,8 @@ SimpleType computeNodeType(JsonElement node) {
} else if (jsonPrimitive.isString()) {
return SimpleType.STRING;
} else {
BigDecimal bigDecimal = jsonPrimitive.getAsBigDecimal();
if (bigDecimal.scale() <= 0 || bigDecimal.stripTrailingZeros().scale() <= 0) {
asNumber = jsonPrimitive.getAsBigDecimal();
if (canConvertToInteger(asNumber)) {
return SimpleType.INTEGER;
} else {
return SimpleType.NUMBER;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,7 @@ SimpleType computeNodeType(JsonValue node) {
case FALSE:
return SimpleType.BOOLEAN;
case NUMBER:
JsonNumber jsonNumber = (JsonNumber) node;
if (jsonNumber.isIntegral() || jsonNumber.bigDecimalValue().stripTrailingZeros().scale() <= 0) {
if (canConvertToInteger(((JsonNumber) node).bigDecimalValue())) {
return SimpleType.INTEGER;
} else {
return SimpleType.NUMBER;
Expand Down
118 changes: 118 additions & 0 deletions src/main/java/dev/harrel/jsonschema/providers/KotlinxJsonNode.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
package dev.harrel.jsonschema.providers;

import dev.harrel.jsonschema.JsonNode;
import dev.harrel.jsonschema.JsonNodeFactory;
import dev.harrel.jsonschema.SimpleType;
import kotlinx.serialization.json.*;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.*;

public final class KotlinxJsonNode extends AbstractJsonNode<JsonElement> {
private BigDecimal asNumber;

private KotlinxJsonNode(JsonElement node, String jsonPointer) {
super(Objects.requireNonNull(node), jsonPointer);
}

private KotlinxJsonNode(JsonElement node) {
this(node, "");
}

@Override
public boolean asBoolean() {
return Boolean.parseBoolean(((JsonPrimitive) node).getContent());
}

@Override
public String asString() {
return ((JsonPrimitive) node).getContent();
}

@Override
public BigInteger asInteger() {
return asNumber.toBigInteger();
}

@Override
public BigDecimal asNumber() {
return asNumber;
}

@Override
List<JsonNode> createArray() {
JsonArray array = (JsonArray) node;
List<JsonNode> result = new ArrayList<>(array.size());
for (int i = 0; i < array.size(); i++) {
result.add(new KotlinxJsonNode(array.get(i), jsonPointer + "/" + i));
}
return result;
}

@Override
Map<String, JsonNode> createObject() {
JsonObject object = (JsonObject) node;
Map<String, JsonNode> result = MapUtil.newHashMap(object.size());
for (Map.Entry<String, JsonElement> entry : object.getEntries()) {
result.put(entry.getKey(), new KotlinxJsonNode(entry.getValue(), this.jsonPointer + "/" + JsonNode.encodeJsonPointer(entry.getKey())));
}
return result;
}

@Override
SimpleType computeNodeType(JsonElement node) {
if (node instanceof JsonNull) {
return SimpleType.NULL;
} else if (node instanceof JsonObject) {
return SimpleType.OBJECT;
} else if (node instanceof JsonArray) {
return SimpleType.ARRAY;
} else if (node instanceof JsonPrimitive) {
JsonPrimitive primitive = (JsonPrimitive) node;
if (primitive.isString()) {
return SimpleType.STRING;
}
String content = primitive.getContent();
if ("true".equals(content) || "false".equals(content)) {
return SimpleType.BOOLEAN;
}
asNumber = new BigDecimal(content);
if (canConvertToInteger(asNumber)) {
return SimpleType.INTEGER;
} else {
return SimpleType.NUMBER;
}
} else {
throw new IllegalArgumentException(String.format("Unknown node class [%s]", node.getClass()));
}
}

public static final class Factory implements JsonNodeFactory {
private final Json json;

public Factory() {
this(Json.Default);
}

public Factory(Json json) {
this.json = json;
}

@Override
public KotlinxJsonNode wrap(Object node) {
if (node instanceof KotlinxJsonNode) {
return new KotlinxJsonNode(((KotlinxJsonNode) node).node);
} else if (node instanceof JsonElement) {
return new KotlinxJsonNode((JsonElement) node);
} else {
throw new IllegalArgumentException("Cannot wrap object which is not an instance of kotlinx.serialization.json.JsonElement");
}
}

@Override
public KotlinxJsonNode create(String rawJson) {
return new KotlinxJsonNode(json.parseToJsonElement(rawJson));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ SimpleType computeNodeType(Node node) {
return SimpleType.STRING;
}
asNumber = floatToBigDecimal(node);
if (asNumber.scale() <= 0 || asNumber.stripTrailingZeros().scale() <= 0) {
if (canConvertToInteger(asNumber)) {
return SimpleType.INTEGER;
} else {
return SimpleType.NUMBER;
Expand Down
124 changes: 124 additions & 0 deletions src/test/java/dev/harrel/jsonschema/providers/KotlinxJsonTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
package dev.harrel.jsonschema.providers;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import dev.harrel.jsonschema.JsonNode;
import dev.harrel.jsonschema.JsonNodeFactory;
import dev.harrel.jsonschema.SimpleType;
import kotlinx.serialization.json.Json;
import kotlinx.serialization.json.JsonElement;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;

class KotlinxJsonTest {
private JsonNodeFactory createFactory() {
return new KotlinxJsonNode.Factory();
}

@Test
void shouldWrapForValidArgument() {
JsonElement object = Json.Default.parseToJsonElement("{}");
JsonNode wrap = createFactory().wrap(object);
assertThat(wrap).isNotNull();
assertThat(wrap.getNodeType()).isEqualTo(SimpleType.OBJECT);
}

@Test
void shouldFailWrapForInvalidArgument() throws JsonProcessingException {
com.fasterxml.jackson.databind.JsonNode object = new ObjectMapper().readTree("{}");
JsonNodeFactory factory = createFactory();
assertThatThrownBy(() -> factory.wrap(object))
.isInstanceOf(IllegalArgumentException.class);
}

@Nested
class SpecificationSuiteTest extends dev.harrel.jsonschema.SpecificationSuiteTest {
@Override
public JsonNodeFactory getJsonNodeFactory() {
return createFactory();
}
}

@Nested
class SupplementarySuiteTest extends dev.harrel.jsonschema.SupplementarySuiteTest {
@Override
public JsonNodeFactory getJsonNodeFactory() {
return createFactory();
}
}

@Nested
class Draft2020EvaluatorFactoryTest extends dev.harrel.jsonschema.Draft2020EvaluatorFactoryTest {
@Override
public JsonNodeFactory getJsonNodeFactory() {
return createFactory();
}
}

@Nested
class Draft2019EvaluatorFactoryTest extends dev.harrel.jsonschema.Draft2019EvaluatorFactoryTest {
@Override
public JsonNodeFactory getJsonNodeFactory() {
return createFactory();
}
}

@Nested
class JsonNodeFactoryTest extends dev.harrel.jsonschema.JsonNodeFactoryTest {
@Override
public JsonNodeFactory getJsonNodeFactory() {
return createFactory();
}
}

@Nested
class JsonNodeTest extends dev.harrel.jsonschema.JsonNodeTest {
@Override
public JsonNodeFactory getJsonNodeFactory() {
return createFactory();
}
}

@Nested
class MetaSchemaTest extends dev.harrel.jsonschema.MetaSchemaTest {
@Override
public JsonNodeFactory getJsonNodeFactory() {
return createFactory();
}
}

@Nested
class VocabulariesTest extends dev.harrel.jsonschema.VocabulariesTest {
@Override
public JsonNodeFactory getJsonNodeFactory() {
return createFactory();
}
}

@Nested
class JsonPointerEscapingTest extends dev.harrel.jsonschema.JsonPointerEscapingTest {
@Override
public JsonNodeFactory getJsonNodeFactory() {
return createFactory();
}
}

@Nested
class EvaluationPathTest extends dev.harrel.jsonschema.EvaluationPathTest {
@Override
public JsonNodeFactory getJsonNodeFactory() {
return createFactory();
}
}

@Nested
class DisabledSchemaValidationTest extends dev.harrel.jsonschema.DisabledSchemaValidationTest {
@Override
public JsonNodeFactory getJsonNodeFactory() {
return createFactory();
}
}
}

0 comments on commit 57174dd

Please sign in to comment.