Working with JSON in Java

In this post I’m gonna show a simple way to work with JSON in Java, converting POJOs to JSON strings and vice-versa. All source code presented here can be downloaded in my GitHub account.

JSON libs

There are many frameworks in Java to facilitate our lives when we want to convert Java objects in JSON strings. You can see many of them here.

One of the most used (and my favorite, btw) is called Jackson. To import it to your project (assuming you have a Maven project), just add the dependency below:

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.6.1</version>
</dependency>

All the examples we gonna do are based on this simple Java object (POJO) called User (I will omit the getters and setters, but you must create them):

import java.util.Calendar;
import java.util.Set;

public class User {
	
    private Long id;
    private String name;
    private Calendar birthDate;
    private Set<String> emails;

    // getters and setters for the 4 properties above

}

Converting Java objects to JSON strings

The process to convert a Java object in a JSON string is called serialization and Jackson turns this very easy. All we need to do is to create an ObjectMapper, the core of Jackson framework convert process, and pass to it an instance of the object we want to serialize.

import com.brunozambiazi.model.User;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.Calendar;
import java.util.HashSet;

public class SerializationTest {

    public static void main(String[] args) throws Exception {
        User user = new User();
        user.setId(1L);
        user.setName("User");
        user.setBirthDate(Calendar.getInstance());
        user.setEmails(new HashSet<String>());
        user.getEmails().add("user@gmail.com");
        user.getEmails().add("user@yahoo.com");
        
        ObjectMapper mapper = new ObjectMapper();
        
        String json = mapper.writeValueAsString(user);
        System.out.println(json);
    }
    
}

Running the code above, you must see this output:

{"id":1,"name":"User","birthDate":1439662844272,"emails":["user@yahoo.com","user@gmail.com"]}

I told you: it’s very easy! If you want to beautify the output, just add this line after instantiate the ObjectMapper:

mapper.enable(SerializationFeature.INDENT_OUTPUT);

And voilà! Our output now will look like this:

{
  "id" : 1,
  "name" : "User",
  "birthDate" : 1439664151489,
  "emails" : [ "user@yahoo.com", "user@gmail.com" ]
}

But, wait a minute. What is that number in the birthDate value? Jackson has his own default serializers for all kind of objects. For Calendar, the default serializer just print the result of #getTimeInMillis() method. If we want to change default behavior, we need to create a custom converter and tell Jackson to use it.

So, let’s create a custom serializer for Calendar class that receives a Calendar object as input parameter and performs a conversion of this to a String with “MM/dd/yyyy” format. It’s important to note the class we need to extending from (JsonSerializer).

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Calendar;

public class CalendarToDateStringSerializer extends JsonSerializer<Calendar> {

    @Override
    public void serialize(Calendar value, JsonGenerator gen, SerializerProvider serializers) throws IOException, JsonProcessingException {
        if (value != null) {        
            SimpleDateFormat sdf = new SimpleDateFormat("MM/dd/yyyy");
            String formatted = sdf.format(value.getTime());
            gen.writeString(formatted);
        }
    }

}

There are at least two approach to use custom converters with Jackson. The first of is to use the annotation @JsonSerialize, passing the custom serializer class as using parameter, in all POJO fields you want to consider your serializer. In our example, we would change the birthDate attribute in User class:

@JsonSerialize(using = CalendarToDateStringSerializer.class)
private Calendar birthDate;

This approach is useful when you want to use custom serializer in a few fields. If your serializer is general and must to be used in all fields of a certain class, the second approach is more appropriate: register a module into the ObjectMapper and so you will not need the annotation approach.

In our example, just change creating ObjectMapper instance snippet for the code below:

SimpleModule module = new SimpleModule();
module.addSerializer(Calendar.class, new CalendarToDateStringSerializer());

ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(module);

Running again the SerializationTest class, you must see something like this:

{
  "id" : 1,
  "name" : "User",
  "birthDate" : "08/15/2015",
  "emails" : [ "user@yahoo.com", "user@gmail.com" ]
}

Converting JSON strings to Java objects

The process to convert a Java object to a string in JSON format is very simple, right? But believe: the opposite process, known as deserialize, is still easier than that. All we need to do is create again an ObjectMapper instance and pass to it the string in JSON format and the class we want the target object.

import com.brunozambiazi.model.User;
import com.fasterxml.jackson.databind.ObjectMapper;

public class DeserializationTest {

    public static void main(String[] args) throws Exception {
        String json = "{\"id\":1,\"name\":\"User\",\"birthDate\":\"08/15/2015\",\"emails\":[\"user@yahoo.com\",\"user@gmail.com\"]}";

        ObjectMapper mapper = new ObjectMapper();
        User user = mapper.readValue(json, User.class);
        System.out.println(user.getName()+" - "+user.getId());
    }
    
}

Running this code you will get… oops!

Exception in thread "main" com.fasterxml.jackson.databind.exc.InvalidFormatException: 
Can not construct instance of java.util.Calendar from String value '08/15/2015': not a valid representation
 (error: Failed to parse Date value '08/15/2015': Can not parse date "08/15/2015": not compatible with any of standard forms ("yyyy-MM-dd'T'HH:mm:ss.SSSZ", "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", "EEE, dd MMM yyyy HH:mm:ss zzz", "yyyy-MM-dd"))
 at [Source: {"id":1,"name":"User","birthDate":"08/15/2015","emails":["user@yahoo.com","user@gmail.com"]}; line: 1, column: 22] (through reference chain: com.brunozambiazi.model.User["birthDate"])
    at com.fasterxml.jackson.databind.exc.InvalidFormatException.from(InvalidFormatException.java:55)
    at com.fasterxml.jackson.databind.DeserializationContext.weirdStringException(DeserializationContext.java:904)
    at com.fasterxml.jackson.databind.deser.std.StdDeserializer._parseDate(StdDeserializer.java:787)
    at com.fasterxml.jackson.databind.deser.std.DateDeserializers$DateBasedDeserializer._parseDate(DateDeserializers.java:175)
    [...]

Do you remember we use a custom serializer to change the way Jackson output a Calendar value? To deserialize correctly, now we need to create a custom deserializer for Calendar, it means to realize the opposite work we did in the serializer.

The Calendar deserializer will get the date string representation, perform a conversion using the format we know (“MM/dd/yyyy”) and return the Calendar instance created with this date. It’s important to note the class we need to extend from (JsonDeserializer).

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import java.io.IOException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;

public class DateStringToCalendarDeserializer extends JsonDeserializer<Calendar> {

    @Override
    public Calendar deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
        try {
            String formatted = p.getValueAsString();
            SimpleDateFormat sdf = new SimpleDateFormat("MM/dd/yyyy");
            
            Calendar calendar = Calendar.getInstance();
            calendar.setTime(sdf.parse(formatted));
            return calendar;
        } catch (ParseException e) {
            throw new IOException(e);
        }
    }

}

We can use this converter by two ways, like we use de serializer converter presented before. The first way is to use an annotation directly in the field we want to convert, like this:

@JsonDeserialize(using = DateStringToCalendarDeserializer.class)
 private Calendar birthDate;

Considering this converter as a general converter for Calendar fields, we can use a simpler solution: register the deserializer as a module of the ObjectMapper. So, change the creating ObjectMapper instance by code snippet below:

SimpleModule module = new SimpleModule();
module.addDeserializer(Calendar.class, new DateStringToCalendarDeserializer());
 
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(module);

And it’s done! Run again the DeserializationTest class and you will get a complete representation of the User instance based on the original JSON string.


Like I said in the begin of this post, there are many frameworks to work with JSON in Java. Jackson is my favourite not just by simplicity but also by the possibility to add more capabilities to it (like work with XML).

Feel free to disagree with me in comments. =)

See you soon.


References:

Advertisements