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 {@link JSONSerializer} andJSONDeserializer 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 {@link 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 {@link flexjson.JSONDeserializer#use(String,Class)} method usesthe 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 {@link flexjson.JSONDeserializer#use(String,Class)} method would allow us toplug 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 {@link flexjson.JSONDeserializer#use(String,ClassLocator)}comes into play. {@link flexjson.ClassLocator} allow you to use a stradegy for finding which java Classyou want to attach at a particular object path.
{@link flexjson.JSONDeserializer#use(String,ClassLocator)} have access to the intermediate form ofthe 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 {@link 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. {@link flexjson.ObjectFactory} interface is theunderpinnings 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 {@link flexjson.transformer.DateTransformer} to deserialize dates into Date objects.
|
|
|
|
|
|
|
|
|
|
|
|