Skip to content

Commit

Permalink
DSL-JSON v1.8.2
Browse files Browse the repository at this point in the history
Improved Kotlin support (fix JsonObject detection by probing into Companion field).
Improved collection support (more collections work without runtime - JsonObject and known converters).

TODO: further improve collection support (known objects can be lazily initialized in the same way).
  • Loading branch information
zapov committed Sep 21, 2018
1 parent 179fbca commit 8783d39
Show file tree
Hide file tree
Showing 11 changed files with 333 additions and 38 deletions.
8 changes: 6 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -187,8 +187,9 @@ To use such feature @JsonValue annotation must be placed on method or field.

### JSON pretty print

Library was designed for speed and does not support human formatted output such as alignment and newlines.
But when such output is required, there is `PrettifyStream` for converting JSON input into formatted JSON output.
Formatted output with alignments and newlines can be created via `PrettifyOutputStream`.

dslJson.serialize(instance, new PrettifyOutputStream(outputStream));

### External annotations

Expand Down Expand Up @@ -367,5 +368,8 @@ When used with Gradle, configuration can be done via:
***Q***: DSL Platform annotation processor checks for new DSL compiler version on every compilation. How can I disable that?
***A***: If you specify custom `dsljson.compiler` processor option or put `dsl-compiler.exe` in project root it will use that one and will not check online for updates

***Q***: When using Android with desugaring I get: `Exception in thread "main" java.lang.IllegalArgumentException: Type without superclass: module-info`. How can I fix that?
***A***: It's a known Android Studio problem. It's fixed in [version 3.3](https://androidstudio.googleblog.com/2018/06/android-studio-33-canary-1-available.html)

***Q***: What is this DSL Platform?
***A***: DSL Platform is a proprietary compiler written in C#. Since v1.7.0 DSL Platform is no longer required to create compile-time databinding. Compiler is free to use, but access to source code is licensed. If you need access to the compiler or need performance consulting [let us know](https://dsl-platform.com)
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ public final class PrettifyOutputStream extends OutputStream {
private static final int INDENT_CACHE_SIZE = 257;

private static final boolean[] WHITESPACE = new boolean[256];

static {
WHITESPACE[9] = true;
WHITESPACE[10] = true;
Expand Down Expand Up @@ -97,7 +98,7 @@ public final void write(final byte[] bytes, final int off, final int len) throws
}
out.write(b);
start = i + 1;
} else if(WHITESPACE[b]) {
} else if (WHITESPACE[b]) {
out.write(bytes, start, i - start);
start = i + 1;
} else if (beginObjectOrList) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ private void asFormatConverter(final StructInfo si, final String name, final Str
boolean hasConverter = context.inlinedConverters.containsKey(typeName);
StructInfo target = context.structs.get(attr.typeName);
if (attr.converter == null && (target == null || target.converter == null) && !hasConverter && !isStaticEnum(attr) && !attr.isJsonObject) {
List<String> types = attr.collectionContent(context.knownTypes);
List<String> types = attr.collectionContent(context.knownTypes, context.structs);
if (target != null && attr.isEnum(context.structs)) {
code.append("\t\tprivate final ").append(findConverterName(target)).append(".EnumConverter converter_").append(attr.name).append(";\n");
} else if (types != null && types.size() == 1 || (attr.isGeneric && !attr.containsStructOwnerType)) {
Expand Down Expand Up @@ -187,7 +187,7 @@ private void asFormatConverter(final StructInfo si, final String name, final Str
for (AttributeInfo attr : si.attributes.values()) {
String typeName = attr.type.toString();
boolean hasConverter = context.inlinedConverters.containsKey(typeName);
List<String> types = attr.collectionContent(context.knownTypes);
List<String> types = attr.collectionContent(context.knownTypes, context.structs);
StructInfo target = context.structs.get(attr.typeName);
if (attr.converter == null && (target == null || target.converter == null) && !hasConverter && !isStaticEnum(attr) && !attr.isJsonObject) {
if (target != null && attr.isEnum(context.structs)) {
Expand Down Expand Up @@ -649,7 +649,7 @@ private void writeProperty(AttributeInfo attr, boolean checkedDefault) throws IO
code.append(optimizedConverter.nonNullableEncoder("writer", readValue)).append(";\n");
} else if (target != null && attr.isEnum(context.structs)) {
enumTemplate.writeName(code, target, readValue, "converter_" + attr.name);
} else if (attr.collectionContent(context.knownTypes) != null) {
} else if (attr.collectionContent(context.knownTypes, context.structs) != null) {
code.append("writer.serialize(").append(readValue);
if (attr.isMap) {
code.append(", key_writer_").append(attr.name).append(", value_writer_").append(attr.name).append(");\n");
Expand Down Expand Up @@ -752,7 +752,7 @@ private void setPropertyValue(AttributeInfo attr, String alignment) throws IOExc
} else {
code.append("converter_").append(attr.name).append(".read(reader)");
}
} else if (attr.collectionContent(context.knownTypes) != null) {
} else if (attr.collectionContent(context.knownTypes, context.structs) != null) {
context.serializeKnownCollection(attr);
} else if (attr.isGeneric && !attr.containsStructOwnerType) {
if (attr.isArray) {
Expand Down Expand Up @@ -804,7 +804,7 @@ private void readPropertyValue(AttributeInfo attr, String alignment) throws IOEx
} else {
code.append("converter_").append(attr.name).append(".read(reader)");
}
} else if (attr.collectionContent(context.knownTypes) != null) {
} else if (attr.collectionContent(context.knownTypes, context.structs) != null) {
context.serializeKnownCollection(attr);
code.append(";\n");
} else if (attr.isGeneric && !attr.containsStructOwnerType) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,12 @@ void writeName(Writer code, StructInfo target, String readValue, String writerNa
private void writeName(Writer code, StructInfo target, String readValue, String writerName, boolean external) throws IOException {
if (target.enumConstantNameSource != null) {
String constantNameType = extractReturnType(target.enumConstantNameSource);
StructInfo info = context.structs.get(constantNameType);
OptimizedConverter converter = context.inlinedConverters.get(constantNameType);
String value = readValue + "." + target.enumConstantNameSource;
if (converter != null) {
if (info != null && info.converter != null) {
code.append(info.converter.fullName).append(".").append(info.converter.writer).append(".write(writer, ").append(value).append(");\n");
} else if (converter != null) {
code.append(converter.nonNullableEncoder("writer", value)).append(";\n");
} else {
code.append(writerName).append(".write(writer, ").append(external ? readValue : value).append(");\n");
Expand All @@ -51,15 +54,18 @@ boolean isStatic(final StructInfo si) {
if (si.enumConstantNameSource == null) return true;
String constantNameType = extractReturnType(si.enumConstantNameSource);
if (constantNameType == null) return true;
return context.inlinedConverters.get(constantNameType) != null;
StructInfo info = context.structs.get(constantNameType);
return info != null && info.converter != null
|| context.inlinedConverters.get(constantNameType) != null;
}

void create(final StructInfo si, final String className) throws IOException {
code.append("\tpublic final static class EnumConverter implements com.dslplatform.json.JsonWriter.WriteObject<");
code.append(className);
code.append(">, com.dslplatform.json.JsonReader.ReadObject<").append(className).append("> {\n");
String constantNameType = extractReturnType(si.enumConstantNameSource);
OptimizedConverter converter = constantNameType != null ? context.inlinedConverters.get(constantNameType) : null;
StructInfo info = constantNameType != null ? context.structs.get(constantNameType) : null;
OptimizedConverter optimizedConverter = constantNameType != null ? context.inlinedConverters.get(constantNameType) : null;
if (constantNameType != null) {
code.append("\t\tprivate static final java.util.Map<").append(constantNameType).append(", ").append(className).append("> values;\n");
code.append("\t\tstatic {\n");
Expand All @@ -68,7 +74,7 @@ void create(final StructInfo si, final String className) throws IOException {
code.append("\t\t\t\tvalues.put(value.").append(si.enumConstantNameSource.toString()).append(", value);\n");
code.append("\t\t\t}\n");
code.append("\t\t}\n");
if (converter == null) {
if (optimizedConverter == null && (info == null || info.converter == null)) {
code.append("\t\tprivate final com.dslplatform.json.JsonWriter.WriteObject<").append(constantNameType).append("> valueWriter;\n");
code.append("\t\tprivate final com.dslplatform.json.JsonReader.ReadObject<").append(constantNameType).append("> valueReader;\n");
code.append("\t\tpublic EnumConverter(com.dslplatform.json.DslJson<Object> __dsljson) {\n");
Expand All @@ -95,8 +101,11 @@ void create(final StructInfo si, final String className) throws IOException {
code.append("\t\tpublic static ").append(className).append(" readStatic(final com.dslplatform.json.JsonReader reader) throws java.io.IOException {\n");
}
if (constantNameType != null) {
if (converter != null) {
code.append("\t\t\tfinal ").append(constantNameType).append(" input = ").append(converter.nonNullableDecoder()).append("(reader);\n");
if (info != null && info.converter != null) {
code.append("\t\t\tfinal ").append(constantNameType).append(" input = ").append(info.converter.fullName).append(".").append(info.converter.reader).append(".read(reader);\n");
code.append("\t\t\t").append(className).append(" value = ").append("values.get(input);\n");
} else if (optimizedConverter != null) {
code.append("\t\t\tfinal ").append(constantNameType).append(" input = ").append(optimizedConverter.nonNullableDecoder()).append("(reader);\n");
code.append("\t\t\t").append(className).append(" value = ").append("values.get(input);\n");
} else {
code.append("\t\t\tfinal ").append(constantNameType).append(" input = valueReader.read(reader);\n");
Expand All @@ -107,7 +116,11 @@ void create(final StructInfo si, final String className) throws IOException {
code.append("\t\t\t\tvalue = ").append(className).append(".").append(si.constants.get(0)).append(";\n");
} else {
code.append("\t\t\t\tthrow new java.lang.IllegalArgumentException(\"No enum constant ");
code.append(className).append(" associated with value '\" + input + \"'\");\n");
code.append(className).append(" associated with value '\" + input + \"'");
if (info != null && info.converter != null) {
code.append(". When using custom objects check that custom hashCode and equals are implemented");
}
code.append("\");\n");
}
code.append("\t\t\t}\n");
code.append("\t\t\treturn value;\n");
Expand Down
161 changes: 161 additions & 0 deletions java8/src/test/java/com/dslplatform/json/JsonObjectTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
package com.dslplatform.json;

import com.dslplatform.json.runtime.Settings;
import org.junit.Assert;
import org.junit.Test;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

public class JsonObjectTest {

public static class ObjectModel {
public JsonObjectReference jsonObject;
}

public static class ImmutableObjectModel {
public final JsonObjectReference jsonObject;
public final JsonObjectReference jsonObjectNonNull;
public ImmutableObjectModel(JsonObjectReference jsonObject, JsonObjectReference jsonObjectNonNull) {
this.jsonObject = jsonObject;
this.jsonObjectNonNull = jsonObjectNonNull;
}
}

public static class ListModel {
public List<JsonObjectReference> jsonObjects;
}

public static class CombinedModel {
public JsonObjectReference jsonObject;
public List<JsonObjectReference> jsonObjects;
public Map<Integer, JsonObjectReference> mapObjects;
}

public static class JsonObjectReference implements JsonObject {
private final String x;
public JsonObjectReference(String x) {
this.x = x;
}
public void serialize(JsonWriter writer, boolean minimal) {
writer.writeAscii("{\"x\":");
writer.writeString(x);
writer.writeAscii("}");
}

public static final JsonReader.ReadJsonObject<JsonObjectReference> JSON_READER = reader -> {
reader.fillName();
reader.getNextToken();
String x1 = reader.readString();
reader.getNextToken();
return new JsonObjectReference(x1);
};

@Override
public boolean equals(Object obj) {
return ((JsonObjectReference)obj).x.equals(this.x);
}
}

public static class KotlinObjectModel {
public final JsonObjectReferenceKotlin jsonObject;
public KotlinObjectModel(JsonObjectReferenceKotlin jsonObject) {
this.jsonObject = jsonObject;
}
}

public static class JsonObjectReferenceKotlin implements JsonObject {
private final String x;
public JsonObjectReferenceKotlin(String x) {
this.x = x;
}
public void serialize(JsonWriter writer, boolean minimal) {
writer.writeAscii("{\"x\":");
writer.writeString(x);
writer.writeAscii("}");
}

public static final Companion Companion = new Companion();
public static class Companion {
public JsonReader.ReadJsonObject<JsonObjectReferenceKotlin> getJSON_READER() {
return reader -> {
reader.fillName();
reader.getNextToken();
String x1 = reader.readString();
reader.getNextToken();
return new JsonObjectReferenceKotlin(x1);
};
}
}

@Override
public boolean equals(Object obj) {
return ((JsonObjectReferenceKotlin)obj).x.equals(this.x);
}
}

private final DslJson<Object> dslJson = new DslJson<>(Settings.basicSetup());

@Test
public void simpleTest() throws IOException {
ObjectModel m = new ObjectModel();
m.jsonObject = new JsonObjectReference("test");
ByteArrayOutputStream os = new ByteArrayOutputStream();
dslJson.serialize(m, os);
Assert.assertEquals("{\"jsonObject\":{\"x\":\"test\"}}", os.toString());
ObjectModel res = dslJson.deserialize(ObjectModel.class, os.toByteArray(), os.size());
Assert.assertEquals(m.jsonObject, res.jsonObject);
}

@Test
public void immutableTest() throws IOException {
ImmutableObjectModel m = new ImmutableObjectModel(
null,
new JsonObjectReference("test2"));
ByteArrayOutputStream os = new ByteArrayOutputStream();
dslJson.serialize(m, os);
Assert.assertEquals("{\"jsonObject\":null,\"jsonObjectNonNull\":{\"x\":\"test2\"}}", os.toString());
ImmutableObjectModel res = dslJson.deserialize(ImmutableObjectModel.class, os.toByteArray(), os.size());
Assert.assertNull(res.jsonObject);
Assert.assertEquals(m.jsonObjectNonNull, res.jsonObjectNonNull);
}

@Test
public void collectionTest() throws IOException {
ListModel m = new ListModel();
m.jsonObjects = Arrays.asList(new JsonObjectReference("test"), null, new JsonObjectReference("xxx"));
ByteArrayOutputStream os = new ByteArrayOutputStream();
dslJson.serialize(m, os);
Assert.assertEquals("{\"jsonObjects\":[{\"x\":\"test\"},null,{\"x\":\"xxx\"}]}", os.toString());
ListModel res = dslJson.deserialize(ListModel.class, os.toByteArray(), os.size());
Assert.assertEquals(m.jsonObjects, res.jsonObjects);
}

@Test
public void complexTest() throws IOException {
CombinedModel m = new CombinedModel();
m.jsonObject = new JsonObjectReference("test");
m.jsonObjects = Arrays.asList(new JsonObjectReference("abc"), null, new JsonObjectReference("xxx"));
m.mapObjects = new LinkedHashMap<Integer, JsonObjectReference>() {{ put(1, null); put(2, new JsonObjectReference("xXx")); }};
ByteArrayOutputStream os = new ByteArrayOutputStream();
dslJson.serialize(m, os);
CombinedModel res = dslJson.deserialize(CombinedModel.class, os.toByteArray(), os.size());
Assert.assertEquals(m.jsonObject, res.jsonObject);
Assert.assertEquals(m.jsonObjects, res.jsonObjects);
Assert.assertEquals(m.mapObjects, res.mapObjects);
}

@Test
public void kotlinTest() throws IOException {
KotlinObjectModel m = new KotlinObjectModel(new JsonObjectReferenceKotlin("test"));
ByteArrayOutputStream os = new ByteArrayOutputStream();
dslJson.serialize(m, os);
Assert.assertEquals("{\"jsonObject\":{\"x\":\"test\"}}", os.toString());
KotlinObjectModel res = dslJson.deserialize(KotlinObjectModel.class, os.toByteArray(), os.size());
Assert.assertEquals(m.jsonObject, res.jsonObject);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,18 +21,22 @@ public static class Bytes {
public static class JsonSerialization extends DslJson<Object> {

private final ByteArrayOutputStream psOut = new ByteArrayOutputStream();
private final PrettifyOutputStream prettyOut = new PrettifyOutputStream(psOut);

public JsonSerialization() {
super(new Settings<>().includeServiceLoader());
}
private ByteArrayOutputStream stream = new ByteArrayOutputStream();
private final PrettifyOutputStream prettyStream = new PrettifyOutputStream(psOut);

public Bytes serialize(Object instance) throws IOException {
stream.reset();
super.serialize(instance, stream);
Bytes b = new Bytes();
b.content = stream.toByteArray();
b.length = b.content.length;
stream.reset();
super.serialize(instance, prettyStream);
return b;
}

Expand All @@ -42,8 +46,7 @@ public <TResult> TResult deserialize(
final int size) throws IOException {
TResult res1 = super.deserialize(manifest, body, size);
psOut.reset();
PrettifyOutputStream prettifyStream = new PrettifyOutputStream(psOut);
prettifyStream.write(body, 0, size);
prettyOut.write(body, 0, size);
super.deserialize(manifest, psOut.toByteArray(), psOut.size());
return res1;
}
Expand Down
Loading

0 comments on commit 8783d39

Please sign in to comment.