如何使用Jackson在MongoDb中将日期字段存储为ISODate()

18

我正在尝试使用 fasterxml jackson 将具有 java.util.Date 字段的 java 对象持久化到 mongo 集合中。

问题是 objectMapper 的默认行为是将 Date 存储为 NumberLong 类型。

例如,java.util.Date 类型的 createdTime 字段被存储如下:

"createdTime" : NumberLong("1427728445176")

我想要将其存储为 mongo Shell 中可用的 ISODate 格式。

现在,我知道有一种方法可以格式化 object mapper 以将日期存储为字符串日期格式。但是我只想要使用 ISODate() 格式。

例如: "createdTime" : ISODate("2015-01-20T16:39:42.132Z")

是否有办法实现这一点? 请高人指点。 提前感谢您的帮助。

5个回答

17
你需要的是Jackson Joda模块。如果将其导入类路径中,您可以在映射器上执行以下操作以将其编写为所需的时间戳:
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JodaModule());
mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, true);
mapper.writeValueAsString(date);

您可以根据需要将上面代码示例中的date替换为您的POJO。

编辑:看起来您真正想要的是自定义序列化器。它可能如下所示:

public class IsoDateSerializer extends JsonSerializer<DateTime> {
    @Override
    public void serialize(DateTime value, JsonGenerator jgen, SerializerProvider provider) {
        String isoDate = ISODateTimeFormat.dateTime().print(value);
        jgen.writeRaw("ISODATE(\"" + isoDate + "\")");
    }

然后你要将所有的日期时间类型都在映射器上注册

mapper.addSerializer(DateTime.class, new IsoDateSerializer());

或在函数中使用注释来指定它

@JsonSerializer(using = IsoDateSerializer.class)
public DateTime createdTime;

1
感谢@xathien。我之前已经尝试过这种方法。但是使用这种方法,它基本上创建了一个字符串。例如,将我的代码更改为以下映射器: ObjectMapper mapper = new ObjectMapper(); mapper.registerModule(new JodaModule()); mapper.configure(com.fasterxml.jackson.databind.SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);输出如下 "createdTime":"2015-03-30T19:33:08.921Z"。我想要的格式类似于以下内容。 "createdTime":ISODate(“2015-01-20T16:39:42.132Z”)或者我错过了什么? - vishy
感谢@xathien。我已经更新了帖子并接受了你的回复作为答案,因为它指引了我正确的方向 :) - vishy
@xathien,嘿,难道不应该是 @JsonSerialize 而不是 @JsonSerializer 吗? - Naruto Sempai
是的,但是这个编辑太小了,SO不会接受。 :) - xathien

9
我能够将日期字符串序列化为ISODate格式。我编写了一个自定义日期序列化器如下所示。
public void serialize(Date date, JsonGenerator jgen, SerializerProvider provider) throws IOException {
    String dateValue = getISODateString(date);
    String text = "{ \"$date\" : \""+   dateValue   +"\"}";
    jgen.writeRawValue(text);
}

根据用户@mmx73的要求,我正在添加客户端日期反序列化器的代码。

public class IsoDateDeSerializer extends JsonDeserializer<Date> {

    @Override
    public Date deserialize(JsonParser jsonParser, DeserializationContext deserializationContext)
            throws IOException, JsonProcessingException {
        ObjectCodec oc = jsonParser.getCodec();
        JsonNode node = oc.readTree(jsonParser);
        String dateValue = node.get("$date").asText();

        //DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
        DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
        Date date = null;
        try {
             date = df.parse(dateValue);
        } catch (ParseException e) {
            e.printStackTrace();
        }

        return date;
    }
}

不要硬编码 {} 等内容,我建议使用 JsonGenerator 函数 writeStartObject()writeFieldName(),然后再使用 writeRawValue()。否则,很高兴能帮到你! - xathien
1
@vishy: 感谢上帝,我对此感到非常疯狂。大多数解决方案都能够序列化和反序列化日期,但是在MongoDB中它被存储为字符串或Int64。这两者都不能在添加TTL索引时提供太多帮助。点赞两个大拇指。 - mmx73
1
很高兴它对你有用。 - vishy
@vishy:你解决了从mongoDB读取JSON时如何正确反序列化日期的问题了吗?也许你想要更新你的答案 :-) - mmx73
你太棒了!每当我尝试使用jsonParser.getText()时,它总是返回“{”符号。我只能给一次赞(抱歉)。 - mmx73
1
@vishy:在与管理员讨论了这个重复问题后,我建议您将您的答案标记为被接受的(正确的)答案,因为另一个答案既不完整也不正确。这只是我的意见。 - mmx73

4

这些答案都没有达到我想要的效果。我遇到了麻烦,因为当我将JSON字符串序列化到MongoDB时,它被存储为字符串。虽然是格式良好的字符串,但仍然是一个字符串。

我使用com.fasterxml.jackson.databind.ObjectMapper将我的对象转换为/从JSON,并且我希望继续使用这个类。我有以下方法:

public enum JsonIntent {NONE, MONGODB};
public static ObjectMapper getMapper(final JsonIntent intent) {

    ObjectMapper mapper = new ObjectMapper();
    // Setting to true saves the date as NumberLong("1463597707000")
    // Setting to false saves the data as "2016-05-18T19:30:52.000+0000"

    mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
    mapper.registerModule(new JodaModule());

    if (intent == JsonIntent.MONGODB) {
        // If you want a date stored in MONGO as a date, then you must store it in a way that MONGO
        // is able to deal with it.
        SimpleModule testModule = new SimpleModule("MyModule", new Version(1, 0, 0, null, null, null));

        testModule.addSerializer(Date.class, new StdSerializer<Date>(Date.class) {
            private static final long serialVersionUID = 1L;

            @Override
            public void serialize(Date value, JsonGenerator jgen, SerializerProvider provider) throws IOException {
                try {
                    if (value == null) {
                        jgen.writeNull();
                    } else {
                        jgen.writeStartObject();
                        jgen.writeFieldName("$date");
                        String isoDate = ISODateTimeFormat.dateTime().print(new DateTime(value));
                        jgen.writeString(isoDate);
                        jgen.writeEndObject();
                    }
                } catch (Exception ex) {
                    Logger.getLogger(JsonUtil.class.getName()).log(Level.SEVERE, "Couldn't format timestamp " + value + ", writing 'null'", ex);
                    jgen.writeNull();
                }
            }
        });

        testModule.addDeserializer(Date.class, new StdDeserializer<Date>(Date.class) {
            private static final long serialVersionUID = 1L;

            @Override
            public Date deserialize(JsonParser jp, DeserializationContext dc) throws IOException, JsonProcessingException {
                JsonNode tree = jp.readValueAsTree();
                SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ");
                try {
                    return ISODateTimeFormat.dateTime().parseDateTime(tree.get("$date").textValue()).toDate();
                } catch (Throwable t) {
                    throw new IOException(t.getMessage(), t);
                }
            }

        });

        mapper.registerModule(testModule);
    }

    return mapper;
}

现在,我可以运行以下测试代码:
BObjectMapper mapper = getMapper(JsonUtil.JsonIntent.NONE);
Date d1 = new Date();
String v = mapper.writeValueAsString(d1);
System.out.println("Joda Mapping: " + v);
Date d2 = mapper.readValue(v, Date.class);
System.out.println("Decoded Joda: " + d2);

mapper = getMapper(JsonUtil.JsonIntent.MONGODB);
v = mapper.writeValueAsString(d1);
System.out.println("Mongo Mapping: " + v);
d2 = mapper.readValue(v, Date.class);
System.out.println("Decoded Mongo: " + d2);

输出如下:
Joda Mapping: "2016-06-13T14:58:11.937+0000"
Decoded Joda: Mon Jun 13 10:58:11 EDT 2016
Mongo Mapping: {"$date":"2016-06-13T10:58:11.937-04:00"}
Decoded Mongo: Mon Jun 13 10:58:11 EDT 2016

请注意,将发送到MONGODB的JSON定义了一个包含名为“$date”的字段的值。这告诉MongoDB这是一个日期对象。当我查看Mongo时,我看到以下内容:
"importDate" : ISODate("2016-05-18T18:55:07Z")

现在,我可以将该字段作为日期而不是字符串进行访问。
要将编码的JSON字符串添加到Mongo中,我的代码如下:
MongoDatabase db = getDatabase();
Document d = Document.parse(json);
db.getCollection(bucket).insertOne(d);

在这种情况下,“json”是编码的JSON字符串。因为它来自JSON字符串,它无法知道类型,除非它推断出这一点,这就是为什么我们需要“$date”部分的原因。 “bucket”只是一个字符串,表示要使用哪个表格。
顺便说一下,我发现如果我从Mongo中提取一个BSON对象并通过调用doc.toJson()(其中doc的类型为org.bison.Document,由查询返回)将其转换为JSON字符串,则日期对象将以长整型值而不是格式化的文本字符串存储。我没有检查过是否可以在以这种方式格式化后将数据推入mongo,但是,您可以修改上面显示的反序列化器来支持此操作,如下所示:
    testModule.addDeserializer(Date.class, new StdDeserializer<Date>(Date.class) {
    private static final long serialVersionUID = 1L;

    @Override
    public Date deserialize(JsonParser jp, DeserializationContext dc) throws IOException, JsonProcessingException {
        JsonNode tree = jp.readValueAsTree();
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ");
        try {
            // Mongo will return something that looks more like:
            // {$date:<long integer for milliseconds>}
            // so handle that as well.
            JsonNode dateNode = tree.get("$date");
            if (dateNode != null) {
                String textValue = dateNode.textValue();
                if (!Util.IsNullOrEmpty(textValue)) {
                    return ISODateTimeFormat.dateTime().parseDateTime(textValue).toDate();
                }
                return Util.MillisToDate(dateNode.asLong());
            }
            return null;
        } catch (Throwable t) {
            Util.LogIt("Exception: " + t.getMessage());
            throw new IOException(t.getMessage(), t);
        }
    }

});

您可以按照以下方式将毫秒转换为日期或日期时间:
    /**
 * Convert milliseconds to a date time. If zero or negative, just return
 * null.
 *
 * @param milliseconds
 * @return
 */
public static Date MillisToDate(final long milliseconds) {
    if (milliseconds < 1) {
        return null;
    }
    Calendar calendar = Calendar.getInstance();
    calendar.setTimeInMillis(milliseconds);
    return calendar.getTime();
}

public static DateTime MillisToDateTime(final long milliseconds) {
    if (milliseconds < 1) {
        return null;
    }
    return new DateTime(milliseconds);
}

这个解决方案对我很有效。对于使用Groovy进行编码的人,请确保使用'$date'而不是"$date",因为后者会尝试将日期解析为某个参数,从而导致失败。 - Sanjay Bharwani

2

如果您收到以下消息:

com.fasterxml.jackson.core.JsonGenerationException: Can not write a field name, expecting a value,请注意使用接受的答案中的writeRawValue。这将正确结束字段,否则下一个要序列化的字段可能会引发此错误。


谢谢!我曾经为什么那个被采纳的答案在我的端上不能用而苦恼不已 :) - user3275313

0

您可以通过读写BSON而不是JSON来解决此问题。这里是一个测试类:

package com.nagra.jongo.mapper;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import de.undercouch.bson4jackson.BsonFactory;
import de.undercouch.bson4jackson.deserializers.BsonDateDeserializer;
import de.undercouch.bson4jackson.serializers.BsonDateSerializer;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;

import java.io.IOException;
import java.util.Date;

/**
 * Uses Bson4Jackson 2.9.0
 *
 * <!-- https://mvnrepository.com/artifact/de.undercouch/bson4jackson -->
 * <dependency>
 *     <groupId>de.undercouch</groupId>
 *     <artifactId>bson4jackson</artifactId>
 *     <version>2.9.2</version>
 * </dependency>
 */
public class ObjectMapperTest {

private ObjectMapper mapper = new ObjectMapper(new BsonFactory());

private static class WrappedDate {
    private Date Date = new Date(0);

    public WrappedDate() {
    }

    public Date getDate() {
        return Date;
    }

    public void setDate(Date Date) {
        this.Date = Date;
    }
}

@Before
public void setUp() {
    SimpleModule module = new SimpleModule();
    module.addSerializer(Date.class, new BsonDateSerializer());
    module.addDeserializer(Date.class, new BsonDateDeserializer());
    mapper.registerModule(module);
}

@Test
public void testDate() throws IOException {
    WrappedDate date = new WrappedDate();
    byte[] b = mapper.writeValueAsBytes(date);
    WrappedDate i = mapper.readValue(b, WrappedDate.class);
    Assert.assertEquals(date.getDate(), i.getDate());
    System.out.println(i.getDate());
}}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接