[serde-gson] Support for Gson readers and writers

This commit is contained in:
2025-08-03 20:21:33 +02:00
parent c58b806bcf
commit 1fc418970f
23 changed files with 710 additions and 82 deletions

View File

@@ -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 {

View File

@@ -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())
}

View File

@@ -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" }

View File

@@ -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)

View File

@@ -1,8 +0,0 @@
plugins {
java
}
java {
sourceCompatibility = JavaVersion.toVersion(libs.versions.java.test.get())
targetCompatibility = JavaVersion.toVersion(libs.versions.java.test.get())
}

View File

@@ -0,0 +1,3 @@
plugins {
id("de.siphalor.tweed5.test-utils")
}

View File

@@ -1,4 +1,4 @@
package de.siphalor.tweed5.testutils; package de.siphalor.tweed5.testutils.generic;
import java.util.*; import java.util.*;

View File

@@ -0,0 +1,3 @@
plugins {
id("de.siphalor.tweed5.test-utils")
}

View File

@@ -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();
}
}

View File

@@ -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;

View File

@@ -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.*;

View File

@@ -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;

View File

@@ -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.*;

View File

@@ -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;

View 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"))
}

View 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.

View File

@@ -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,
}
}

View File

@@ -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,
}
}

View File

@@ -0,0 +1,4 @@
@NullMarked
package de.siphaolor.tweed5.data.gson;
import org.jspecify.annotations.NullMarked;

View File

@@ -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);
}
}

View File

@@ -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
}
}""");
}
}

View File

@@ -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 {

View File

@@ -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();
}
} }
} }