blob: 3d7d6a689890e8d7d0dfe3ee787665e5568ffad8 [file] [log] [blame]
package org.codehaus.jackson.map.ext;
import java.io.IOException;
import java.util.*;
import org.joda.time.*;
import org.joda.time.format.DateTimeFormatter;
import org.joda.time.format.ISODateTimeFormat;
import org.codehaus.jackson.*;
import org.codehaus.jackson.map.*;
import org.codehaus.jackson.map.deser.std.StdDeserializer;
import org.codehaus.jackson.map.deser.std.StdScalarDeserializer;
import org.codehaus.jackson.map.util.Provider;
/**
* Provider for deserializers that handle some basic data types
* for <a href="http://joda-time.sourceforge.net/">Joda</a> date/time library.
*
* @since 1.4
*/
public class JodaDeserializers
implements Provider<StdDeserializer<?>>
{
@Override
public Collection<StdDeserializer<?>> provide() {
return Arrays.asList(new StdDeserializer<?>[] {
new DateTimeDeserializer<DateTime>(DateTime.class)
,new DateTimeDeserializer<ReadableDateTime>(ReadableDateTime.class)
,new DateTimeDeserializer<ReadableInstant>(ReadableInstant.class)
,new LocalDateDeserializer()
,new LocalDateTimeDeserializer()
,new DateMidnightDeserializer()
,new PeriodDeserializer()
});
}
/*
/*********************************************************************
/* Intermediate base classes
/*********************************************************************
*/
abstract static class JodaDeserializer<T> extends StdScalarDeserializer<T>
{
final static DateTimeFormatter _localDateTimeFormat = ISODateTimeFormat.localDateOptionalTimeParser();
protected JodaDeserializer(Class<T> cls) { super(cls); }
protected DateTime parseLocal(JsonParser jp)
throws IOException, JsonProcessingException
{
String str = jp.getText().trim();
if (str.length() == 0) { // [JACKSON-360]
return null;
}
return _localDateTimeFormat.parseDateTime(str);
}
}
/*
/*********************************************************************
/* Concrete deserializers
/*********************************************************************
*/
/**
* Basic deserializer for {@link DateTime}. Accepts JSON String and Number
* values and passes those to single-argument constructor.
* Does not (yet?) support JSON object; support can be added if desired.
*<p>
* Since 1.6 this has been generic, to handle multiple related types,
* including super types of {@link DateTime}
*/
public static class DateTimeDeserializer<T extends ReadableInstant>
extends JodaDeserializer<T>
{
public DateTimeDeserializer(Class<T> cls) { super(cls); }
@SuppressWarnings("unchecked")
@Override
public T deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException
{
JsonToken t = jp.getCurrentToken();
if (t == JsonToken.VALUE_NUMBER_INT) {
return (T) new DateTime(jp.getLongValue(), DateTimeZone.UTC);
}
if (t == JsonToken.VALUE_STRING) {
String str = jp.getText().trim();
if (str.length() == 0) { // [JACKSON-360]
return null;
}
return (T) new DateTime(str, DateTimeZone.UTC);
}
throw ctxt.mappingException(getValueClass());
}
}
/**
* @since 1.5
*/
public static class LocalDateDeserializer
extends JodaDeserializer<LocalDate>
{
public LocalDateDeserializer() { super(LocalDate.class); }
@Override
public LocalDate deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException
{
// We'll accept either long (timestamp) or array:
if (jp.isExpectedStartArrayToken()) {
jp.nextToken(); // VALUE_NUMBER_INT
int year = jp.getIntValue();
jp.nextToken(); // VALUE_NUMBER_INT
int month = jp.getIntValue();
jp.nextToken(); // VALUE_NUMBER_INT
int day = jp.getIntValue();
if (jp.nextToken() != JsonToken.END_ARRAY) {
throw ctxt.wrongTokenException(jp, JsonToken.END_ARRAY, "after LocalDate ints");
}
return new LocalDate(year, month, day);
}
switch (jp.getCurrentToken()) {
case VALUE_NUMBER_INT:
return new LocalDate(jp.getLongValue());
case VALUE_STRING:
DateTime local = parseLocal(jp);
if (local == null) {
return null;
}
return local.toLocalDate();
}
throw ctxt.wrongTokenException(jp, JsonToken.START_ARRAY, "expected JSON Array, String or Number");
}
}
/**
* @since 1.5
*/
public static class LocalDateTimeDeserializer
extends JodaDeserializer<LocalDateTime>
{
public LocalDateTimeDeserializer() { super(LocalDateTime.class); }
@Override
public LocalDateTime deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException
{
// We'll accept either long (timestamp) or array:
if (jp.isExpectedStartArrayToken()) {
jp.nextToken(); // VALUE_NUMBER_INT
int year = jp.getIntValue();
jp.nextToken(); // VALUE_NUMBER_INT
int month = jp.getIntValue();
jp.nextToken(); // VALUE_NUMBER_INT
int day = jp.getIntValue();
jp.nextToken(); // VALUE_NUMBER_INT
int hour = jp.getIntValue();
jp.nextToken(); // VALUE_NUMBER_INT
int minute = jp.getIntValue();
jp.nextToken(); // VALUE_NUMBER_INT
int second = jp.getIntValue();
// let's leave milliseconds optional?
int millisecond = 0;
if (jp.nextToken() != JsonToken.END_ARRAY) { // VALUE_NUMBER_INT
millisecond = jp.getIntValue();
jp.nextToken(); // END_ARRAY?
}
if (jp.getCurrentToken() != JsonToken.END_ARRAY) {
throw ctxt.wrongTokenException(jp, JsonToken.END_ARRAY, "after LocalDateTime ints");
}
return new LocalDateTime(year, month, day, hour, minute, second, millisecond);
}
switch (jp.getCurrentToken()) {
case VALUE_NUMBER_INT:
return new LocalDateTime(jp.getLongValue());
case VALUE_STRING:
DateTime local = parseLocal(jp);
if (local == null) {
return null;
}
return local.toLocalDateTime();
}
throw ctxt.wrongTokenException(jp, JsonToken.START_ARRAY, "expected JSON Array or Number");
}
}
/**
* @since 1.5
*/
public static class DateMidnightDeserializer
extends JodaDeserializer<DateMidnight>
{
public DateMidnightDeserializer() { super(DateMidnight.class); }
@Override
public DateMidnight deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException
{
// We'll accept either long (timestamp) or array:
if (jp.isExpectedStartArrayToken()) {
jp.nextToken(); // VALUE_NUMBER_INT
int year = jp.getIntValue();
jp.nextToken(); // VALUE_NUMBER_INT
int month = jp.getIntValue();
jp.nextToken(); // VALUE_NUMBER_INT
int day = jp.getIntValue();
if (jp.nextToken() != JsonToken.END_ARRAY) {
throw ctxt.wrongTokenException(jp, JsonToken.END_ARRAY, "after DateMidnight ints");
}
return new DateMidnight(year, month, day);
}
switch (jp.getCurrentToken()) {
case VALUE_NUMBER_INT:
return new DateMidnight(jp.getLongValue());
case VALUE_STRING:
DateTime local = parseLocal(jp);
if (local == null) {
return null;
}
return local.toDateMidnight();
}
throw ctxt.wrongTokenException(jp, JsonToken.START_ARRAY, "expected JSON Array, Number or String");
}
}
/**
* @since 1.9.2
*/
public static class PeriodDeserializer
extends JodaDeserializer<ReadablePeriod>
{
public PeriodDeserializer() { super(ReadablePeriod.class); }
@Override
public ReadablePeriod deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException
{
// TODO: perhaps support array of numbers...
//if (jp.isExpectedStartArrayToken()) { ]
switch (jp.getCurrentToken()) {
case VALUE_NUMBER_INT: // assume it's millisecond count
return new Period(jp.getLongValue());
case VALUE_STRING:
return new Period(jp.getText());
}
throw ctxt.wrongTokenException(jp, JsonToken.START_ARRAY, "expected JSON Number or String");
}
}
}