JSON Strings and Java Objects

April 8, 2010

Introduction

In the previous posting I used the Jackson JSON Processor libraries to parse and format JSON strings. The Java representation of the JSON String being a HashMap whose contents are named attributes some of which are themselves HashMaps, or indeed arrays of HashMaps. This is a very flexible representation, but writing client code that works with the data is (to my eyes) quite cumbersome:

        HashMap<String,Object> payload
                    = mapper.readValue(from, typeRef);

        Boolean success = (Boolean) payload.get("success");
        System.out.println("success " + success);

        ArrayList<HashMap<String,Object>> artists
              = (ArrayList<HashMap<String,Object>>)
                          payload.get("artists");
        System.out.println("artist count " + artists.size());

        for (HashMap<String,Object> oneArtist: artists){
            System.out.println("artist name " + oneArtist.get("name"));
        }

We see access to attributes such as “success” and “artists” and the requirement for the coder to cast the values to the appropriate type. Note that any errors in either the names of the attributes or their types will not be discovered until runtime.

Coming from an Object Oriented background my personal preference would be to have Object representations of attributes. I would like to have objects such as Artist and Albumn – I feel uncomfortable having

    map.get(“interestingField”)

code scattered across my business logic. (Your tastes may differ!) This article shows how to use Jackson to populate such objects, and in particular how to customize the mapping to Java objects, which is useful in more complex cases.

Sparse Object

Let’s begin with a simple object representing the payload.

    package djna.jackson.eg;

    public class CatalogueResponse {
        public boolean success;
    }

I’m creating this object on the basis of my understanding of the JSON string to be parsed. [For simplicity I’ve made the attribute public, in real life it would be private with a suitable accessor method.]

   {
      "success": true,
      "artists": [
         {
            "name": "Ashley Hutchings",

and to keep things simple I just consider one attribute: “success”.

I adjust my application to specify that CatalogueResponse is the class the parser should produce.

    CatalogueResponse catalogue
             = mapper.readValue(from, CatalogueResponse.class);
    System.out.println("Catalogue " + catalogue);

This code runs but produces the exception:

   Exception org.codehaus.jackson.map.JsonMappingException:
   Unrecognized field "artists"

At development time this is potentially a helpful error message, telling us that our payload object does not match all the fields in the JSON String. When deployed in a production environment we would probably prefer to decouple the client from upgrades to the server that produce new attributes. We’d request that such unmapped fields are silently ignored. This is specified using another configuration feature:

    mapper.getDeserializationConfig().set(    

    DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES,
            false

    );

Running the application now produces the expected (if minimal) output:

    Catalogue djna.jackson.eg.CatalogueResponse@3d0a3d0a
    {
       "success" : true
    }
    Complete.

Objects, Names and Values

We can extend our object to allow access to the artist array. A simple possibility is to add that as an array of Objects:

    public class CatalogueResponse {
        public boolean success;    
        public Object[] artists;
    }

This works, and can be convenient if we have no specific interest in artists, we just get an array of HashMaps as before. More likely we want to create suitable classes for artist and albumnre

    public class Artist {
        public String name;
        public Albumn albumns[];
    }

   public class Albumn {
       public String title;
       public Object[] properties;
   }

and our response class can now refer to Artists

  public class CatalogueResponse {
      public boolean success;    
      public Artist[] artists;
  }

The creation of these payload classes is a little tedious and error-prone but is up-front work producing classes that can be re-used many times.

Custom Mapping

Looking at the Albumn class we see that the “properties” attribute is representing a set of descriptive fields that may be attached to any albumn. This approach gives a flexible data structure that can be extended in the future.

       "properties": [
                     { "name": "artist",
                       "value" : "Michael Chapman"
                     },
                     { "name": "genre",
                       "value" : "instrumental"
                     },
                     { "name": "id",
                       "value" : "BP315CD"
                     }                 

The default mapping produced by Jackson would be an array of HashMaps, each HashMap containing a “name” entry and a “value” entry. It would be much more convenient from the client developer’s point of view to represent the properties  by a single HashMap with keys such as “artist”, “genre” etc. In effect transforming the above structure to this:

      "properties": [
                     { "artist" : "Michael Chapman" },
                     { "genre: "instrumental" },
                     { "id : "BP315CD" } 

This is achieved by using custom serialize/deserialize code.        

Serialization annotation

The first step is to annotate the payload class to specify that we are using a custom mapping.

    public class Albumn {
       public String title;
       @JsonSerialize(using=PropertyMapSerializer.class)
       @JsonDeserialize(using=PropertyMapDeserializer.class)
       public Map<String, String> properties;
    }

Here I’m annotating the attribute directly, specifying the JsonSerialize and JsonDeserialize information. I would usually have a private attribute with getter and setter methods and in which case I would annotate the getter with the JsonSerialize and the setter with JsonDeserialize.

We now need to write the custom Serialize classes as referenced in the annotations.

The Deserializer Class

We create a class

    package djna.jackson.eg;

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

    import org.codehaus.jackson.JsonParser;
    import org.codehaus.jackson.JsonProcessingException;
    import org.codehaus.jackson.map.DeserializationContext;
    import org.codehaus.jackson.map.JsonDeserializer;
    import org.codehaus.jackson.map.ObjectMapper;
    import org.codehaus.jackson.type.TypeReference;

    public class PropertyMapDeserializer
         extends JsonDeserializer<
              Map<String, String>
        >
   {

The key point here is that this class extends the Jackson class JsonDeserialize, which is specialised with the type corresponding to our property field. So

     extends JsonDeserializer<
              Map<String, String>
        >
matches

       public Map<String, String> properties;

The class then also must implement the deserializer method

        @Override
       public Map<String, String> deserialize(
            JsonParser parser, DeserializationContext context)
            throws IOException,
                JsonProcessingException {

The purpose of this method is to read data from the parser and construct and return the value that will be placed into our property.

Implementing the Deserializer

The implementation is in three steps: initialisation of some variables, reading the raw data and constructing the property hash map.

Initialisation

We initialise two variables

    Map<String, String>  retMap =
              new HashMap<String, String>();

    TypeReference<HashMap<String,String>[]>  typeRef
            = new TypeReference<
               HashMap<String,String>[]
             >() {};

The retMap is the return value we are populating. The typeRef is going to be used when reading the JSON string to specify the type of the raw data to be produced. [This is a common Jackson idiom that I used in the previous article.]

Reading Raw Data

We read the raw data into a natural representation of the JSON string using a Jackson ObjectMapper.

       ObjectMapper mapper = new ObjectMapper();
       HashMap<String, String>[] maps
           = mapper.readValue(parser, typeRef);

In this case we’re reading an array of HashMaps. Each HashMap in the array contains the “name” and “value” corresponding to a property:

 

       "properties": [
                     { "name": "artist",
                       "value" : "Michael Chapman"
                     },

We then transfer this data into the single hash map we are creating

Populate Returned Hash Map

We simply iterate the array and extract the name and value. (Yes, there should be some error handling.)

    for (HashMap<String, String> oneMap : maps){
             String name = oneMap.get("name");
             String value = oneMap.get("value");
             retMap.put(name, value);   
       }       
       return retMap;

 

Using the HashMap

The client code can now access the Albumn properties in quite a natural way:

    albumn.properties.get(“genre”);

Next we need to provide the inverse functionality, to serialize to a JSON string.

Implementing the Serializer

The serialization code is also quite simple. First we create the class, which must extend an appropriate specialisation of the Jackson JsonSerializer

    package djna.jackson.eg;

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

    import org.codehaus.jackson.JsonGenerator;
    import org.codehaus.jackson.JsonParser;
    import org.codehaus.jackson.JsonProcessingException;
    import org.codehaus.jackson.map.JsonSerializer;
    import org.codehaus.jackson.map.ObjectMapper;
    import org.codehaus.jackson.map.SerializerProvider;

    public class PropertyMapSerializer extends JsonSerializer<
              Map<String, String>
        >
    {

Note that the specialisation matches the type of the field we are serialising.

   @JsonSerialize(using=PropertyMapSerializer.class)
   @JsonDeserialize(using=PropertyMapDeserializer.class)
   public Map<String, String> properties;

We then need to implement the serialisation method:

@Override
    public void serialize(Map<String, String> data,
            JsonGenerator generator,
            SerializerProvider provider) throws IOException,
            JsonProcessingException {

The requirement is to write to the JsonGenerator an appropriate representation of the Map<String, String> data received. We could do this by constructing an array of HashMaps, but instead I decided to create an array of a simple Property class. I think that this make for more readable code.

The Property Class

The class is a simple representation of the property data with a convenient constructor.

    public class Property {
       
       public Property(String newName, String newValue){
          name = newName;
          value = newValue;
       }
       public String name;
       public String value;

    }

The payload we are writing is a representation of an array of these objects.

Preparing the Payload

The payload is obtained from the data paramter passed by Jackson to our serialisation method

        Property values[] = new Property[data.size()];
        int i = 0;
        for ( String key: data.keySet() ){
            values[i++] = new Property(key, data.get(key));
        }

we now have a payload to write:

Writing the payload

Once again we construct a mapper object and use it to write our property array to the generator, which Jackson supplied to our serlialisation method.

       ObjectMapper mapper = new ObjectMapper();
        mapper.writeValue(generator, values);

And that completes the custom mapping.

Summary

Although it has taken a while to explain each step in preparing a mapping between Java Classes and JSON representations I wrote the code with very little effort, the Jackson programming model seems very effective.

One other note: You might feel that the custom mapping example I chose is somewhat contrived. In fact it is a simplified form of a real-life system I have been working with. The capability to provide a suitable custom mapping of the JSON payload is extremely useful in my real-world example.

About these ads

14 Responses to “JSON Strings and Java Objects”

  1. Vikas said

    This is the best article I’ve seen regarding Ser/Deser, with clean explaination. Great!!!

  2. Karl said

    You don’t happen to have a zip file of the complete source code for this sample do you?

  3. Jeff Zhou said

    Hi djna, you referred this link to me. My question, however, may be somewhat different.

    I have a class defined as follow:

    public class JST {

    class Data{
    public HashMap Data;
    }

    public HashMap desc;
    public ArrayList Datalist;

    public JST(){}

    //Getter and Setter functions omitted for convenient look

    }

    And the main function is as follow:

    public static void main(String[] args) throws JsonGenerationException, JsonMappingException, IOException{

    //Omit initialization of the jst variable and its children variables

    ObjectMapper mapper = new ObjectMapper();
    mapper.writeValue(new File(“c:/yyt/test/user-modified.json”), jst);

    }

    My question is that the output file looks like this:

    {“desc”:{},”reviewlist”:[{},{},{},{},{},{},{},{},{},{}]}

    How haven’t the values initialized been printed out? Which part of your article will you point out as a solution to my problem?

    Thanks

  4. djna said

    Hi Jeff,

    I read your question as :

    “It seems that I need to add a serializor, but how?”

    And the referenced article I think talks about creating new serialializers. I’m afraid I don’t have time to look in detail at your problem, my guess is that your serializer isn’t quite doing the right thing. I’d be inclined to focus on serializing just one item in your dataList (or is it review list) and once you have confidence in that, then look at the more complex object. Good luck …

  5. Shervin said

    Great post, saved me a lot of time thank you!

  6. wang said

    Hi djna. Your article is greate, and it ‘s very helpful for me .

    Now, I face a new proplem.

    My POJO :

    class Person{
    public String name;
    public int age;

    }

    And the JSON is {“name”:”Jackson”,”age”:””}.

    If I write the code like this:

    Person person = mapper.readValue(“{\”name\”:\”wanxf\”,\”age\”:\”\”}”, Person.class);

    A Exception is thrown, it complains that

    Can not construct instance of int from String value ”: not a valid int value.

    If the JSON is “{\”name\”:\”Jackson\”,\”age\”:null}”, it’s OK.

    But now , I don’t want to modify the JSON. And how can I do ?

    • djna said

      Thanks for your interest. For this kind of question I think it’s best to post to a wider forum. I suggest that you ask the question on http://www.stackoverflow.com, and I’ll have a try to answer it there – I’m djna over there too. The reason I like StackOverflow is that there’s a wider range of expertise there (some really very clever people) and the speed of answering tends to be very quick. Here, I might be out of teh office for a few days and not see questions like this immediately.

  7. Jason Reast-Jones said

    Thanks Dave, this was exactly what I needed to get Jackson behaving as I’d hoped it would. I did have to reassemble your code in Eclipse so a source attachment would be handy for others I’m sure. Thanks though!

  8. […] I can not find a standard solution for this. Everybody seems to be implementing his own wrapper solution. This requirement seems extremly basic to me; I can’t believe that this is the generally […]

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: