9

有许多 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<String,Object> parseJSON(InputStream is) {
    Map<String,Object> data = null;

    try {
        data = mapper.readValue(is, Map.class);
    } catch(Exception e) {
      // ...
    }

    return data;
}

我认为 dateerializer 可以将 java.util.Date 转换为 ISO 8601-ish 字符串。它正在以另一种方式使我感到困惑。显然,在没有上下文的 JSON 文档中,字符串是字符串,所以我不知道它是否曾经是日期。所以我准备回避输入这个并检查所有被反序列化的字符串,如果它们闻起来像 YYYY-MM-DDTHH:MM:SS.sss 日期时间,那么我将创建一个 java.util.Date 而不是仅仅传回一个字符串。所以给出:

{ "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

4 回答 4

5

我最近一直在寻找相关主题的答案,并提出了以下解决方案,这要感谢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);
    }
}
于 2016-09-17T03:02:30.813 回答
4

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

下面是一个以不同方式序列化和反序列化对象的示例:List<POJO>to StringStringtoMapMaptoList<POJO>再次。显然,在地图中的Date值是String

该解决方案是线程安全的,因为使用org.joda.time.format.DateTimeFormatorg.joda.time.format.DateTimeFormatter,您可以在这篇文章中找到更多信息如何使用杰克逊反序列化 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<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();
    }

}
于 2014-11-17T12:38:03.390 回答
3

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

我要补充一点,因为我正在寻找一个非常具体的鸭子,将 String 转换为 Date 的最快实现是一个大约 120 行长的手动操作,它验证并为 Calendar 然后设置正确的整数 mdyhms-ms来电getTime()。10,000,000 次转换需要 4240 毫秒,即大约 2.3m/秒。

在 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不是线程安全的,因此不考虑在转换器实用程序中使用,因为我无法对调用者做出任何假设。

于 2013-10-09T15:10:23.623 回答
-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();
    }
}

}

于 2013-09-14T00:13:19.923 回答