Spring MVC, REST, Joda DateTime & Gson

When building REST web-service with Spring MVC the framework automatically configures message converters based on certain libraries found on the classpath (thanks to <mvc:annotation-driven/>). So it’s enough to have Gson (google-gson) as a dependency (in pom.xml) to make your REST service to produce/accept JSON formatted content. There are other JSON libraries as well but in this article I’m focusing on using Gson.

Unfortunately there is a problem with formatting Joda DateTime fields. By default the JSON output made by Gson for DateTime object looks like this:

 {"iMillis":1465070656778,"iChronology":{"iBase":{"iBase":{"iBase":{"iMinDaysInFirstWeek":4}},"iParam":{"iZone":{
"iTransitions":[-9223372036854775808,-2840145840000,-1717032240000,-1693706400000,-1680483600000,-1663455600000,-1650150000000,-1632006000000,-1618700400000,-1600473600000,-1587168000000,-1501725600000,-931734000000,-857257200000,-844556400000,-828226800000,-812502000000,-796608000000,-778726800000,-762660000000,-748486800000,-733273200000,-715215600000,-701910000000,-684975600000,-670460400000,-654130800000,-639010800000,-397094400000,-386812800000,-371088000000,-355363200000,-334195200000,-323308800000,-307584000000,-291859200000,-271296000000,-260409600000,-239846400000,-228960000000,-208396800000,-197510400000,-176342400000,-166060800000,228873600000,243993600000,260323200000,276048000000,291772800000,307497600000,323827200000,338947200000,354672000000,370396800000,386121600000,401846400000,417571200000,433296000000,449020800000,465350400000,481075200000,496800000000,512524800000,528249600000,543974400000,559699200000,575427600000,591152400000,606877200000,622602000000,638326800000,654656400000,670381200000,686106000000,701830800000,717555600000,733280400000,749005200000,764730000000,780454800000,796179600000,811904400000,828234000000,846378000000],
"iWallOffsets":[5040000,5040000,3600000,7200000,3600000,7200000,3600000,7200000,7200000,10800000,7200000,3600000,7200000,3600000,7200000,3600000,7200000,3600000,7200000,3600000,7200000,3600000,7200000,3600000,7200000,3600000,7200000,3600000,7200000,3600000,7200000,3600000,7200000,3600000,7200000,3600000,7200000,3600000,7200000,3600000,7200000,3600000,7200000,3600000,7200000,3600000,7200000,3600000,7200000,3600000,7200000,3600000,7200000,3600000,7200000,3600000,7200000,3600000,7200000,3600000,7200000,3600000,7200000,3600000,7200000,3600000,7200000,3600000,7200000,3600000,7200000,3600000,7200000,3600000,7200000,3600000,7200000,3600000,7200000,3600000,7200000,3600000,7200000,3600000],
"iStandardOffsets":[5040000,5040000,3600000,3600000,3600000,3600000,3600000,3600000,7200000,7200000,7200000,3600000,3600000,3600000,3600000,3600000,3600000,3600000,3600000,3600000,3600000,3600000,3600000,3600000,3600000,3600000,3600000,3600000,3600000,3600000,3600000,3600000,3600000,3600000,3600000,3600000,3600000,3600000,3600000,3600000,3600000,3600000,3600000,3600000,3600000,3600000,3600000,3600000,3600000,3600000,3600000,3600000,3600000,3600000,3600000,3600000,3600000,3600000,3600000,3600000,3600000,3600000,3600000,3600000,3600000,3600000,3600000,3600000,3600000,3600000,3600000,3600000,3600000,3600000,3600000,3600000,3600000,3600000,3600000,3600000,3600000,3600000,3600000,3600000],
"iNameKeys":["LMT","WMT","CET","CEST","CET","CEST","CET","CEST","EET","EEST","EET","CET","CEST","CET","CEST","CET","CEST","CET","CEST","CET","CEST","CET","CEST","CET","CEST","CET","CEST","CET","CEST","CET","CEST","CET","CEST","CET","CEST","CET","CEST","CET","CEST","CET","CEST","CET","CEST","CET","CEST","CET","CEST","CET","CEST","CET","CEST","CET","CEST","CET","CEST","CET","CEST","CET","CEST","CET","CEST","CET","CEST","CET","CEST","CET","CEST","CET","CEST","CET","CEST","CET","CEST","CET","CEST","CET","CEST","CET","CEST","CET","CEST","CET","CEST","CET"],
"iTailZone":{"iStandardOffset":3600000,"iStartRecurrence":{"iOfYear":{"iMode":"u","iMonthOfYear":3,"iDayOfMonth":-1,"iDayOfWeek":7,"iAdvance":false,"iMillisOfDay":3600000},"iNameKey":"CEST","iSaveMillis":3600000},"iEndRecurrence":{"iOfYear":{"iMode":"u","iMonthOfYear":10,"iDayOfMonth":-1,"iDayOfWeek":7,"iAdvance":false,"iMillisOfDay":3600000},"iNameKey":"CET","iSaveMillis":0},"iID":"Europe/Warsaw"},"iID":"Europe/Warsaw"},"iID":"Europe/Warsaw"}}}} 

To fix it we need to setup a message converter used by Spring MVC to cooperate with Gson. It turns out that GsonHttpMessageConverter class is used as a message converter in this case. This class has property named “gson” of type com.google.gson.Gson. Knowing this we can prepare a solution consisting of following 3 steps:

1. Create Gson TypeAdapter for Joda DateTime

I wanted to represent Joda DateTime objects as numbers of milliseconds so I’ve created the following Gson type-adapter (you can modify this part to obtain other representations):

package kt.data.gson;

import com.google.gson.TypeAdapter;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
import com.google.gson.stream.JsonWriter;
import java.io.IOException;
import org.joda.time.DateTime;

/**
 * Gson adapter for serializing joda DateTime objects.
 */
public class DateTimeAdapter extends TypeAdapter<DateTime> {

	@Override
	public void write(JsonWriter writer, DateTime dt) throws IOException {
		if (dt == null) {
			writer.nullValue();
		} else {
			writer.value(dt.getMillis());
		}
	}

	@Override
	public DateTime read(JsonReader reader) throws IOException {
		if (reader.peek() == JsonToken.NULL) {
			reader.nextNull();
			return null;
		} else {
			return new DateTime(reader.nextLong());
		}
	}
}

2. Create Gson object factory bean

package kt.data.gson;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import org.joda.time.DateTime;
import org.springframework.beans.factory.FactoryBean;

public class GsonFactoryBean implements FactoryBean<Gson> {

	@Override
	public Gson getObject() throws Exception {
		return new GsonBuilder()
			.registerTypeAdapter(DateTime.class, new DateTimeAdapter())
			.create();
	}

	@Override
	public Class<?> getObjectType() {
		return Gson.class;
	}

	@Override
	public boolean isSingleton() {
		return false;
	}	
}

3. Configure Spring MVC

Instead of just <mvc:annotation-driven/> in our Spring context XML configuration file we now need:

    <mvc:annotation-driven>
        <mvc:message-converters>
            <bean class="org.springframework.http.converter.json.GsonHttpMessageConverter">
                <property name="gson">
                    <bean class="kt.data.gson.GsonFactoryBean"/>
                </property>
            </bean>
        </mvc:message-converters>
    </mvc:annotation-driven>  

This works at least for Spring Framework 4.2.6, Gson 2.3.1 and Joda Time 2.9.1.

Additional reading: SPRING: <MVC:MESSAGE-CONVERTERS> EXPLAINED.

Advertisements

About krzysztoftomaszewski

I've got M.Sc. in software engineering. I graduated in 2005 at Institute of Computer Science, Warsaw University of Technology, Faculty of Electronics and Information Technology. I'm working on computer software design and engineering continuously since 2004.
This entry was posted in Java, Spring and tagged , , , . Bookmark the permalink.

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