Подтвердить что ты не робот

Как сохранить поле даты как ISODate() с помощью jackson в MongoDb

Я пытаюсь сохранить объект java с полем java.util.Date в коллекции mongo с помощью quickxml jackson. Проблема заключается в том, что по умолчанию объект objectMapper должен хранить Date как NumberLong.

Например, поле createdTime типа java.util.Date сохраняется следующим образом:

"createdTime" : NumberLong("1427728445176")

Я хочу сохранить его в формате ISODate, который доступен в оболочке mongo.

Теперь я знаю, что есть способ форматировать объект mapper для хранения Date в формате строки String. Но я ТОЛЬКО ищу формат ISODate().

Например, "createdTime" : ISODate("2015-01-20T16:39:42.132Z")

Есть ли способ сделать это? Пожалуйста, посоветуйте гуру. Заранее спасибо за помощь.

4b9b3361

Ответ 1

Что вам нужно, это Jackson Joda Module. Если вы импортируете это в свой путь к классам, вы можете сделать следующее на вашем картографе, чтобы записать его как желаемую временную метку:

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 для всех типов DateTime

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

или указать его в функции с помощью аннотаций

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

Ответ 2

Мне удалось сериализовать строку даты в формате ISODate. Я написал сериализатор даты клиента, как показано ниже.

public void serialize(Date date, JsonGenerator jgen, SerializerProvider provider) throws IOException {
    String dateValue = getISODateString(date);
    String text = "{ \"$date\" : \""+   dateValue   +"\"}";
    jgen.writeRawValue(text);
}

По запросу пользователя @mmx73, я добавляю код для клиента Date DeSeriaizer.

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

Ответ 3

Ни один из этих ответов не выполнил то, что я хотел. У меня были проблемы, потому что когда я сериализовал строку 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

Обратите внимание, что JSON, который будет отправлен в MONGODB, определяет значение, содержащее поле с именем "date date". Это говорит MongoDB, что это объект даты, кажется.

Когда я смотрю на Монго, я вижу следующее:

"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". "Ведро" - это просто строка, указывающая, какую таблицу использовать.

Как замечание, я узнал, что если я вытащу объект BSON из Mongo и преобразую его в строку JSON, вызвав doc.toJson() (где doc имеет тип org.bison.Document как возвращаемый из запроса), объект даты хранится с длинным значением, а не с форматированной текстовой строкой. Я не проверял, могу ли я форматировать данные в 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);
        }
    }

});

Вы можете конвертировать миллисекунды в Date или DateTime следующим образом:

    /**
 * 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);
}

Ответ 4

Если вы получили сообщение типа

com.fasterxml.jackson.core.JsonGenerationException: Can not write a field name, expecting a value.

Убедитесь, что вы используете writeRawValue вместо принятого ответа. Это правильно завершает поле, в противном случае следующее поле для сериализации может выдать эту ошибку.

Ответ 5

Вы можете решить эту проблему, читая/записывая 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());
}}