Package flexjson

Source Code of flexjson.JSONDeserializer

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;

/**
* <p>
* 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.
* </p>
* <p>
* 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:
* </p>
*
* <pre>
*   Hero harveyBirdman = new Hero("Harvey Birdman", new SecretIdentity("Attorney At Law"), new SecretLair("Sebben & Sebben") );
*   String jsonHarvey = new JSONSerialize().serialize(hero);
* </pre>
* <p>
* Now to reconsitute Harvey to fight for the law we'd use JSONDeserializer like so:
* </p>
* <pre>
*   Hero hero = new JSONDeserializer<Hero>().deserialize( jsonHarvey );
* </pre>
* <p>
* 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.
* </p>
* <p>
* Let's exclude the class attribute in our json like so:
* </p>
*
* <pre>
*   String jsonHarvey = new JSONSerialize().exclude("*.class").serialize(hero);
* </pre>
* <p>
* 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:
* </p>
* <pre>
*   Hero hero = new JSONDeserializer<Hero>().use( null, Hero.class ).deserialize( jsonHarvey );
* </pre>
* <p>
* 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.
* </p>
* <p>
* 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.
* </p>
* <p>
* 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.
* </p>
* <p>
* 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.
* </p>
* <p>
* {@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.
* </p>
* <p>
* 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.
* </p>
* <pre>
* String json = new JSONSerializer()
*      .include("powers.class")
*      .transform( new SimpleTransformer(), "powers.class")
*      .exclude("*.class")
*      .serialize( superhero );
* Hero hero = new JSONDeserializer<Hero>()
*      .use("powers.class", new PackageClassLocator())
*      .deserialize( json );
* </pre>
* <p>
*
* </p>
* <p>
* 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}.
* </p>
* <p>
* 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:
* </p>
* <pre>
* new JSONDeserializer().use("person.friends", ArrayList.class).use("person.friends.values", Frienemies.class)
* </pre>
* <p>
* 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.
* </p>
* <p>
* 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.
* </p>
*/
public class JSONDeserializer<T>
{

  private Map<Class, ObjectFactory> typeFactories= new HashMap<Class, ObjectFactory>();
  private Map<Path, ObjectFactory> pathFactories= new HashMap<Path, ObjectFactory>();
  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<T> use(String path, ClassLocator locator)
  {
    pathFactories.put(Path.parse(path), new ClassLocatorObjectFactory(locator));
    return this;
  }

  public JSONDeserializer<T> use(String path, Class clazz)
  {
    return use(path, new StaticClassLocator(clazz));
  }

  public JSONDeserializer<T> use(Class clazz, ObjectFactory factory)
  {
    typeFactories.put(clazz, factory);
    return this;
  }

  public JSONDeserializer<T> use(String path, ObjectFactory factory)
  {
    pathFactories.put(Path.parse(path), factory);
    return this;
  }

  public JSONDeserializer<T> 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;
  }

}
TOP

Related Classes of flexjson.JSONDeserializer

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.