-
Notifications
You must be signed in to change notification settings - Fork 245
JSON serialization
(released with 2.29)
The Json codec provides the possibility to do full fledged object graph serialization based on JSon format. Other than existing solutions (e.g. Jackson Databind or Boon) it is fully precise (Object Graph is identical after deserialization) and supports/restores cyclic and interlinked object graphs same as serialization. It uses Jackson-Core under the hood to parse/generate Json, so its FST's serialization implementation using a Jackson-back end instead of byte arrays for in/output.
Ofc there is a cost for maintaining full type information, so performance is reasonable, however for simple non cyclic datastructures, non-generic Json libs are faster (at least the performance leaders). However once data gets more complex, limited databinding can lead to manual translation of Application Data <=> Json Intermediate Message representation which is usually not covered by benchmarks.
- cross platform/language interoperability.
- productivity. Leverage existing Serializable interfaces by just switching the codec from native serialization to Json.
- readability/historical safety. Can be read+processed by any tool. Original classes are not required.
- generalization+abstraction. Effortless multi-protocol servers etc ..
- Serializes
Serializable
andExternalizable
object graphs to Json keeping precise type information same as JDK and FST Serialization - Support for cyclic and linked data structures same as serialization
- Flat mode (ignore references in object graph) supported
- Reuse existing serialization code to support Json.
- benchmark. Note that the strength of fst-json is fully automated genericity. For half automated/assisted json interfacing jackson-databind is more appropriate. The benchmark for comparision also shows non-json fst serialization and JDK serialization.
fast-serialization produces and consumes UTF-8 json strings (delivered as byte array for better performance). As this is not a "json-parser" but a objects-to json-to objects serializer, this shouldn't be a problem.
- embedded type information produces somewhat ugly Json output (btw: machines don't mind ;) ). Additionally there is a performance cost for additional type information tags compared to handpicked Json encoding.
- classes using old school JDK serialization implementations (readObject/writeObject/readReplace..) are not supported, one needs to register FST-Serializers for those. Fortunately fst comes with a broad range of predefined+preconfigured Serializers (Collections, frequently used Data Types), so for most cases it will work out of the box same as regular FST Serialization.
static class SimpleClass implements Serializable {
String name = "You";
double aDouble = 13.3456;
int anInt;
}
public static void main(String[] args) throws UnsupportedEncodingException {
FSTConfiguration conf = FSTConfiguration.createJsonConfiguration();
Object p = new SimpleClass();
byte[] bytes = conf.asByteArray(p);
System.out.println(new String(bytes,"UTF-8"));
Object deser = conf.asObject(bytes);
}
yields:
{
"typ": "ser.Play$SimpleClass",
"obj": {
"aDouble": 13.3456,
"anInt": 0,
"name": "You"
}
}
each regular serialized object results in {"typ": ..java type.. obj:{..data key/val..}}
. By preregistering classes, class names can be shortened. HashMap, ArrayList, Object arrays are already preregistered (named 'list', 'map' and 'array').
Native primitive arrays (+ class registration conf.registerCrossPlatformClassMappingUseSimpleName(SimpleClass.class);
)
public static class SimpleClass implements Serializable {
String name = "You";
double aDouble = 13.3456;
int anInt;
int integers[] = { 1,2,3,4,5 };
short shorts[] = { 1,2,3,4,5 };
}
yields (notice the type tagging of short array. All primitive arrays except int arrays are type tagged, required to handle e.g. Object o = new short[] { 1,23 };
):
{
"typ": "SimpleClass",
"obj": {
"aDouble": 13.3456,
"anInt": 0,
"name": "You",
"integers": [ 1, 2, 3, 4, 5 ],
"shorts": [ "short", 1, 2, 3, 4, 5 ]
}
}
Externalizable / classes with Custom Serializer registered use a slightly different object notation: {"typ": ..java type.. obj:[..data key/val..]}
. The 'obj' value is an array containing the output of an externalize implementation or custom serializer write method.
public static class SampleClass implements Serializable {
List myList = new ArrayList();
Map<Integer,String> myMap = new HashMap<>();
{
myMap.put(1,"Some String");
myList.add(1); myList.add(2); myList.add("Hello");// myList.add(new Date());
}
}
yields:
{
"typ": "ser.Play$SampleClass",
"obj": {
"myList": {
"typ": "list",
"obj": [ 3, //number_of_list_elements
1, 2, "Hello"
]
},
"myMap": {
"typ": "map",
"obj": [
1, //number_of_hashmap_entries
1, "Some String"
]
}
}
}
As the FSTCollection Serializer first writes the length of a collection, then the elements of the collection, you see this reflected in the 'obj:[]' json array. Note that "HashMap" and "ArrayList" are preregistered to be shortened as "map" and "list". This also opens opportunities to shorten the Json footprint of frequently serialized classes by registering a custom serializer (no field names written).
non-primitive and multidimensional arrays
public static class SampleClass implements Serializable {
int iii[][][] = new int[][][] { { {1,2,3}, {4,5,6} }, { {7,8,9}, {10,11,12} } };
Object objArr[][] = { { "A", "B" }, null, { null, "C", "D" } };
}
yields (again for sequences, first element is size of list)
{
"typ": "SampleClass",
"obj": {
"objArr": {
"styp": "[[Ljava.lang.Object;",
"seq": [
3,
{
"styp": "array",
"seq": [ 2, "A", "B" ]
},
null,
{
"styp": "array",
"seq": [ 3, null, "C", "D" ]
}
]
},
"iii": {
"styp": "[[[I",
"seq": [
2,
{
"styp": "[[I",
"seq": [ 2, [ 1, 2, 3 ], [ 4, 5, 6 ] ]
},
{
"stype": "[[I",
"seq": [ 2, [ 7, 8, 9 ], [ 10, 11, 12 ] ]
}
]
}
}
}
registering classnames (there are also methods to register arbitrary names)
conf.registerCrossPlatformClassMappingUseSimpleName(
SampleClass.class,
Object[].class,
Object[][].class,
int[][].class,
int[][][].class
);
yields ('array' is preregistered for 'Object[]')
{
"typ": "SampleClass",
"obj": {
"objArr": {
"styp": "Object[][]",
"seq": [
3,
{
"styp": "array",
"seq": [ 2, "A", "B" ]
},
null,
{
"styp": "array",
"seq": [ 3, null, "C", "D" ]
}
]
},
"iii": {
"styp": "int[][][]",
"seq": [
2,
{
"stye": "int[][]",
"seq": [ 2, [ 1, 2, 3 ], [ 4, 5, 6 ] ]
},
{
"styp": "int[][]",
"seq": [ 2, [ 7, 8, 9 ], [ 10, 11, 12 ] ]
}
]
}
}
}
references
public static class SampleClass implements Serializable {
String a = "bla bla bla bla bla bla bla bla bla bla bla bla bla ";
Object b = a;
}
{
"typ": "SampleClass",
"obj": {
"b": "bla bla bla bla bla bla bla bla bla bla bla bla bla ",
"a": {
"ref": 32
}
}
}
Note the ref value denotes the position of the object in the non-pretty printed Json string. That's why references don't work for pretty printed Json. References can be turned off at FSTConfiguration, if sharing is disabled, there is also no detection for cyclic references anymore.
interop/postprocessing in JavaScript
As objects get more complex, the additional level of nesting inflicted by type information can get
annoying when processing json in a javascript client. Therefore here is a method to "flatten" the
object graph. var message = transform(JSON.parse(string),true);
.
It reduces the virtual nesting and transforms ArrayList and Object[] arrays to a more intuitive representation. Alternatively, the type info is added as a property named '_typ'.
/**
* makes a fst json serialized object more js-friendly
* @param obj
* @param preserveTypeAsAttribute - create a _typ property on each object denoting original java type
* @param optionalTransformer - called as a map function for each object. if returns != null => replace given object in tree with result
* @returns {*}
*/
function transform(obj, preserveTypeAsAttribute, optionalTransformer) {
if (optionalTransformer) {
var trans = optionalTransformer.apply(null, [obj]);
if (trans)
return trans;
}
if (!obj)
return obj;
if (obj["styp"] && obj["seq"]) {
var arr = transform(obj["seq"], preserveTypeAsAttribute, optionalTransformer);
if (arr) {
arr.shift();
if (preserveTypeAsAttribute)
arr["_typ"] = obj["styp"];
}
return arr;
}
if (obj["typ"] && obj["obj"]) {
if ('list' === obj['typ']) {
// remove leading element length from arraylist
obj["obj"].shift();
}
var res = transform(obj["obj"], preserveTypeAsAttribute, optionalTransformer);
if (preserveTypeAsAttribute)
res["_typ"] = obj["typ"];
return res;
}
for (var property in obj) {
if (obj.hasOwnProperty(property) && obj[property] != null) {
if (obj[property].constructor == Object) {
obj[property] = transform(obj[property], preserveTypeAsAttribute, optionalTransformer);
} else if (obj[property].constructor == Array) {
for (var i = 0; i < obj[property].length; i++) {
obj[property][i] = transform(obj[property][i], preserveTypeAsAttribute, optionalTransformer);
}
}
}
}
return obj;
}
you might also have a look at Kontraktor 3's js4k.js for more convenience regarding Java-JavaScript back and forth encoding.