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

时间:2013-09-13 22:45:25

标签: java json date jackson deserialization

杰克逊有很多来自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<String,Object> parseJSON(InputStream is) {
    Map<String,Object> 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<String,Object> m = mapper.readValue(is, Map.class);
Object o1 = m.get("name");   // o1 is instanceof String
Object o2 = m.get("theDate");  // o2 is instanceof Date

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

4 个答案:

答案 0 :(得分:4)

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

按照以不同方式序列化和反序列化对象的示例:List<POJO>StringStringMapMapList<POJO>再次。显然,在地图中,Date值为String

此解决方案是线程安全的,因为使用 org.joda.time.format.DateTimeFormat org.joda.time.format.DateTimeFormatter ,您可以在此处找到更多信息这篇文章How to deserialize JS date using Jackson?和此链接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;
    }

}

My Serializer:

@Component
public class JsonDateDeSerializer extends JsonDeserializer<Date> {

    // 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<Date> {

    // 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<QueueTask> getActiveActivities(QName taskStatus) {
        final List<QueueTask> listToReturn = new LinkedList<QueueTask>();

        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<QueueTask> 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<Map<String, Object>> listOfMap = mapper.readValue(jsonString, new TypeReference<List<Map<String, Object>>>() {
        });
        assertNotNull(listOfMap);

        final DateFormat formatter = new SimpleDateFormat("dd/MM/yyyy hh:mm:ss");
        for (Map<String, Object> 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<QueueTask> deserializedTaskList = mapper.convertValue(listOfMap, new TypeReference<List<QueueTask>>() {
        });

        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();
    }

}

答案 1 :(得分:3)

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

我将补充说,因为我有一个非常具体的鸭子,所以将String转换为Date的最快实现是一个大约120行的手动卷,验证和设置为日历调整正确的整数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不是线程安全的,因此不考虑在转换器实用程序中使用,我无法对调用者做任何假设。

答案 2 :(得分:2)

我最近一直在寻找相关主题的答案,并提出以下解决方案,感谢 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<String, Object> parseJSON(InputStream is) {
        Map<String, Object> 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<String, Object> 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);
    }
}

答案 3 :(得分:-2)

这是一个关于如何使用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 <E extends ModelObject> void writeTo(E object, Class<E> type, OutputStream out) throws IOException {
        ObjectMapper mapper = getMapper();
        mapper.writeValue(out, object);
    }

    public <E extends ModelObject> void writeTo(E object, Class<E> type, Writer out) throws IOException {
        ObjectMapper mapper = getMapper();
        mapper.writeValue(out, object);
    }

    public <E extends ModelObject> E read(String input, Class<E> 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<String, Date> {

    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();
    }
}

}