But in some cases we really need to improvised, for example in the project I'm working right now I have a date string from a .net application in this format: "2014-04-08T07:08:48.1344874Z". As you can see it has 7 characters in the millisecond section, while java normally use 3. Of course you'll say just use a simpleDateFormat that will parse the 7 characters, I've thought of that and here's the result
SimpleDateFormat df1 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSSSSSX");
Date d1 = df1.parse("2014-04-07T15:20:40.7439627Z");
System.out.println("D1: " + d1); // produces "Mon Apr 07 19:24:39 CEST 2014"
//as I said it works on 3 character millies
SimpleDateFormat df2 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSX");
Date d2 = df2.parse("2014-04-07T15:20:40.743Z");
System.out.println("D2: " + d2); // produces "Mon Apr 07 17:20:40 CEST 2014"
Note that I'm using UTC+2.
I found a solution that is worth mentioning, although it didn't really work 100% for me. Normally when you generate classes from xsd using xjc, it will use an XmlGregorianCalendar object. So the first problem is how will I convert it to a Date object?
1.) Create a xs:dateTime binding, xs:dateTime is what you'll see in your xsd.
<?xml version="1.0" encoding="UTF-8"?>
<bindings xmlns="http://java.sun.com/xml/ns/jaxb" version="2.0"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
<globalBindings>
<javaType name="java.util.Date" xmlType="xs:dateTime"
parseMethod="org.czetsuya.xsd.converter.XsdDateTimeConverter.unmarshal"
printMethod="org.czetsuya.xsd.converter.XsdDateTimeConverter.marshalDateTime" />
<javaType name="java.util.Date" xmlType="xs:date"
parseMethod="org.czetsuya.xsd.converter.XsdDateTimeConverter.unmarshal"
printMethod="org.czetsuya.xsd.converter.XsdDateTimeConverter.marshalDate" />
</globalBindings>
</bindings>
2.) Generate the java class from xsd, I normally do it via eclipse using the jaxb converter from jaxb schema to java class.
What it does is create a JavaType adapter and replace XmlGregorianCalendar with a date object. The xs:dateTime fields will be annotated like this:
@XmlJavaTypeAdapter(Adapter1 .class)
@XmlSchemaType(name = "dateTime")
protected Date myDate;
And it will work, and so I thought but it did not :-) Same problem I describe earlier. Btw, I'm using jackson ObjectMapper to parse a json string into an object like this:
ObjectMapper mapper = ObjectMapperFactory.createObjectMapper();
messageWrapper = mapper.readValue(message, MyClass.class);
That code successfully parsed my json string but the date values are still totally wrong no matter the data type.
And so the final part of the solution. Customizing a Json de-serializer for object of type Date. See our XmlGregorianCalendar to Date comes into place.
So our converter:
package org.czetsuya.xsd.converter;
import java.io.IOException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.GregorianCalendar;
import javax.xml.bind.DatatypeConverter;
import org.apache.log4j.Logger;
import org.codehaus.jackson.JsonParser;
import org.codehaus.jackson.JsonProcessingException;
import org.codehaus.jackson.map.DeserializationContext;
import org.codehaus.jackson.map.JsonDeserializer;
/**
* @author Edward P. Legaspi
**/
public class XsdDateTimeConverter extends JsonDeserializer{
protected static Logger log = Logger.getLogger(XsdDateTimeConverter.class);
public static Date unmarshal(String dateTime) {
return DatatypeConverter.parseDate(dateTime).getTime();
}
public static String marshalDate(Date date) {
final GregorianCalendar calendar = new GregorianCalendar();
calendar.setTime(date);
return DatatypeConverter.printDate(calendar);
}
public static String marshalDateTime(Date dateTime) {
final GregorianCalendar calendar = new GregorianCalendar();
calendar.setTime(dateTime);
return DatatypeConverter.printDateTime(calendar);
}
@Override
public Date deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
// format: 2014-04-08T07:08:48.1344874Z
String date = jp.getText();
if (date == null || date.length() != 28) {
log.debug("Null or invalid date=" + date);
return null;
}
// remove extra 4 milliseconds
date = date.substring(0, date.length() - 5)
+ date.charAt(date.length() - 1);
SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSX");
try {
return sf.parse(date);
} catch (ParseException e) {
log.error("Error parsing date=" + date);
return null;
}
}
}
Well you can disregard the first 3 methods as it really not use. The most important part is in the deserialize method. It removes the last 4 millisecond characters so java can understand and parse it to a Date object. But how will the mapper know about this converter, of course you have to tell it :-)
ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule("czetsuya", Version.unknownVersion());
module.addDeserializer(Date.class, new XsdDateTimeConverter());
mapper.registerModule(module);
And that's it when you call the reader from the mapper it should now parse the correct value of date string.
References:
http://stackoverflow.com/questions/6553051/setting-up-json-custom-deserializer
http://stackoverflow.com/questions/5106987/jax-ws-and-joda-time
http://www.baeldung.com/jackson-deserialization
http://stackoverflow.com/questions/21706415/jackson-objectmapper-using-custom-serializers-and-deserializers
http://blog.palominolabs.com/2012/06/05/writing-a-custom-jackson-serializer-and-deserializer/
0 nhận xét:
Đăng nhận xét