We start recognizing that Serialization is not such a good idea.
It is cool and can really work on a wide range of objects, even including complex and cyclic reference graphs. And it was essential for some older Java frameworks like EJB and RMI, which allowed remote access to Java objects and classes.
But it is no longer the future, Oracle will soon deprecate and later remove it. And it will happen this time, even though they really keep stuff around for a long time due to compatibility requirements.
Just to recap: it opens up security discussion, it opens up hidden behavior and makes it harder to reason about code, it creates tight coupling between remote components and it can result in bugs, that only occur at runtime and cannot be discovered at compile time. In short, it is not resilient.
So we need something else. Obvious candidates are XML, YAML and JSON. XML is of course an option and is powerful enough to do many things, but often a bit too clumbsy and too much boiler plate, so we try to move away from it. YAML and JSON kind of do the same thing, but it seems that JSON is winning the race and we all need to know JSON and many of us tend to skip YAML.
So why not use JSON. It is easy, it has good libraries and we can even find databases that work with JSON.
What JSON can express very well are scalars, lists and maps and combinations of these. This is quite exactly what we have in Perl, JavaScript or Clojure as basic building blocks. These languages support object oriented programming, but for simple stuff we go with these basic building blocks. And objects can be modelled as (hash-)maps, with the attribute names as keys. Actually JSON is valid JavaScript code.
We do have to change our thinking when moving from Java Serialization to JSON. JSON does not store any serializable object but just data. Maybe that is enough and that is what we actually want. It totally works in heterogeneous environments, where we are using different programming languages or different implementations.
There are good libraries. I have tried two, Jackson and GSON which both work well, recently mostly Jackson. It is important to think of Clojure, JavaScript, Perl or something like that without objects. So we loose type information, which can be considered good or bad, but if we can arrange ourselves with it, we avoid the tight coupling. JavaBeans are expressed exactly the same as a HashMap with the attribute names as keys. We can provide the top level class when deserializing, but at the child levels it will not be able to figure that out, if it relies on runtime information.
Example Code
Here it has been tried out. Find full example code on github.
A class that contains all kinds of stuff. Not prepared for really putting in nulls, but it is just experimental code…
package net.itsky.jackson;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class TestObject {
private Long l;
private String s;
private Boolean b;
private Set> set;
private List> list;
private Map
public TestObject(Long l, String s, Boolean b, Set> set, List> list, Map
this.l = l;
this.s = s;
this.b = b;
this.set = set;
this.list = list;
this.map = map;
}
public TestObject() {
// only for framework purposes
}
public Long getL() {
return l;
}
public String getS() {
return s;
}
public Boolean getB() {
return b;
}
public Set> getSet() {
return set;
}
public List> getList() {
return list;
}
public Map
return map;
}
@Override
public String toString() {
return getClass().getSimpleName() + "("
+ "l=" + l + " (" + l.getClass() + ") "
+ " s=\"" + s + "\" (" + s.getClass() + ") "
+ " b=" + b + " (" + b.getClass() + ") "
+ " set=" + set + " (" + set.getClass() + ") "
+ " list=" + list + " (" + list.getClass() + ") "
+ " map=" + map + " (" + map.getClass() + "))";
}
}
And this is used for running everything. To play around more, it should probably be moved to tests..
package net.itsky.jackson;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectWriter;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import java.io.StringReader;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class App {
public static void main(String[] args) {
try {
Set
Set
Map
List> l1 = ImmutableList.of("i", "e", "a", "o", "u");
TestObject t1 = new TestObject(30303L, "uv", true, s2, l1, m1);
Map
List> l2 = ImmutableList.of("ä", "ö", "ü", "å", "ø");
Set> s3 = ImmutableSet.of("x", "y", "z");
TestObject t2 = new TestObject(40404L, "ijk", false, s3, l2, m2);
ObjectMapper mapper = new ObjectMapper();
ObjectWriter writer = mapper.writerWithDefaultPrettyPrinter();
System.out.println("t2=" + t2);
String json = writer.writeValueAsString(t2);
System.out.println("json=" + json);
StringReader stringReader = new StringReader(json);
TestObject t3 = mapper.readValue(stringReader, TestObject.class);
System.out.println("t3=" + t3);
} catch (Exception ex) {
RuntimeException rex;
if (ex instanceof RuntimeException) {
rex = (RuntimeException) ex;
} else {
rex = new RuntimeException(ex);
}
throw rex;
}
}
}
And here is the output:
t2=TestObject(l=40404 (class java.lang.Long) s="ijk" (class java.lang.String)
b=false (class java.lang.Boolean)
set=[x, y, z] (class com.google.common.collect.RegularImmutableSet)
list=[ä, ö, ü, å, ø] (class com.google.common.collect.RegularImmutableList)
map={r=r101, s=202,
t=TestObject(l=30303 (class java.lang.Long)
s="uv" (class java.lang.String) b=true (class java.lang.Boolean)
set=[1, 2, 3] (class com.google.common.collect.RegularImmutableSet)
list=[i, e, a, o, u] (class com.google.common.collect.RegularImmutableList)
map={A=abc, B=3, C=[1, 2, 3]} (class com.google.common.collect.RegularImmutableMap))}
(class com.google.common.collect.RegularImmutableMap))
json={
"l" : 40404,
"s" : "ijk",
"b" : false,
"set" : [ "x", "y", "z" ],
"list" : [ "ä", "ö", "ü", "å", "ø" ],
"map" : {
"r" : "r101",
"s" : 202,
"t" : {
"l" : 30303,
"s" : "uv",
"b" : true,
"set" : [ 1, 2, 3 ],
"list" : [ "i", "e", "a", "o", "u" ],
"map" : {
"A" : "abc",
"B" : 3,
"C" : [ 1, 2, 3 ]
}
}
}
}
t3=TestObject(l=40404 (class java.lang.Long) s="ijk" (class java.lang.String)
b=false (class java.lang.Boolean)
set=[x, y, z] (class java.util.HashSet)
list=[ä, ö, ü, å, ø] (class java.util.ArrayList)
map={r=r101, s=202,
t={l=30303, s=uv, b=true,
set=[1, 2, 3],
list=[i, e, a, o, u],
map={A=abc, B=3, C=[1, 2, 3]}}}
(class java.util.LinkedHashMap))
Process finished with exit code 0
So the immediate object and its immediate attributes were deserialized properly to what we provided. But everything inside went to maps, lists and scalars.
The intermediate JSON does not carry the type information at all, so this is the best that can be done.
Often it is useful what we want. If not, we need to find something else or see if we can tweak JSON to carry type information.
It will be interesting to explore other serialization protocols…
Links
- Serialization
- JSON
- Baeldung: Jackson
- Wikipedia: Jackson
- Github: Jackson
- Github: Jackson documentation
- GSON
- Baeldung: Jackson Custom Serialization
- github of example
- 4 alternatives to Serializable: Protobuf, Avro, Thrift and Kryo (talk at Scala Days 2018)
- Signifies Blog about this talk
- XML
- Protocol Buffers
- Avro
- Thrift
- Introduction to Kryo