Jackson De /在通用地图中序列化日期到字符串的日期

jackson有很多来自java.util.Date代码的例子,但他们似乎都在利用POJO注释。 我有通用的标量映射,我希望将其序列化为JSON。 这是当前的解串器设置; 非常简单:

public class JSONUtils { static { DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS"); mapper = new ObjectMapper(); mapper.configure(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS, true); mapper.setDateFormat(df); // this works for outbounds but has no effect on inbounds mapper.getDeserializationConfig().with(df); // Gave this a shot but still does not sniff strings for a format that we declare should be treated as java.util.Date } public static Map parseJSON(InputStream is) { Map data = null; try { data = mapper.readValue(is, Map.class); } catch(Exception e) { // ... } return data; } 

我知道dateserializer可以将java.util.Date转换为ISO 8601-ish字符串。 这是困扰我的另一种方式。 显然,在没有上下文的JSON文档中,字符串是一个字符串,所以我不知道它是否曾经是一个日期。 因此,我准备对其进行类型化处理并检查所有被反序列化的字符串,如果它们闻起来像YYYY-MM-DDTHH:MM:SS.sss日期时间,那么我将创建一个java.util.Date而不是仅传回一个String。 所以给出:

 { "name": "buzz", "theDate": "2013-09-10T12:00:00.000" } 

会屈服

 Map m = mapper.readValue(is, Map.class); Object o1 = m.get("name"); // o1 is instanceof String Object o2 = m.get("theDate"); // o2 is instanceof Date 

但这意味着解串器必须返回两种不同的类型,而我无法弄清楚如何在jackson中做到这一点。 有没有人知道一个好的,紧凑的例子,它会嗅到类似日期的字符串并将它们变成日期,将其他人留作字符串?

如果你有一个POJO ,你可以使用序列化器和反序列化器在get和set方法上轻松使用注释。

下面以不同方式序列化和反序列化对象的示例: ListStringString to MapMap to List 。 显然,在地图中, Date值为String

这个解决方案是线程安全的,因为使用org.joda.time.format.DateTimeFormatorg.joda.time.format.DateTimeFormatter ,你可以在这里找到更多信息如何使用Jackson反序列化JS日期? 这个链接http://fahdshariff.blogspot.co.uk/2010/08/dateformat-with-multiple-threads.html

我的POJO:

 @JsonAutoDetect public class QueueTask implements Serializable { private static final long serialVersionUID = -4411796657106403937L; public enum ActivitiQueueStatus { IN_PROGRESS(AsyncProcessingWorkflowContentModel.InProgressTask.TYPE.getLocalName()), // IN_QUEUE(AsyncProcessingWorkflowContentModel.InQueueTask.TYPE.getLocalName()); private String value; private ActivitiQueueStatus(final String value) { this.value = value; } public static ActivitiQueueStatus enumOf(final String value) { for (ActivitiQueueStatus enum_i : values()) { if (enum_i.value.equals(value)) return enum_i; } throw new IllegalArgumentException("value '" + value + "' is not a valid enum"); } } private String user; private Date creationDate; private int noRowsSelected; private ActivitiQueueStatus status; public String getUser() { return user; } public void setUser(String user) { this.user = user; } @JsonSerialize(using = JsonDateSerializer.class) public Date getCreationDate() { return creationDate; } @JsonDeserialize(using = JsonDateDeSerializer.class) public void setCreationDate(Date creationDate) { this.creationDate = creationDate; } public int getNoRowsSelected() { return noRowsSelected; } public void setNoRowsSelected(int noRowsSelected) { this.noRowsSelected = noRowsSelected; } public ActivitiQueueStatus getStatus() { return status; } public void setStatus(ActivitiQueueStatus status) { this.status = status; } } 

我的序列化器:

 @Component public class JsonDateDeSerializer extends JsonDeserializer { // use joda library for thread safe issue private static final DateTimeFormatter dateFormat = DateTimeFormat.forPattern("dd/MM/yyyy hh:mm:ss"); @Override public Date deserialize(final JsonParser jp, final DeserializationContext ctxt) throws IOException, JsonProcessingException { if (jp.getCurrentToken().equals(JsonToken.VALUE_STRING)) return dateFormat.parseDateTime(jp.getText().toString()).toDate(); return null; } } 

和解串器:

 @Component public class JsonDateSerializer extends JsonSerializer { // use joda library for thread safe issue private static final DateTimeFormatter dateFormat = DateTimeFormat.forPattern("dd/MM/yyyy hh:mm:ss"); @Override public void serialize(final Date date, final JsonGenerator gen, final SerializerProvider provider) throws IOException, JsonProcessingException { final String formattedDate = dateFormat.print(date.getTime()); gen.writeString(formattedDate); } } 

我的服务:

 public class ServiceMock { // mock this parameter for usage. public List getActiveActivities(QName taskStatus) { final List listToReturn = new LinkedList(); final SimpleDateFormat dateFormat = new SimpleDateFormat("dd/MM/yyyy hh:mm:ss"); Date d1 = null, d2 = null, d3 = null, d4 = null, d5 = null; try { d1 = dateFormat.parse("01/02/2013 12:44:44"); d2 = dateFormat.parse("21/12/2013 16:44:44"); d3 = dateFormat.parse("21/12/2013 16:45:44"); d4 = dateFormat.parse("21/12/2013 16:44:46"); d5 = dateFormat.parse("11/09/2013 16:44:44"); } catch (ParseException e) { } QueueTask dataSet = new QueueTask(); dataSet = new QueueTask(); dataSet.setUser("user_b"); dataSet.setStatus(ActivitiQueueStatus.enumOf("placeInQueue")); dataSet.setNoRowsSelected(500); dataSet.setCreationDate(d1); listToReturn.add(dataSet); dataSet = new QueueTask(); dataSet.setUser("user_d"); dataSet.setStatus(ActivitiQueueStatus.enumOf("placeInQueue")); dataSet.setNoRowsSelected(300); dataSet.setCreationDate(d2); listToReturn.add(dataSet); dataSet = new QueueTask(); dataSet.setUser("user_a"); dataSet.setStatus(ActivitiQueueStatus.enumOf("inProgress")); dataSet.setNoRowsSelected(700); dataSet.setCreationDate(d3); listToReturn.add(dataSet); dataSet = new QueueTask(); dataSet.setUser("user_k"); dataSet.setStatus(ActivitiQueueStatus.enumOf("inProgress")); dataSet.setNoRowsSelected(700); dataSet.setCreationDate(d4); listToReturn.add(dataSet); dataSet = new QueueTask(); dataSet.setUser("user_l"); dataSet.setStatus(ActivitiQueueStatus.enumOf("inProgress")); dataSet.setNoRowsSelected(700); dataSet.setCreationDate(d5); listToReturn.add(dataSet); return listToReturn; } } 

主要用途:

 public class SerializationServiceTest { private static final Logger LOGGER = LoggerFactory.getLogger(OUPQueueStatusServiceIT.class); public void testGetActiveActivitiesSerialization() throws Exception { LOGGER.info("testGetActiveActivitiesSerialization - start"); ServiceMock mockedService = new ServiceMock(); // AsyncProcessingWorkflowContentModel.InProgressTask.TYPE is an QName, mock this calling List tasks = mockedService.getActiveActivities(AsyncProcessingWorkflowContentModel.InProgressTask.TYPE); assertNotNull(tasks); assertTrue(tasks.size() == 5); assertNotNull(tasks.get(0).getUser()); assertNotNull(tasks.get(0).getCreationDate()); assertNotNull(tasks.get(0).getStatus()); assertNotNull(tasks.get(0).getNoRowsSelected()); final ObjectMapper mapper = new ObjectMapper(); final String jsonString = mapper.writeValueAsString(tasks); assertNotNull(jsonString); assertTrue(jsonString.contains("creationDate")); // test serialization from string to Map final List> listOfMap = mapper.readValue(jsonString, new TypeReference>>() { }); assertNotNull(listOfMap); final DateFormat formatter = new SimpleDateFormat("dd/MM/yyyy hh:mm:ss"); for (Map map_i : listOfMap) { // check date value assertTrue(map_i.containsKey("creationDate")); final Date date = formatter.parse("" + map_i.get("creationDate")); assertNotNull(date); assertNotNull(map_i.get("user")); assertNotNull(map_i.get("status")); assertNotNull(ActivitiQueueStatus.valueOf("" + map_i.get("status"))); assertNotNull(map_i.get("noRowsSelected")); } // test de-serialization List deserializedTaskList = mapper.convertValue(listOfMap, new TypeReference>() { }); assertNotNull(deserializedTaskList); assertTrue(deserializedTaskList.size() == 5); for (QueueTask t : deserializedTaskList) { assertNotNull(t.getUser()); assertNotNull(t.getCreationDate()); assertNotNull(t.getDownloadType()); assertNotNull(t.getStatus()); } LOGGER.info("testGetActiveActivitiesSerialization - end"); } public static void main(String[] args) throws Exception { new SerializationServiceTest().SerializationServiceTest(); } } 

经过几个星期的讨论(没有其他评论或答案),我现在相信我所寻求的是jackson不可能的。 将JSON反序列化为具有日期鸭子类型的映射必须在事后进行。 没有办法插入解析流,为YYYY-MM-DDTHH:MM:SS.SSS嗅探字符串,并在匹配时替换Date对象而不是String 。 你必须让jackson建立Map ,然后在jackson之外回到顶部并走过Map ,嗅探日期。

我将补充一点,因为我有一个非常具体的鸭我正在寻找,将String转换为Date的最快实现是一个大约120行长的手动卷事件validation并为Calendar设置正确的整数mdyhms-ms然后调用getTime() 。 10,000,000转换需要4240毫秒,或约2.3米/秒。

在joda-time大厅播出之前,是的,我先尝试过:

 // This is set up ONCE, outside the timing loop: DateTimeFormatter format = ISODateTimeFormat.dateHourMinuteSecondMillis(); // These are in the timing loop: while(loop) { DateTime time = format.parseDateTime("2013-09-09T14:45:00.123"); Date d = time.toDate(); } 

运行大约9630毫米,大约1.04米/秒; 速度的一半。 但这仍然比“开箱即用的javax”选项更快:

 java.util.Calendar c2 = javax.xml.bind.DatatypeConverter.parseDateTime(s); Date d = c2.getTime(); 

这需要30428工厂运行,大约0.33米/秒 – 几乎比手动工具慢7倍。

SimpleDateFormat不是线程安全的,因此不考虑在转换器实用程序中使用,我不能对调用者做任何假设。

我最近一直在寻找相关主题的答案,并提出以下解决方案,感谢Justin Musgrove和他的文章Custom jackson date deserializer 。 基本上,我们的想法是替换Object.class的标准反序列化器,它将指定格式的任何字符串转换为Date对象,否则回退到标准行为。 显然,此操作是以额外处理为代价的,因此您需要为此配置ObjectMapper的专用实例,并且仅在绝对必要时使用它或者无论如何都准备好进行第二次传递。

请注意,示例中的日期字符串格式没有时区组件,这可能会导致一些问题,但我将格式保留为请求。 您可以使用您选择的解析器代替Apache Commons Lang的FastDateFormat。 我实际上在我的情况下使用Instant。

CustomObjectDeserializer.java

 import java.io.IOException; import org.apache.commons.lang3.time.FastDateFormat; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.JsonTokenId; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.deser.std.UntypedObjectDeserializer; public class CustomObjectDeserializer extends UntypedObjectDeserializer { private static final long serialVersionUID = 1L; private static final FastDateFormat format = FastDateFormat.getInstance("yyyy-MM-dd'T'HH:mm:ss.SSS"); public CustomObjectDeserializer() { super(null, null); } @Override public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException { if (p.getCurrentTokenId() == JsonTokenId.ID_STRING) { try { String value = p.getText(); // put your own parser here return format.parse(value); } catch (Exception e) { return super.deserialize(p, ctxt); } } else { return super.deserialize(p, ctxt); } } } 

JSONUtils.java

 import java.io.ByteArrayInputStream; import java.io.InputStream; import java.nio.charset.StandardCharsets; import java.util.Map; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.module.SimpleModule; public class JSONUtils { private static final ObjectMapper mapper = new ObjectMapper(); static { mapper.configure(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS, true); SimpleModule module = new SimpleModule("DateConverter"); // register a new deserializer extending and replacing UntypedObjectDeserializer module.addDeserializer(Object.class, new CustomObjectDeserializer()); mapper.registerModule(module); } public static Map parseJSON(InputStream is) { Map data = null; try { data = mapper.readValue(is, Map.class); } catch (Exception e) { // ... e.printStackTrace(); } return data; } public static void main(String[] args) throws Exception { String input = "{\"name\": \"buzz\", \"theDate\": \"2013-09-10T12:00:00.000\"}"; InputStream is = new ByteArrayInputStream(input.getBytes(StandardCharsets.UTF_8)); Map m = mapper.readValue(is, Map.class); Object o1 = m.get("name"); // o1 is instanceof String Object o2 = m.get("theDate"); // o2 is instanceof Date System.out.println(o1.getClass().getName() + " : " + o1); System.out.println(o2.getClass().getName() + " : " + o2); } } 

以下是如何使用Jackson序列化反序列化对象日期的基本示例

公共课JacksonSetup {

 private static class JacksonSerializer { private static JacksonSerializer instance; private JacksonSerializer() { } public static JacksonSerializer getInstance() { if (instance == null) instance = new JacksonSerializer(); return instance; } public  void writeTo(E object, Class type, OutputStream out) throws IOException { ObjectMapper mapper = getMapper(); mapper.writeValue(out, object); } public  void writeTo(E object, Class type, Writer out) throws IOException { ObjectMapper mapper = getMapper(); mapper.writeValue(out, object); } public  E read(String input, Class type) throws IOException { ObjectMapper mapper = getMapper(); E result = (E) mapper.readValue(input, type); return result; } private ObjectMapper getMapper() { ObjectMapper mapper = new ObjectMapper(); mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); AnnotationIntrospector introspector = new JaxbAnnotationIntrospector(mapper.getTypeFactory()); mapper.setAnnotationIntrospector(introspector); return mapper; } } private static class JaxbDateSerializer extends XmlAdapter { private SimpleDateFormat dateFormat = new SimpleDateFormat("MM-dd-yyyy"); @Override public String marshal(Date date) throws Exception { return dateFormat.format(date); } @Override public Date unmarshal(String date) throws Exception { return dateFormat.parse(date); } } private static abstract class ModelObject { } private static class Person extends ModelObject { private String name; private Date bday; public String getName() { return name; } public void setName(String name) { this.name = name; } @XmlElement(name = "birth-day") @XmlJavaTypeAdapter(JaxbDateSerializer.class) public Date getBday() { return bday; } public void setBday(Date bday) { this.bday = bday; } } public static void main(String[] args) { try { Person person = new Person(); person.setName("Jhon Doe"); person.setBday(new Date()); Writer writer = new StringWriter(); JacksonSerializer.getInstance().writeTo(person, Person.class, writer); System.out.println(writer.toString()); } catch (Exception e) { e.printStackTrace(); } } 

}