All Downloads are FREE. Search and download functionalities are using the official Maven repository.

flexjson.JSONDeserializer Maven / Gradle / Ivy

package flexjson;

import java.io.Reader;
import java.util.HashMap;
import java.util.Map;

import flexjson.factories.ClassLocatorObjectFactory;
import flexjson.factories.ExistingObjectFactory;
import flexjson.locators.StaticClassLocator;

/**
 * 

* 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} 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 {@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 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 {@link 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 {@link flexjson.JSONDeserializer#use(String, ClassLocator)} * comes into play. {@link flexjson.ClassLocator} allow you to use a stradegy for finding which java Class * you want to attach at a particular object path. *

*

* {@link 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 {@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 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 * {@link flexjson.transformer.DateTransformer} to deserialize dates into Date objects. *

*/ public class JSONDeserializer { private Map typeFactories= new HashMap(); private Map pathFactories= new HashMap(); public static ObjectBinder lastObjectBinder; public JSONDeserializer() { } /** * Deserialize the given json formatted input into a Java object. * * @param input a json formatted string. * @return an Java instance deserialized from the json input. */ public T deserialize(String input) { ObjectBinder binder= createObjectBinder(); return (T) binder.bind(new JSONTokener(input).nextValue()); } /** * Same as {@link #deserialize(String)}, but uses an instance of * java.io.Reader as json input. * * @param input the stream where the json input is coming from. * @return an Java instance deserialized from the java.io.Reader's input. */ public T deserialize(Reader input) { ObjectBinder binder= createObjectBinder(); return (T) binder.bind(new JSONTokener(input).nextValue()); } /** * Deserialize the given json input, and use the given Class as * the type of the initial object to deserialize into. This object * must implement a no-arg constructor. * * @param input a json formatted string. * @param root a Class used to create the initial object. * @return the object created from the given json input. */ public T deserialize(String input, Class root) { ObjectBinder binder= createObjectBinder(); return (T) binder.bind(new JSONTokener(input).nextValue(), root); } /** * Same as {@link #deserialize(java.io.Reader, Class)}, but uses an instance of * java.io.Reader as json input. * * @param input the stream where the json input is coming from. * @param root a Class used to create the initial object. * @return an Java instance deserialized from the java.io.Reader's input. */ public T deserialize(Reader input, Class root) { ObjectBinder binder= createObjectBinder(); return (T) binder.bind(new JSONTokener(input).nextValue(), root); } /** * Deserialize the given json input, and use the given ObjectFactory to * create the initial object to deserialize into. * * @param input a json formatted string. * @param factory an ObjectFactory used to create the initial object. * @return the object created from the given json input. */ public T deserialize(String input, ObjectFactory factory) { use((String) null, factory); ObjectBinder binder= createObjectBinder(); return (T) binder.bind(new JSONTokener(input).nextValue()); } /** * Same as {@link #deserialize(String, ObjectFactory)}, but uses an instance of * java.io.Reader as json input. * * @param input the stream where the json input is coming from. * @param factory an ObjectFactory used to create the initial object. * @return an Java instance deserialized from the java.io.Reader's input. */ public T deserialize(Reader input, ObjectFactory factory) { use((String) null, factory); ObjectBinder binder= createObjectBinder(); return (T) binder.bind(new JSONTokener(input).nextValue()); } /** * Deserialize the given input into the existing object target. * Values in the json input will overwrite values in the * target object. This means if a value is included in json * a new object will be created and set into the existing object. * * @param input a json formatted string. * @param target an instance to set values into from the json string. * @return will return a reference to target. */ public T deserializeInto(String input, T target) { return deserialize(input, new ExistingObjectFactory(target)); } /** * Same as {@link #deserializeInto(String, Object)}, but uses an instance of * java.io.Reader as json input. * * @param input the stream where the json input is coming from. * @param target an instance to set values into from the json string. * @return will return a reference to target. */ public T deserializeInto(Reader input, T target) { return deserialize(input, new ExistingObjectFactory(target)); } public JSONDeserializer use(String path, ClassLocator locator) { pathFactories.put(Path.parse(path), new ClassLocatorObjectFactory(locator)); return this; } public JSONDeserializer use(String path, Class clazz) { return use(path, new StaticClassLocator(clazz)); } public JSONDeserializer use(Class clazz, ObjectFactory factory) { typeFactories.put(clazz, factory); return this; } public JSONDeserializer use(String path, ObjectFactory factory) { pathFactories.put(Path.parse(path), factory); return this; } public JSONDeserializer use(ObjectFactory factory, String... paths) { for (String p : paths) { use(p, factory); } return this; } private ObjectBinder createObjectBinder() { ObjectBinder binder= new ObjectBinder(); lastObjectBinder= binder; for (Object clazz : typeFactories.keySet()) { binder.use(clazz, typeFactories.get(clazz)); } for (Path p : pathFactories.keySet()) { binder.use(p, pathFactories.get(p)); } return binder; } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy