[serde-gson] Support for Gson readers and writers
This commit is contained in:
@@ -51,7 +51,7 @@ dependencies {
|
|||||||
testImplementation(libs.mockito)
|
testImplementation(libs.mockito)
|
||||||
testAgent(libs.mockito)
|
testAgent(libs.mockito)
|
||||||
testImplementation(libs.assertj)
|
testImplementation(libs.assertj)
|
||||||
testImplementation(project(":test-utils"))
|
testImplementation(project(":generic-test-utils"))
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.compileTestJava {
|
tasks.compileTestJava {
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import gradle.kotlin.dsl.accessors._182d53d78a136df48d95cf7411f9259f.lombok
|
||||||
|
import org.gradle.kotlin.dsl.assign
|
||||||
|
|
||||||
|
plugins {
|
||||||
|
java
|
||||||
|
alias(libs.plugins.lombok)
|
||||||
|
}
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation(project(":tweed5-serde-api"))
|
||||||
|
implementation(platform(libs.junit.platform))
|
||||||
|
implementation(libs.junit.core)
|
||||||
|
implementation(libs.mockito)
|
||||||
|
implementation(libs.assertj)
|
||||||
|
}
|
||||||
|
|
||||||
|
lombok {
|
||||||
|
version = libs.versions.lombok.get()
|
||||||
|
}
|
||||||
|
|
||||||
|
java {
|
||||||
|
sourceCompatibility = JavaVersion.toVersion(libs.versions.java.test.get())
|
||||||
|
targetCompatibility = JavaVersion.toVersion(libs.versions.java.test.get())
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@ assertj = "3.26.3"
|
|||||||
asm = "9.7"
|
asm = "9.7"
|
||||||
autoservice = "1.1.1"
|
autoservice = "1.1.1"
|
||||||
acl = "1.3.5"
|
acl = "1.3.5"
|
||||||
|
gson = "2.13.1"
|
||||||
jackson = "2.19.0"
|
jackson = "2.19.0"
|
||||||
java-main = "8"
|
java-main = "8"
|
||||||
java-test = "21"
|
java-test = "21"
|
||||||
@@ -26,6 +27,7 @@ asm-commons = { group = "org.ow2.asm", name = "asm-commons", version.ref = "asm"
|
|||||||
asm-core = { group = "org.ow2.asm", name = "asm", version.ref = "asm" }
|
asm-core = { group = "org.ow2.asm", name = "asm", version.ref = "asm" }
|
||||||
autoservice-annotations = { group = "com.google.auto.service", name = "auto-service-annotations", version.ref = "autoservice" }
|
autoservice-annotations = { group = "com.google.auto.service", name = "auto-service-annotations", version.ref = "autoservice" }
|
||||||
autoservice-processor = { group = "com.google.auto.service", name = "auto-service", version.ref = "autoservice" }
|
autoservice-processor = { group = "com.google.auto.service", name = "auto-service", version.ref = "autoservice" }
|
||||||
|
gson = { group = "com.google.code.gson", name = "gson", version.ref = "gson" }
|
||||||
jackson-core = { group = "com.fasterxml.jackson.core", name = "jackson-core", version.ref = "jackson" }
|
jackson-core = { group = "com.fasterxml.jackson.core", name = "jackson-core", version.ref = "jackson" }
|
||||||
jetbrains-annotations = { group = "org.jetbrains", name = "annotations", version.ref = "jetbrains-annotations" }
|
jetbrains-annotations = { group = "org.jetbrains", name = "annotations", version.ref = "jetbrains-annotations" }
|
||||||
jspecify-annotations = { group = "org.jspecify", name = "jspecify", version.ref = "jspecify" }
|
jspecify-annotations = { group = "org.jspecify", name = "jspecify", version.ref = "jspecify" }
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ include("tweed5-naming-format")
|
|||||||
include("tweed5-patchwork")
|
include("tweed5-patchwork")
|
||||||
include("tweed5-serde-api")
|
include("tweed5-serde-api")
|
||||||
include("tweed5-serde-extension")
|
include("tweed5-serde-extension")
|
||||||
|
include("tweed5-serde-gson")
|
||||||
include("tweed5-serde-hjson")
|
include("tweed5-serde-hjson")
|
||||||
include("tweed5-serde-jackson")
|
include("tweed5-serde-jackson")
|
||||||
include("tweed5-type-utils")
|
include("tweed5-type-utils")
|
||||||
@@ -21,6 +22,9 @@ include("tweed5-weaver-pojo-validation-extension")
|
|||||||
|
|
||||||
includeAs("minecraft:tweed5-bundle", "minecraft/tweed5-bundle")
|
includeAs("minecraft:tweed5-bundle", "minecraft/tweed5-bundle")
|
||||||
|
|
||||||
|
includeAs("generic-test-utils", "test-utils/generic")
|
||||||
|
includeAs("serde-json-test-utils", "test-utils/serde-json")
|
||||||
|
|
||||||
fun includeAs(name: String, path: String) {
|
fun includeAs(name: String, path: String) {
|
||||||
include(name)
|
include(name)
|
||||||
project(":$name").projectDir = file(path)
|
project(":$name").projectDir = file(path)
|
||||||
|
|||||||
@@ -1,8 +0,0 @@
|
|||||||
plugins {
|
|
||||||
java
|
|
||||||
}
|
|
||||||
|
|
||||||
java {
|
|
||||||
sourceCompatibility = JavaVersion.toVersion(libs.versions.java.test.get())
|
|
||||||
targetCompatibility = JavaVersion.toVersion(libs.versions.java.test.get())
|
|
||||||
}
|
|
||||||
3
test-utils/generic/build.gradle.kts
Normal file
3
test-utils/generic/build.gradle.kts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
plugins {
|
||||||
|
id("de.siphalor.tweed5.test-utils")
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package de.siphalor.tweed5.testutils;
|
package de.siphalor.tweed5.testutils.generic;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
3
test-utils/serde-json/build.gradle.kts
Normal file
3
test-utils/serde-json/build.gradle.kts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
plugins {
|
||||||
|
id("de.siphalor.tweed5.test-utils")
|
||||||
|
}
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
package de.siphalor.tweed5.testutils.serde.json;
|
||||||
|
|
||||||
|
import de.siphalor.tweed5.dataapi.api.TweedDataReader;
|
||||||
|
import lombok.SneakyThrows;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
public interface JsonReaderTest {
|
||||||
|
TweedDataReader createJsonReader(String text);
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@SneakyThrows
|
||||||
|
default void complexJsonReadTest() {
|
||||||
|
var reader = createJsonReader("""
|
||||||
|
{
|
||||||
|
"first": [
|
||||||
|
[ 1 ]
|
||||||
|
],
|
||||||
|
"second": {
|
||||||
|
"test": "Hello World!"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""");
|
||||||
|
|
||||||
|
var token = reader.peekToken();
|
||||||
|
assertThat(token.isMapStart()).isTrue();
|
||||||
|
token = reader.readToken();
|
||||||
|
assertThat(token.isMapStart()).isTrue();
|
||||||
|
token = reader.readToken();
|
||||||
|
assertThat(token.isMapEntryKey()).isTrue();
|
||||||
|
assertThat(token.canReadAsString()).isTrue();
|
||||||
|
assertThat(token.readAsString()).isEqualTo("first");
|
||||||
|
token = reader.readToken();
|
||||||
|
assertThat(token.isMapEntryValue()).isTrue();
|
||||||
|
assertThat(token.isListStart()).isTrue();
|
||||||
|
token = reader.readToken();
|
||||||
|
assertThat(token.isMapEntryValue()).isFalse();
|
||||||
|
assertThat(token.isListValue()).isTrue();
|
||||||
|
assertThat(token.isListStart()).isTrue();
|
||||||
|
token = reader.readToken();
|
||||||
|
assertThat(token.isListValue()).isTrue();
|
||||||
|
assertThat(token.canReadAsInt()).isTrue();
|
||||||
|
assertThat(token.readAsInt()).isEqualTo(1);
|
||||||
|
token = reader.readToken();
|
||||||
|
assertThat(token.isListValue()).isTrue();
|
||||||
|
assertThat(token.isListEnd()).isTrue();
|
||||||
|
token = reader.readToken();
|
||||||
|
assertThat(token.isListValue()).isFalse();
|
||||||
|
assertThat(token.isMapEntryValue()).isTrue();
|
||||||
|
assertThat(token.isListEnd()).isTrue();
|
||||||
|
token = reader.readToken();
|
||||||
|
assertThat(token.isMapEntryKey()).isTrue();
|
||||||
|
assertThat(token.canReadAsString()).isTrue();
|
||||||
|
assertThat(token.readAsString()).isEqualTo("second");
|
||||||
|
token = reader.readToken();
|
||||||
|
assertThat(token.isMapEntryValue()).isTrue();
|
||||||
|
assertThat(token.isMapStart()).isTrue();
|
||||||
|
token = reader.readToken();
|
||||||
|
assertThat(token.isMapEntryValue()).isFalse();
|
||||||
|
assertThat(token.isMapEntryKey()).isTrue();
|
||||||
|
assertThat(token.canReadAsString()).isTrue();
|
||||||
|
assertThat(token.readAsString()).isEqualTo("test");
|
||||||
|
token = reader.readToken();
|
||||||
|
assertThat(token.isMapEntryValue()).isTrue();
|
||||||
|
assertThat(token.canReadAsString()).isTrue();
|
||||||
|
assertThat(token.readAsString()).isEqualTo("Hello World!");
|
||||||
|
token = reader.readToken();
|
||||||
|
assertThat(token.isMapEnd()).isTrue();
|
||||||
|
token = reader.readToken();
|
||||||
|
assertThat(token.isMapEnd()).isTrue();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -23,14 +23,13 @@ import java.io.StringWriter;
|
|||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.function.Consumer;
|
|
||||||
|
|
||||||
import static de.siphalor.tweed5.attributesextension.api.AttributesExtension.attribute;
|
import static de.siphalor.tweed5.attributesextension.api.AttributesExtension.attribute;
|
||||||
import static de.siphalor.tweed5.attributesextension.api.AttributesExtension.attributeDefault;
|
import static de.siphalor.tweed5.attributesextension.api.AttributesExtension.attributeDefault;
|
||||||
import static de.siphalor.tweed5.data.extension.api.ReadWriteExtension.*;
|
import static de.siphalor.tweed5.data.extension.api.ReadWriteExtension.*;
|
||||||
import static de.siphalor.tweed5.data.extension.api.readwrite.TweedEntryReaderWriters.compoundReaderWriter;
|
import static de.siphalor.tweed5.data.extension.api.readwrite.TweedEntryReaderWriters.compoundReaderWriter;
|
||||||
import static de.siphalor.tweed5.data.extension.api.readwrite.TweedEntryReaderWriters.stringReaderWriter;
|
import static de.siphalor.tweed5.data.extension.api.readwrite.TweedEntryReaderWriters.stringReaderWriter;
|
||||||
import static de.siphalor.tweed5.testutils.MapTestUtils.sequencedMap;
|
import static de.siphalor.tweed5.testutils.generic.MapTestUtils.sequencedMap;
|
||||||
import static java.util.Map.entry;
|
import static java.util.Map.entry;
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.assertj.core.api.InstanceOfAssertFactories.map;
|
import static org.assertj.core.api.InstanceOfAssertFactories.map;
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ import java.util.Map;
|
|||||||
import static de.siphalor.tweed5.data.extension.api.ReadWriteExtension.entryReaderWriter;
|
import static de.siphalor.tweed5.data.extension.api.ReadWriteExtension.entryReaderWriter;
|
||||||
import static de.siphalor.tweed5.data.extension.api.readwrite.TweedEntryReaderWriters.*;
|
import static de.siphalor.tweed5.data.extension.api.readwrite.TweedEntryReaderWriters.*;
|
||||||
import static de.siphalor.tweed5.defaultextensions.comment.api.CommentExtension.baseComment;
|
import static de.siphalor.tweed5.defaultextensions.comment.api.CommentExtension.baseComment;
|
||||||
import static de.siphalor.tweed5.testutils.MapTestUtils.sequencedMap;
|
import static de.siphalor.tweed5.testutils.generic.MapTestUtils.sequencedMap;
|
||||||
import static java.util.Map.entry;
|
import static java.util.Map.entry;
|
||||||
import static org.junit.jupiter.api.Assertions.*;
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ import java.util.stream.Stream;
|
|||||||
import static de.siphalor.tweed5.data.extension.api.ReadWriteExtension.entryReaderWriter;
|
import static de.siphalor.tweed5.data.extension.api.ReadWriteExtension.entryReaderWriter;
|
||||||
import static de.siphalor.tweed5.data.extension.api.ReadWriteExtension.read;
|
import static de.siphalor.tweed5.data.extension.api.ReadWriteExtension.read;
|
||||||
import static de.siphalor.tweed5.data.extension.api.readwrite.TweedEntryReaderWriters.*;
|
import static de.siphalor.tweed5.data.extension.api.readwrite.TweedEntryReaderWriters.*;
|
||||||
import static de.siphalor.tweed5.testutils.MapTestUtils.sequencedMap;
|
import static de.siphalor.tweed5.testutils.generic.MapTestUtils.sequencedMap;
|
||||||
import static java.util.Map.entry;
|
import static java.util.Map.entry;
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.junit.jupiter.params.provider.Arguments.argumentSet;
|
import static org.junit.jupiter.params.provider.Arguments.argumentSet;
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ import static de.siphalor.tweed5.data.extension.api.ReadWriteExtension.read;
|
|||||||
import static de.siphalor.tweed5.data.extension.api.readwrite.TweedEntryReaderWriters.*;
|
import static de.siphalor.tweed5.data.extension.api.readwrite.TweedEntryReaderWriters.*;
|
||||||
import static de.siphalor.tweed5.defaultextensions.comment.api.CommentExtension.baseComment;
|
import static de.siphalor.tweed5.defaultextensions.comment.api.CommentExtension.baseComment;
|
||||||
import static de.siphalor.tweed5.defaultextensions.validation.api.ValidationExtension.validators;
|
import static de.siphalor.tweed5.defaultextensions.validation.api.ValidationExtension.validators;
|
||||||
import static de.siphalor.tweed5.testutils.MapTestUtils.sequencedMap;
|
import static de.siphalor.tweed5.testutils.generic.MapTestUtils.sequencedMap;
|
||||||
import static java.util.Map.entry;
|
import static java.util.Map.entry;
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.junit.jupiter.api.Assertions.*;
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ import java.util.function.Function;
|
|||||||
|
|
||||||
import static de.siphalor.tweed5.data.extension.api.ReadWriteExtension.entryReaderWriter;
|
import static de.siphalor.tweed5.data.extension.api.ReadWriteExtension.entryReaderWriter;
|
||||||
import static de.siphalor.tweed5.data.extension.api.readwrite.TweedEntryReaderWriters.*;
|
import static de.siphalor.tweed5.data.extension.api.readwrite.TweedEntryReaderWriters.*;
|
||||||
import static de.siphalor.tweed5.testutils.MapTestUtils.sequencedMap;
|
import static de.siphalor.tweed5.testutils.generic.MapTestUtils.sequencedMap;
|
||||||
import static java.util.Map.entry;
|
import static java.util.Map.entry;
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||||
|
|||||||
13
tweed5-serde-gson/build.gradle.kts
Normal file
13
tweed5-serde-gson/build.gradle.kts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
plugins {
|
||||||
|
id("de.siphalor.tweed5.base-module")
|
||||||
|
id("de.siphalor.tweed5.minecraft.mod.dummy")
|
||||||
|
id("de.siphalor.tweed5.shadow.explicit")
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation(project(":tweed5-serde-api"))
|
||||||
|
implementation(libs.gson)
|
||||||
|
|
||||||
|
testImplementation(project(":serde-json-test-utils"))
|
||||||
|
}
|
||||||
|
|
||||||
2
tweed5-serde-gson/gradle.properties
Normal file
2
tweed5-serde-gson/gradle.properties
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
minecraft.mod.name = Tweed 5 Gson
|
||||||
|
minecraft.mod.description = Tweed 5 module that adds support for reading and writing JSON using the Gson library.
|
||||||
@@ -0,0 +1,275 @@
|
|||||||
|
package de.siphaolor.tweed5.data.gson;
|
||||||
|
|
||||||
|
import com.google.gson.stream.JsonReader;
|
||||||
|
import de.siphalor.tweed5.dataapi.api.TweedDataReadException;
|
||||||
|
import de.siphalor.tweed5.dataapi.api.TweedDataReader;
|
||||||
|
import de.siphalor.tweed5.dataapi.api.TweedDataToken;
|
||||||
|
import de.siphalor.tweed5.dataapi.api.TweedDataTokens;
|
||||||
|
import org.jspecify.annotations.Nullable;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayDeque;
|
||||||
|
import java.util.Deque;
|
||||||
|
|
||||||
|
public class GsonReader implements TweedDataReader {
|
||||||
|
private final JsonReader reader;
|
||||||
|
|
||||||
|
private final Deque<Context> contextStack = new ArrayDeque<>();
|
||||||
|
|
||||||
|
private @Nullable TweedDataToken peekedToken;
|
||||||
|
|
||||||
|
public GsonReader(JsonReader reader) {
|
||||||
|
this.reader = reader;
|
||||||
|
contextStack.push(Context.VALUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TweedDataToken peekToken() throws TweedDataReadException {
|
||||||
|
if (peekedToken == null) {
|
||||||
|
peekedToken = nextToken();
|
||||||
|
}
|
||||||
|
return peekedToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TweedDataToken readToken() throws TweedDataReadException {
|
||||||
|
if (peekedToken != null) {
|
||||||
|
TweedDataToken token = peekedToken;
|
||||||
|
peekedToken = null;
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
return nextToken();
|
||||||
|
}
|
||||||
|
|
||||||
|
private TweedDataToken nextToken() throws TweedDataReadException {
|
||||||
|
try {
|
||||||
|
switch (reader.peek()) {
|
||||||
|
case BEGIN_ARRAY: {
|
||||||
|
reader.beginArray();
|
||||||
|
TweedDataToken token = wrapToken(TweedDataTokens.getListStart());
|
||||||
|
contextStack.push(Context.LIST);
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
case END_ARRAY: {
|
||||||
|
reader.endArray();
|
||||||
|
popContext(Context.LIST);
|
||||||
|
TweedDataToken token = wrapToken(TweedDataTokens.getListEnd());
|
||||||
|
afterValueRead();
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
case BEGIN_OBJECT: {
|
||||||
|
reader.beginObject();
|
||||||
|
TweedDataToken token = wrapToken(TweedDataTokens.getMapStart());
|
||||||
|
contextStack.push(Context.MAP);
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
case END_OBJECT: {
|
||||||
|
reader.endObject();
|
||||||
|
popContext(Context.MAP);
|
||||||
|
TweedDataToken token = wrapToken(TweedDataTokens.getMapEnd());
|
||||||
|
afterValueRead();
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
case NAME:
|
||||||
|
contextStack.push(Context.MAP_ENTRY_VALUE);
|
||||||
|
return TweedDataTokens.asMapEntryKey(createStringToken(reader.nextName()));
|
||||||
|
case NULL: {
|
||||||
|
reader.nextNull();
|
||||||
|
TweedDataToken token = wrapToken(TweedDataTokens.getNull());
|
||||||
|
afterValueRead();
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
case BOOLEAN: {
|
||||||
|
boolean value = reader.nextBoolean();
|
||||||
|
TweedDataToken token = wrapToken(new TweedDataToken() {
|
||||||
|
@Override
|
||||||
|
public boolean canReadAsBoolean() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean readAsBoolean() throws TweedDataReadException {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
afterValueRead();
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
case NUMBER: {
|
||||||
|
Long longValue = tryReadLong();
|
||||||
|
TweedDataToken token;
|
||||||
|
if (longValue != null) {
|
||||||
|
token = wrapToken(new TweedDataToken() {
|
||||||
|
@Override
|
||||||
|
public boolean canReadAsByte() {
|
||||||
|
return longValue >= Byte.MIN_VALUE && longValue <= Byte.MAX_VALUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte readAsByte() {
|
||||||
|
return longValue.byteValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean canReadAsShort() {
|
||||||
|
return longValue >= Short.MIN_VALUE && longValue <= Short.MAX_VALUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public short readAsShort() {
|
||||||
|
return longValue.shortValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean canReadAsInt() {
|
||||||
|
return longValue >= Integer.MIN_VALUE && longValue <= Integer.MAX_VALUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int readAsInt() {
|
||||||
|
return longValue.intValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean canReadAsLong() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long readAsLong() {
|
||||||
|
return longValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean canReadAsFloat() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public float readAsFloat() {
|
||||||
|
return longValue.floatValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean canReadAsDouble() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public double readAsDouble() {
|
||||||
|
return longValue.doubleValue();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
double doubleValue = reader.nextDouble();
|
||||||
|
token = wrapToken(new TweedDataToken() {
|
||||||
|
@Override
|
||||||
|
public boolean canReadAsFloat() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public float readAsFloat() {
|
||||||
|
return (float) doubleValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean canReadAsDouble() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public double readAsDouble() {
|
||||||
|
return doubleValue;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
afterValueRead();
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
case STRING: {
|
||||||
|
TweedDataToken token = wrapToken(createStringToken(reader.nextString()));
|
||||||
|
afterValueRead();
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
throw TweedDataReadException.builder()
|
||||||
|
.message("Encountered unexpected " + peekedToken + " token at " + reader.getPath())
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw TweedDataReadException.builder()
|
||||||
|
.message("Error reading data using gson at " + reader.getPath())
|
||||||
|
.cause(e)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private @Nullable Long tryReadLong() throws IOException {
|
||||||
|
try {
|
||||||
|
return reader.nextLong();
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private TweedDataToken wrapToken(TweedDataToken token) throws TweedDataReadException {
|
||||||
|
switch (peekContext()) {
|
||||||
|
case MAP_ENTRY_VALUE:
|
||||||
|
return TweedDataTokens.asMapEntryValue(token);
|
||||||
|
case LIST:
|
||||||
|
return TweedDataTokens.asListValue(token);
|
||||||
|
default:
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void afterValueRead() throws TweedDataReadException {
|
||||||
|
Context context = peekContext();
|
||||||
|
switch (context) {
|
||||||
|
case MAP_ENTRY_VALUE:
|
||||||
|
case VALUE:
|
||||||
|
popContext(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Context peekContext() throws TweedDataReadException {
|
||||||
|
Context context = contextStack.peek();
|
||||||
|
if (context == null) {
|
||||||
|
throw TweedDataReadException.builder()
|
||||||
|
.message("Tried to read context but currently not in any context")
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void popContext(Context expectedContext) throws TweedDataReadException {
|
||||||
|
Context context = contextStack.pop();
|
||||||
|
if (context != expectedContext) {
|
||||||
|
throw TweedDataReadException.builder()
|
||||||
|
.message("Unexpected context " + context + " when popping " + expectedContext)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private TweedDataToken createStringToken(String value) {
|
||||||
|
return new TweedDataToken() {
|
||||||
|
@Override
|
||||||
|
public boolean canReadAsString() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String readAsString() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum Context {
|
||||||
|
VALUE,
|
||||||
|
LIST,
|
||||||
|
MAP,
|
||||||
|
MAP_ENTRY_VALUE,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,214 @@
|
|||||||
|
package de.siphaolor.tweed5.data.gson;
|
||||||
|
|
||||||
|
import com.google.gson.stream.JsonWriter;
|
||||||
|
import de.siphalor.tweed5.dataapi.api.TweedDataVisitor;
|
||||||
|
import de.siphalor.tweed5.dataapi.api.TweedDataWriteException;
|
||||||
|
import de.siphalor.tweed5.dataapi.api.decoration.TweedDataCommentDecoration;
|
||||||
|
import de.siphalor.tweed5.dataapi.api.decoration.TweedDataDecoration;
|
||||||
|
import org.jspecify.annotations.Nullable;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayDeque;
|
||||||
|
import java.util.Deque;
|
||||||
|
|
||||||
|
public class GsonWriter implements TweedDataVisitor {
|
||||||
|
private final JsonWriter writer;
|
||||||
|
|
||||||
|
private final Deque<Context> contextStack = new ArrayDeque<>();
|
||||||
|
|
||||||
|
private @Nullable String deferredFieldComment;
|
||||||
|
|
||||||
|
public GsonWriter(JsonWriter writer) {
|
||||||
|
this.writer = writer;
|
||||||
|
this.contextStack.push(Context.VALUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void visitNull() {
|
||||||
|
try {
|
||||||
|
writer.nullValue();
|
||||||
|
afterValueWritten();
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw createWriteExceptionFromIoException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void visitBoolean(boolean value) {
|
||||||
|
try {
|
||||||
|
writer.value(value);
|
||||||
|
afterValueWritten();
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw createWriteExceptionFromIoException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void visitByte(byte value) {
|
||||||
|
try {
|
||||||
|
writer.value(value);
|
||||||
|
afterValueWritten();
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw createWriteExceptionFromIoException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void visitShort(short value) {
|
||||||
|
try {
|
||||||
|
writer.value(value);
|
||||||
|
afterValueWritten();
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw createWriteExceptionFromIoException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void visitInt(int value) {
|
||||||
|
try {
|
||||||
|
writer.value(value);
|
||||||
|
afterValueWritten();
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw createWriteExceptionFromIoException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void visitLong(long value) {
|
||||||
|
try {
|
||||||
|
writer.value(value);
|
||||||
|
afterValueWritten();
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw createWriteExceptionFromIoException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void visitFloat(float value) {
|
||||||
|
try {
|
||||||
|
writer.value(value);
|
||||||
|
afterValueWritten();
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw createWriteExceptionFromIoException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void visitDouble(double value) {
|
||||||
|
try {
|
||||||
|
writer.value(value);
|
||||||
|
afterValueWritten();
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw createWriteExceptionFromIoException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void visitString(String value) {
|
||||||
|
try {
|
||||||
|
writer.value(value);
|
||||||
|
afterValueWritten();
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw createWriteExceptionFromIoException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void visitListStart() {
|
||||||
|
try {
|
||||||
|
writer.beginArray();
|
||||||
|
contextStack.push(Context.LIST);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw createWriteExceptionFromIoException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void visitListEnd() {
|
||||||
|
try {
|
||||||
|
writer.endArray();
|
||||||
|
popContext(Context.LIST);
|
||||||
|
afterValueWritten();
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw createWriteExceptionFromIoException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void visitMapStart() {
|
||||||
|
try {
|
||||||
|
writer.beginObject();
|
||||||
|
contextStack.push(Context.MAP);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw createWriteExceptionFromIoException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void visitMapEntryKey(String key) {
|
||||||
|
try {
|
||||||
|
if (deferredFieldComment != null) {
|
||||||
|
writer.name(key + "__comment");
|
||||||
|
writer.value(deferredFieldComment);
|
||||||
|
deferredFieldComment = null;
|
||||||
|
}
|
||||||
|
writer.name(key);
|
||||||
|
contextStack.push(Context.VALUE);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw createWriteExceptionFromIoException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void visitMapEnd() {
|
||||||
|
try {
|
||||||
|
writer.endObject();
|
||||||
|
popContext(Context.MAP);
|
||||||
|
afterValueWritten();
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw createWriteExceptionFromIoException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void visitDecoration(TweedDataDecoration decoration) {
|
||||||
|
if (decoration instanceof TweedDataCommentDecoration) {
|
||||||
|
if (deferredFieldComment == null) {
|
||||||
|
deferredFieldComment = ((TweedDataCommentDecoration) decoration).comment();
|
||||||
|
} else {
|
||||||
|
deferredFieldComment += "\n" + ((TweedDataCommentDecoration) decoration).comment();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void afterValueWritten() {
|
||||||
|
if (peekContext() == Context.VALUE) {
|
||||||
|
contextStack.pop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void popContext(Context expectedContext) {
|
||||||
|
Context context = contextStack.pop();
|
||||||
|
if (context != expectedContext) {
|
||||||
|
throw new TweedDataWriteException("Unexpected context " + context + " when popping " + expectedContext);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Context peekContext() {
|
||||||
|
Context context = contextStack.peek();
|
||||||
|
if (context == null) {
|
||||||
|
throw new TweedDataWriteException("Tried to read context but currently not in any context");
|
||||||
|
}
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
private TweedDataWriteException createWriteExceptionFromIoException(IOException e) {
|
||||||
|
return new TweedDataWriteException("Error writing data using Gson", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum Context {
|
||||||
|
VALUE,
|
||||||
|
LIST,
|
||||||
|
MAP,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
@NullMarked
|
||||||
|
package de.siphaolor.tweed5.data.gson;
|
||||||
|
|
||||||
|
import org.jspecify.annotations.NullMarked;
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package de.siphaolor.tweed5.data.gson;
|
||||||
|
|
||||||
|
import com.google.gson.GsonBuilder;
|
||||||
|
import com.google.gson.stream.JsonReader;
|
||||||
|
import de.siphalor.tweed5.dataapi.api.TweedDataReader;
|
||||||
|
import de.siphalor.tweed5.testutils.serde.json.JsonReaderTest;
|
||||||
|
|
||||||
|
import java.io.StringReader;
|
||||||
|
|
||||||
|
class GsonReaderTest implements JsonReaderTest {
|
||||||
|
@Override
|
||||||
|
public TweedDataReader createJsonReader(String text) {
|
||||||
|
JsonReader jsonReader = new GsonBuilder().create().newJsonReader(new StringReader(text));
|
||||||
|
return new GsonReader(jsonReader);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
package de.siphaolor.tweed5.data.gson;
|
||||||
|
|
||||||
|
import com.google.gson.GsonBuilder;
|
||||||
|
import de.siphalor.tweed5.dataapi.api.decoration.TweedDataCommentDecoration;
|
||||||
|
import lombok.SneakyThrows;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.io.StringWriter;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
class GsonWriterTest {
|
||||||
|
|
||||||
|
@SneakyThrows
|
||||||
|
@Test
|
||||||
|
void complex() {
|
||||||
|
var stringWriter = new StringWriter();
|
||||||
|
var writer = new GsonWriter(new GsonBuilder().setPrettyPrinting().create().newJsonWriter(stringWriter));
|
||||||
|
|
||||||
|
writer.visitMapStart();
|
||||||
|
|
||||||
|
writer.visitMapEntryKey("first");
|
||||||
|
writer.visitListStart();
|
||||||
|
writer.visitInt(123);
|
||||||
|
writer.visitListStart();
|
||||||
|
writer.visitBoolean(false);
|
||||||
|
writer.visitListEnd();
|
||||||
|
writer.visitListEnd();
|
||||||
|
|
||||||
|
writer.visitDecoration((TweedDataCommentDecoration) () -> "Hello");
|
||||||
|
writer.visitDecoration((TweedDataCommentDecoration) () -> "World");
|
||||||
|
writer.visitMapEntryKey("second");
|
||||||
|
writer.visitMapStart();
|
||||||
|
writer.visitMapEntryKey("nested");
|
||||||
|
writer.visitDouble(12.34);
|
||||||
|
writer.visitMapEnd();
|
||||||
|
|
||||||
|
writer.visitMapEnd();
|
||||||
|
|
||||||
|
assertThat(stringWriter.toString()).isEqualTo("""
|
||||||
|
{
|
||||||
|
"first": [
|
||||||
|
123,
|
||||||
|
[
|
||||||
|
false
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"second__comment": "Hello\\nWorld",
|
||||||
|
"second": {
|
||||||
|
"nested": 12.34
|
||||||
|
}
|
||||||
|
}""");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,6 +8,8 @@ dependencies {
|
|||||||
implementation(project(":tweed5-serde-api"))
|
implementation(project(":tweed5-serde-api"))
|
||||||
implementation(libs.jackson.core)
|
implementation(libs.jackson.core)
|
||||||
shadowOnly(libs.jackson.core)
|
shadowOnly(libs.jackson.core)
|
||||||
|
|
||||||
|
testImplementation(project(":serde-json-test-utils"))
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.shadowJar {
|
tasks.shadowJar {
|
||||||
|
|||||||
@@ -2,77 +2,21 @@ package de.siphalor.tweed5.data.jackson;
|
|||||||
|
|
||||||
import com.fasterxml.jackson.core.JsonFactory;
|
import com.fasterxml.jackson.core.JsonFactory;
|
||||||
import com.fasterxml.jackson.core.StreamReadFeature;
|
import com.fasterxml.jackson.core.StreamReadFeature;
|
||||||
|
import de.siphalor.tweed5.dataapi.api.TweedDataReader;
|
||||||
|
import de.siphalor.tweed5.testutils.serde.json.JsonReaderTest;
|
||||||
import lombok.SneakyThrows;
|
import lombok.SneakyThrows;
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
class JacksonReaderTest implements JsonReaderTest {
|
||||||
|
|
||||||
class JacksonReaderTest {
|
|
||||||
@SneakyThrows
|
@SneakyThrows
|
||||||
@Test
|
@Override
|
||||||
void complex() {
|
public TweedDataReader createJsonReader(String text) {
|
||||||
var inputStream = new ByteArrayInputStream("""
|
var parser = JsonFactory.builder()
|
||||||
{
|
.enable(StreamReadFeature.INCLUDE_SOURCE_IN_LOCATION)
|
||||||
"first": [
|
.build()
|
||||||
[ 1 ]
|
.createParser(new ByteArrayInputStream(text.getBytes(StandardCharsets.UTF_8)));
|
||||||
],
|
return new JacksonReader(parser);
|
||||||
"second": {
|
|
||||||
"test": "Hello World!"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
""".getBytes(StandardCharsets.UTF_8));
|
|
||||||
try (var parser = JsonFactory.builder().enable(StreamReadFeature.INCLUDE_SOURCE_IN_LOCATION).build().createParser(inputStream)) {
|
|
||||||
var reader = new JacksonReader(parser);
|
|
||||||
|
|
||||||
var token = reader.peekToken();
|
|
||||||
assertThat(token.isMapStart()).isTrue();
|
|
||||||
token = reader.readToken();
|
|
||||||
assertThat(token.isMapStart()).isTrue();
|
|
||||||
token = reader.readToken();
|
|
||||||
assertThat(token.isMapEntryKey()).isTrue();
|
|
||||||
assertThat(token.canReadAsString()).isTrue();
|
|
||||||
assertThat(token.readAsString()).isEqualTo("first");
|
|
||||||
token = reader.readToken();
|
|
||||||
assertThat(token.isMapEntryValue()).isTrue();
|
|
||||||
assertThat(token.isListStart()).isTrue();
|
|
||||||
token = reader.readToken();
|
|
||||||
assertThat(token.isMapEntryValue()).isFalse();
|
|
||||||
assertThat(token.isListValue()).isTrue();
|
|
||||||
assertThat(token.isListStart()).isTrue();
|
|
||||||
token = reader.readToken();
|
|
||||||
assertThat(token.isListValue()).isTrue();
|
|
||||||
assertThat(token.canReadAsInt()).isTrue();
|
|
||||||
assertThat(token.readAsInt()).isEqualTo(1);
|
|
||||||
token = reader.readToken();
|
|
||||||
assertThat(token.isListValue()).isTrue();
|
|
||||||
assertThat(token.isListEnd()).isTrue();
|
|
||||||
token = reader.readToken();
|
|
||||||
assertThat(token.isListValue()).isFalse();
|
|
||||||
assertThat(token.isMapEntryValue()).isTrue();
|
|
||||||
assertThat(token.isListEnd()).isTrue();
|
|
||||||
token = reader.readToken();
|
|
||||||
assertThat(token.isMapEntryKey()).isTrue();
|
|
||||||
assertThat(token.canReadAsString()).isTrue();
|
|
||||||
assertThat(token.readAsString()).isEqualTo("second");
|
|
||||||
token = reader.readToken();
|
|
||||||
assertThat(token.isMapEntryValue()).isTrue();
|
|
||||||
assertThat(token.isMapStart()).isTrue();
|
|
||||||
token = reader.readToken();
|
|
||||||
assertThat(token.isMapEntryValue()).isFalse();
|
|
||||||
assertThat(token.isMapEntryKey()).isTrue();
|
|
||||||
assertThat(token.canReadAsString()).isTrue();
|
|
||||||
assertThat(token.readAsString()).isEqualTo("test");
|
|
||||||
token = reader.readToken();
|
|
||||||
assertThat(token.isMapEntryValue()).isTrue();
|
|
||||||
assertThat(token.canReadAsString()).isTrue();
|
|
||||||
assertThat(token.readAsString()).isEqualTo("Hello World!");
|
|
||||||
token = reader.readToken();
|
|
||||||
assertThat(token.isMapEnd()).isTrue();
|
|
||||||
token = reader.readToken();
|
|
||||||
assertThat(token.isMapEnd()).isTrue();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user