A Java <-> Json converter with type inference.
In my day to day, I have to map a lot of Java to Json objects and vice versa. It is a tedious work to map a Class instance to a Json and deal with class modifications, objects structure, etc. Transformer comes handy for automatic, reflection-based type mappings.
Currently, Transformer uses runtime reflection for mapping between Java and Json. There's some WIP to make Transformer work
Transformer exposes two main class objects: JSONSerializer and JSONDeserializer.
Serialization happens by runtime reflection. It recursively scans object's fields to create a Json representation of it.
Fields with names of the form this$ are not serialized since they identify inner classes. Also, fields annotated with @Transient are not serialized.
JSONSerializer exposes just one method: Serialize( Object obj ). This method takes track of cyclic serializations issues by keeping a HashMap (could be SparseArray but depending on your objects hashmap might be more suitable) where an object is associated with its hashCode. If a previously existing hashCode is found, it is assumed a cyclic reference in the Object fields graph, and the serialization would fire a CyclicObjectException. This is a RuntimeException and hance not mandatory to catch.
JSONSerializer has two specific use cases for List<?> and Map<?,?> objects. List objects will be serialized as Json arrays, and Map objects as Json objects.
assertEquals(JSONSerializer.Serialize(null), "null")
assertEquals(JSONSerializer.Serialize("Hola"), "\"Hola\"").
Note double quotes on resulting string.
assertEquals(JSONSerializer.Serialize( new int[]{1,2,3}), "[1,2,3]").
Yes, serialize arrays like a boss.
assertEquals(JSONSerializer.Serialize( new Object[]{1,new TestA(),3}), "[1,{\"a\":8,\"str\":\"123\"},3]")
assertEquals(JSONSerializer.Serialize( new Object[]{1,null,"aa"}), "[1,null,\"aa\"]").
Given these simple Java class elements:
publicclassTestA{inta=8; Stringstr = "123"; publicTestA(){} publicTestA( inta, Stringstr ){this.a= a; this.str= str} } publicclassTestB{TestAa = newTestA(); Stringx= null} publicclassTestC{inta=8; @TransientStringstr = "123"} publicclassTestD{inta=8; @TransientStringstr = "123"; TestDd} assertEquals(JSONSerializer.Serialize( new TestA() ), "{\"a\":8,\"str\":\"123\"}");
assertEquals(JSONSerializer.Serialize( new TestB() ), "{\"a\":{\"a\":8,\"str\":\"123\"},\"x\":null}");
assertEquals(JSONSerializer.Serialize( new TestC() ), "{\"a\":8}");
voidtestRaiseException(){TestDd = newTestD(); d.d=d; booleancatched = false; try{JSONSerializer.Serialize(d)} catch(CyclicObjectExceptione){catched= true} assertTrue(catched)}- Serialization of List
voidtestSerializeList(){ArrayList<Object> al= newArrayList<>(); al.add( newTestA(1,"1qaz") ); al.add( newTestA(2,"2wsx") ); al.add( newTestA(3,"3edc") ); ArrayList<Object> al2= newArrayList<>(); al2.add( newTestA(11,"11qqaazz") ); al2.add( newTestA(22,"22wwssxx") ); al.add( al2 ); Stringstr = JSONSerializer.Serialize(al)}# result: str= [{"a":1,"str":"1qaz"},{"a":2,"str":"2wsx"},{"a":3,"str":"3edc"}, [{"a":11,"str":"11qqaazz"},{"a":22,"str":"22wwssxx"} ] ] voidtestSerialieMap(){ArrayList<Object> list= newArrayList<>(); list.add( newTestA(1,"1qaz") ); list.add( newTestA(2,"2wsx") ); list.add( newTestA(3,"3edc") ); HashMap<String,Object> h = newHashMap<>(); h.put( "a", 1 ); h.put( "bb", 2 ); h.put( "ccc", 3 ); h.put( "dddd", list ); Stringstr = JSONSerializer.Serialize(h)}// result: str ={"bb":2, "a":1, "ccc":3, "dddd":[{"a":1,"str":"1qaz"},{"a":2,"str":"2wsx"},{"a":3,"str":"3edc"} ] } Deserialization is the inverse process of serialization, with some major caveats:
- Deserialization target must have all fields qualified at the class level.
Transformercan not infer a type forObjectorList<String>, but will do a good job at mappingArrayList<Integer>. - Deserialization target fields must be of type primitive
(boolean, byte, char, short, int, long, float, double, String), typeList, or any other user defined class type whose fields follow the same rules. TheListtype, must be fully qualified, e.g.ArrayList<TestA>. - Deserialization target objects can be main or inner classes.
Like JSONSerializer, JSONDeserializer will omit set values for fields annotated at @Transient.
The process of deserialization may crash raising a MatchTypeException for many different reasons, but most likely because of a bad type mapping between a Java field and a JSON node. In example, assign a String to a Java int field.
Transformer deserializer is very powerful. Let's see some examples:
All examples will use these classes as reference:
publicclassTestA{inta=8; Stringstr = "123"; TestA( inta, Stringstr ){this.a=a; this.str=str} } publicclassTestB{longl = 0L; TestAa; TestBb} publicclassTestC{longl = 0L; TestAa; TestBb; int[] arr} publicclassTestD{ArrayList<TestA> arr}voiddeserializeTestA(){JSONObjectjson = newJSONObject( "{" + "\"a\":8," + "\"str\":\"aaabbb\"" + "}"); TestAa = JSONDeserializer.Deserialize(TestA.class, json); // a.a = 8// a.str = "aaabbb" }voiddeserializeTestB(){JSONObjectjson = newJSONObject( "{" + "\"l\":89485," + "\"a\":{" + "\"a\":81," + "\"str\":\"cdef\"" + "}," + "\"b\":null" + "}"); TestBa = JSONDeserializer.Deserialize(TestB.class, json); // a.l = 89485// a.b = null// a.a = 81// a.str = "cdef" }voiddeserializeTest(){JSONObjectjson = newJSONObject( "{" + "\"l\":5," + "\"a\":{" + "\"a\":12345," + "\"str\":\"1qaz\"" + "}," + "\"b\":null," + "\"arr\":[1,2,3,4,5,6]" + "}"); TestCa = JSONDeserializer.Deserialize( TestC.class , json); // a.l = 5// a.arr[0]=1 }voiddeserializePrimitiveArray(){JSONArrayjson = newJSONArray("[0,1,2,3]"); int[] a = JSONDeserializer.Deserialize( int[].class , json); // a[0] = 0;// a[1] = 1;// a[2] = 2;// a[3] = 3; }voiddeserializeNonPrimitiveArray(){// use serializer to get json representation.TestA[] ta = newTestA[]{newTestA(1234,"1qaz"), newTestA(2345,"2wsx"), newTestA(3456,"3edc") }; Stringsta= JSONSerializer.Serialize(ta); JSONArrayjson = newJSONArray(sta); // deserialize array:TestA[] a = JSONDeserializer.Deserialize( TestA[].class , json); // a[0].a = 1234// a[2].str = "3edc" }voiddeserializeList(){JSONObjectjson = newJSONObject( "{" + "arr:[" + "{\"a\":12345,\"str\":\"1qaz\"}," + "{\"a\":2345,\"str\":\"2wsx\"}," + "{\"a\":34567,\"str\":\"3edc\"}," + "null" + "]" + "}"); TestDa = JSONDeserializer.Deserialize( TestD.class , json); // a.arr[0].str = "1qaz"// a.arr[2].a = "34567" }voiddeserializeMappingError(){JSONArrayjson = newJSONArray( "[" + "{\"a\":12345,\"str\":\"1qaz\"}," + "{\"a\":2345,\"str\":\"2wsx\"}," + "{\"a\":34567,\"str\":\"3edc\"}" + "]"); TestB[] a = JSONDeserializer.Deserialize( TestB[].class , json); // error. Mapping TestA types into TestB types.// com.transformer.json.MatchTypeException: // Setting l in long got error: java.lang.IllegalArgumentException: // field com.hyper.ui.MainActivity$TestB.l has type long, got null }