JSONDeserializer takes as input a json string and produces a static typed object graph from that
json representation. By default it uses the class property in the json data in order to map the
untyped generic json data into a specific Java type. However, you are limited to only json strings
with class information embedded when resolving it into a Java type. But, for now let's just look at
the simplest case of class attributes in your json. We'll look at how
JSONSerializer and
JSONDeserializer pair together out of the box.
Say we have a simple object like Hero (see the superhero package under the test and mock).
To create a json represenation of Hero we'd do the following:
Hero harveyBirdman = new Hero("Harvey Birdman", new SecretIdentity("Attorney At Law"), new SecretLair("Sebben & Sebben") );
String jsonHarvey = new JSONSerialize().serialize(hero);
Now to reconsitute Harvey to fight for the law we'd use JSONDeserializer like so:
Hero hero = new JSONDeserializer().deserialize( jsonHarvey );
Pretty easy when all the type information is included with the JSON data. Now let's look at the more difficult
case of how we might reconstitute something missing type info.
Let's exclude the class attribute in our json like so:
String jsonHarvey = new JSONSerialize().exclude("*.class").serialize(hero);
The big trick here is to replace that type information when we instantiate the deserializer.
To do that we'll use the
flexjson.JSONDeserializer#use(String,Class) method like so:
Hero hero = new JSONDeserializer().use( null, Hero.class ).deserialize( jsonHarvey );
Like riding a horse with no saddle without our type information. So what is happening here is we've registered
the Hero class to the root of the json. The
flexjson.JSONDeserializer#use(String,Class) method uses
the object graph path to attach certain classes to those locations. So, when the deserializer is deserializing
it knows where it is in the object graph. It uses that graph path to look up the java class it should use
when reconstituting the object.
Notice that in our json you'd see there is no type information in the stream. However, all we had to do is point
the class at the Hero object, and it figured it out. That's because it uses the target type (in this case Hero)
to figure out the other types by inspecting that class. Meaning notice that we didn't have to tell it about
SecretLair or SecretIdentity. That's because it can figure that out from the Hero class.
Pretty cool. Where this fails is when we starting working with interfaces, abstract classes, and subclasses.
Yea our friend polymorphism can be a pain when deserializing. Why? Well if you haven't realized by now
inspecting the type from our target class won't help us because either it's not a concrete class or we
can't tell the subclass by looking at the super class alone. Next section we're going to stand up on our
bare back horse. Ready? Let's do it.
Before we showed how the
flexjson.JSONDeserializer#use(String,Class) method would allow us to
plug in a single class for a given path. That might work when you know exactly which class you want to
instantiate, but when the class type depends on external factors we really need a way to specify several
possibilities. That's where the second version of
flexjson.JSONDeserializer#use(String,ClassLocator)comes into play.
flexjson.ClassLocator allow you to use a stradegy for finding which java Class
you want to attach at a particular object path.
flexjson.JSONDeserializer#use(String,ClassLocator) have access to the intermediate form of
the object as a Map. Given the Map at the object path the ClassLocator figures out which Class
Flexjson will bind the parameters into that object.
Let's take a look at how this can be done using our Hero class. All Heros have a list of super powers.
These super powers are things like X Ray Vision, Heat Vision, Flight, etc. Each super power is represented
by a subclass of SuperPower. If we serialize a Hero without class information embedded we'll need a way to
figure out which instance to instantiate when we deserialize. In this example I'm going to use a Transformer
during serialization to embed a special type information into the object. All this transformer does is strip
off the package information on the class property.
String json = new JSONSerializer()
.include("powers.class")
.transform( new SimpleTransformer(), "powers.class")
.exclude("*.class")
.serialize( superhero );
Hero hero = new JSONDeserializer()
.use("powers.class", new PackageClassLocator())
.deserialize( json );
All objects that pass through the deserializer must have a no argument constructor. The no argument
constructor does not have to be public. That allows you to maintain some encapsulation. JSONDeserializer
will bind parameters using setter methods of the objects instantiated if available. If a setter method
is not available it will using reflection to set the value directly into the field. You can use setter
methods transform the any data from json into the object structure you want. That way json structure
can be different from your Java object structure. The works very much in the same way getters do for
the
flexjson.JSONSerializer.
Collections and Maps have changed the path structure in order to specify concrete classes for both
the Collection implementation and the contained values. Normally you would use generics to specify
the concrete class to load. However, if you're contained class is an interface or abstract class
then you'll need to define those concrete classes using paths. To specify the concrete class for
a Collection use the path to the collection. To specify the contained instance's concrete class
append "values" onto the path. For example, if your collection path is "person.friends" you can
specify the collection type using:
new JSONDeserializer().use("person.friends", ArrayList.class).use("person.friends.values", Frienemies.class)
Notice that append "values" onto the "person.friends" to specify the class to use inside the
Collection. Maps have both keys and values within them. For Maps you can specify those by
appending "keys" and "values" to the path.
Now onto the advanced topics of the deserializer.
flexjson.ObjectFactory interface is the
underpinnings of the deserializer. All object creation is controlled by ObjectFactories. By default
there are many ObjectFactories registered to handle all of the default types supported. However, you
can add your own implementations to handle specialized formats. For example, say you've encoded your
Dates using yyyy.MM.dd. If you want to read these into java.util.Date objects you can register a
flexjson.transformer.DateTransformer to deserialize dates into Date objects.