Skip to content

Commit

Permalink
Use ClassValue to simplify JSON (#453)
Browse files Browse the repository at this point in the history
* As per recent commits, use ClassValue to speed up JSON serialization
* Code is structured to allow simple JSON logic to be unified
  • Loading branch information
jodastephen authored Dec 29, 2024
1 parent 52693f5 commit ed76064
Show file tree
Hide file tree
Showing 11 changed files with 1,082 additions and 234 deletions.
31 changes: 20 additions & 11 deletions src/changes/changes.xml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,14 @@
Mostly compatible with v2.x.
Deprecated methods have been removed.
</action>
<action dev="jodastephen" type="add" issue="232">
Potentially incompatible change:
Manual equals, hashCode and toString methods must now be located *before* the autogenerated block.
This change allows nested classes to be written with their own equals, hashCode and toString methods,
so long as the nested class is written *after* the autogenerated block.
It is expected that most uses of Joda-Beans already have the equals, hashCode and toString methods
before the autogenerated block and therefore will be unaffected.
</action>
<action dev="jodastephen" type="add" issue="445">
Add a new binary format.
This change adds a third binary format, with a focus on size and performance.
Expand All @@ -24,24 +32,25 @@
</action>
<action dev="jodastephen" type="add" issue="446">
Potentially incompatible change:
The standard binary and simple JSON formats now handle `Iterable` as a collection type.
The standard and referencing binary formats now support null as a map key.
</action>
<action dev="jodastephen" type="add" issue="446">
Potentially incompatible change:
The standard and referencing binary formats now support null as a map key.
The standard binary and simple JSON formats now handle `Iterable` as a collection type.
</action>
<action dev="jodastephen" type="add" issue="232">
Potentially incompatible change:
Manual equals, hashCode and toString methods must now be located *before* the autogenerated block.
This change allows nested classes to be written with their own equals, hashCode and toString methods,
so long as the nested class is written *after* the autogenerated block.
It is expected that most uses of Joda-Beans already have the equals, hashCode and toString methods
before the autogenerated block and therefore will be unaffected.
<action dev="jodastephen" type="update">
Incompatible change:
The JSON serialization formats have changed for primitive arrays.
Instead of a Joda-Convert formatted string, the array is output as a list of primitives.
This also applies to multi-dimensional arrays.
This is a much more natural JSON format.
The old format cam still be parsed successfully.
</action>
<action dev="jodastephen" type="update">
Incompatible change:
The simple JSON serialization format has changed for 2-dimensional primitive arrays.
Instead of a list of strings, they are output as a list of list of primitives.
The JSON serialization formats have changed requiring fewer @meta and @type annotations.
For example, where the type of the collection is Object,
previously a String value was explicitly typed as a String, now the type is implcit.
This is a much more natural JSON format.
The old format cam still be parsed successfully.
</action>
Expand Down
943 changes: 725 additions & 218 deletions src/main/java/org/joda/beans/ser/json/JodaBeanJsonWriter.java

Large diffs are not rendered by default.

84 changes: 79 additions & 5 deletions src/test/java/org/joda/beans/ser/json/TestSerializeJson.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import org.joda.beans.sample.ImmArrays;
import org.joda.beans.sample.ImmDoubleFloat;
import org.joda.beans.sample.ImmEmpty;
import org.joda.beans.sample.ImmGuava;
import org.joda.beans.sample.ImmKey;
import org.joda.beans.sample.ImmMappedKey;
import org.joda.beans.sample.ImmOptional;
Expand Down Expand Up @@ -61,7 +62,7 @@ void test_writeAddress() throws IOException {
var bean = SerTestHelper.testAddress();
var json = JodaBeanSer.PRETTY.jsonWriter().write(bean);
// System.out.println(json);
assertEqualsSerialization(json, "/org/joda/beans/ser/Address.json");
assertEqualsSerialization(json, "/org/joda/beans/ser/Address2.json");

var parsed = (Address) JodaBeanSer.PRETTY.jsonReader().read(json);
BeanAssert.assertBeanEquals(bean, parsed);
Expand All @@ -72,18 +73,21 @@ void test_writeImmAddress() throws IOException {
var bean = SerTestHelper.testImmAddress(false);
var json = JodaBeanSer.PRETTY.jsonWriter().write(bean);
// System.out.println(json);
assertEqualsSerialization(json, "/org/joda/beans/ser/ImmAddress.json");
assertEqualsSerialization(json, "/org/joda/beans/ser/ImmAddress2.json");

var parsed = (ImmAddress) JodaBeanSer.PRETTY.jsonReader().read(json);
BeanAssert.assertBeanEquals(bean, parsed);

var oldParsed = loadAndParse("/org/joda/beans/ser/ImmAddress1.json", ImmAddress.class);
BeanAssert.assertBeanEquals(bean, oldParsed);
}

@Test
void test_writeImmOptional() throws IOException {
var bean = SerTestHelper.testImmOptional();
var json = JodaBeanSer.PRETTY.withIncludeDerived(true).jsonWriter().write(bean);
// System.out.println(json);
assertEqualsSerialization(json, "/org/joda/beans/ser/ImmOptional.json");
assertEqualsSerialization(json, "/org/joda/beans/ser/ImmOptional2.json");

var parsed = (ImmOptional) JodaBeanSer.PRETTY.jsonReader().read(json);
BeanAssert.assertBeanEquals(bean, parsed);
Expand All @@ -100,21 +104,27 @@ void test_writeImmArrays() throws IOException {
new boolean[][] {{true, false}, {false}, {}});
var json = JodaBeanSer.PRETTY.jsonWriter().write(bean);
// System.out.println(json);
assertEqualsSerialization(json, "/org/joda/beans/ser/ImmArrays.json");
assertEqualsSerialization(json, "/org/joda/beans/ser/ImmArrays2.json");

var parsed = JodaBeanSer.PRETTY.simpleJsonReader().read(json, ImmArrays.class);
BeanAssert.assertBeanEquals(bean, parsed);

var oldParsed = loadAndParse("/org/joda/beans/ser/ImmArrays1.json", ImmArrays.class);
BeanAssert.assertBeanEquals(bean, oldParsed);
}

@Test
void test_writeCollections() throws IOException {
var bean = SerTestHelper.testCollections(true);
var json = JodaBeanSer.PRETTY.jsonWriter().write(bean);
// System.out.println(json);
assertEqualsSerialization(json, "/org/joda/beans/ser/Collections.json");
assertEqualsSerialization(json, "/org/joda/beans/ser/Collections2.json");

var parsed = JodaBeanSer.PRETTY.jsonReader().read(json);
BeanAssert.assertBeanEquals(bean, parsed);

var oldParsed = loadAndParse("/org/joda/beans/ser/Collections1.json", ImmGuava.class);
BeanAssert.assertBeanEquals(bean, oldParsed);
}

@Test
Expand All @@ -135,6 +145,12 @@ private void assertEqualsSerialization(String json, String expectedResource) thr
.isEqualTo(expected.trim().replace(System.lineSeparator(), "\n"));
}

private <T> T loadAndParse(String expectedResource, Class<T> type) throws IOException {
var url = TestSerializeJson.class.getResource(expectedResource);
var text = Resources.asCharSource(url, StandardCharsets.UTF_8).read();
return JodaBeanSer.PRETTY.simpleJsonReader().read(text, type);
}

//-----------------------------------------------------------------------
@Test
void test_readWriteBeanEmptyChild_pretty() {
Expand Down Expand Up @@ -193,6 +209,64 @@ void test_readWriteJodaConvertBean() {
BeanAssert.assertBeanEquals(bean, parsed);
}

//-------------------------------------------------------------------------
@Test
void test_readWrite_objectArrayAsObject() {
var bean = new FlexiBean();
bean.set("data", new Object[] {"1", 2, 3L, 4d, 5f});
var json = JodaBeanSer.COMPACT.jsonWriter().write(bean);
assertThat(json).isEqualTo("""
{"@bean":"org.joda.beans.impl.flexi.FlexiBean","data":{"@meta":"Object[]",\
"value":["1",2,{"@type":"Long","value":3},4.0,{"@type":"Float","value":5.0}]}}""");
var parsed = JodaBeanSer.COMPACT.jsonReader().read(new StringReader(json));
BeanAssert.assertBeanEquals(bean, parsed);
}

@Test
void test_readWrite_stringArrayAsObject() {
var bean = new FlexiBean();
bean.set("data", new String[] {"1", "2"});
var json = JodaBeanSer.COMPACT.jsonWriter().write(bean);
assertThat(json).isEqualTo("""
{"@bean":"org.joda.beans.impl.flexi.FlexiBean","data":{"@meta":"String[]","value":["1","2"]}}""");
var parsed = JodaBeanSer.COMPACT.jsonReader().read(new StringReader(json));
BeanAssert.assertBeanEquals(bean, parsed);
}

@Test
void test_readWrite_numberArrayAsObject() {
var bean = new FlexiBean();
bean.set("data", new Number[] {1, 2.3, 4L});
var json = JodaBeanSer.COMPACT.jsonWriter().write(bean);
assertThat(json).isEqualTo("""
{"@bean":"org.joda.beans.impl.flexi.FlexiBean","data":{"@meta":"java.lang.Number[]","value":[1,2.3,{"@type":"Long","value":4}]}}""");
var parsed = JodaBeanSer.COMPACT.jsonReader().read(new StringReader(json));
BeanAssert.assertBeanEquals(bean, parsed);
}

@Test
void test_readWrite_longArrayAsObject() {
var bean = new FlexiBean();
bean.set("data", new long[] {1L, 2L, 3L});
var json = JodaBeanSer.COMPACT.jsonWriter().write(bean);
assertThat(json).isEqualTo("""
{"@bean":"org.joda.beans.impl.flexi.FlexiBean","data":{"@meta":"long[]","value":[1,2,3]}}""");
var parsed = JodaBeanSer.COMPACT.jsonReader().read(new StringReader(json));
BeanAssert.assertBeanEquals(bean, parsed);
}

@Test
void test_readWrite_intArrayAsObject() {
var bean = new FlexiBean();
bean.set("data", new int[] {1, 2, 3});
var json = JodaBeanSer.COMPACT.jsonWriter().write(bean);
assertThat(json).isEqualTo("""
{"@bean":"org.joda.beans.impl.flexi.FlexiBean","data":{"@meta":"int[]","value":[1,2,3]}}""");
var parsed = JodaBeanSer.COMPACT.jsonReader().read(new StringReader(json));
BeanAssert.assertBeanEquals(bean, parsed);
}

//-------------------------------------------------------------------------
@Test
void test_readWrite_boolean_true() {
var bean = new FlexiBean();
Expand Down
50 changes: 50 additions & 0 deletions src/test/resources/org/joda/beans/ser/Collections2.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
{
"@bean": "org.joda.beans.sample.ImmGuava",
"collection": {
"@meta": "List",
"value": []
},
"list": ["A", "B"],
"set": ["A", "B"],
"sortedSet": ["A", "B"],
"map": [["A", "AA"], ["B", "BB"]],
"sortedMap": [["A", "AA"], ["B", "BB"]],
"biMap": [["A", "AA"], ["B", "BB"]],
"multimap": {
"@meta": "ListMultimap",
"value": [["A", "B"], ["A", "C"], ["B", "D"]]
},
"listMultimap": [["A", "B"], ["A", "C"], ["B", "D"]],
"setMultimap": [["A", "B"], ["A", "C"], ["B", "D"]],
"multiset": [["A", 1], ["B", 2], ["C", 3]],
"sortedMultiset": [],
"collectionInterface": {
"@meta": "List",
"value": []
},
"listInterface": ["A", "B"],
"setInterface": ["A", "B"],
"sortedSetInterface": ["A", "B"],
"mapInterface": [["A", "AA"], ["B", "BB"]],
"sortedMapInterface": [["A", "AA"], ["B", "BB"]],
"biMapInterface": [["A", "AA"], ["B", "BB"]],
"multimapInterface": {
"@meta": "ListMultimap",
"value": [["A", "B"], ["A", "C"], ["B", "D"]]
},
"listMultimapInterface": [["A", "B"], ["A", "C"], ["B", "D"]],
"setMultimapInterface": [["A", "B"], ["A", "C"], ["B", "D"]],
"multisetInterface": [],
"sortedMultisetInterface": [],
"listWildExtendsT": [],
"listWildExtendsNumber": [],
"listWildExtendsComparable": [],
"setWildExtendsT": [],
"setWildExtendsNumber": [],
"setWildExtendsComparable": [],
"listWildBuilder1": [],
"listWildBuilder2": [],
"mapWildBuilder1": {},
"mapWildKey": [],
"table": [["A", 1, "B"], ["A", 2, "C"], ["B", 3, "D"]]
}
Loading

0 comments on commit ed76064

Please sign in to comment.