1

I am giving up. I have looked through all possible SO pages but I can not get it to work.

I have a class ConfigKeyVal like this:

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@JsonIgnoreProperties(ignoreUnknown = true)
public class ConfigKeyValue {

    private String name;
    private NssConfigDto value;
}

Where Config class looks like this:

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@JsonIgnoreProperties(ignoreUnknown = true)
public class Config {

    private String name;
    private String source;
    private String destination;
    private int cycle;
    private LocalDateTime fixedTime;
    private LocalDateTime submitDate;
}

I am trying to deserialize JSON array of ConfigKeyVal (top one) objects directly into the ArrayList of mine.

public class ConfigKeyValueList extends ArrayList<ConfigKeyValue> {
    public ConfigKeyValueList() {
        super();
    }
}

Like this:

final Data values = result.results().get("attributes"); // this is an array of ConfigKeyValue objects
ObjectMapper mapper = new ObjectMapper();
ConfigKeyValueList configKeyValueList = new ConfigKeyValueList();
try {
    configKeyValueList = mapper.readValue(values.asText(), ConfigKeyValueList.class);
} catch (IOException e) {
    e.printStackTrace();
}

I have tried using mapper.registerModule(new JavaTimeModule()); but that did not help. Do I have to write my own deserializer for this or is there a valid tool and I am doing it all wrong?

The error I am getting is: com.fasterxml.jackson.databind.JsonMappingException: Can not construct instance of java.time.LocalDateTime: no suitable constructor found, can not deserialize from Object value (missing default constructor or creator, or perhaps need to add/enable type information?))

I am using those jackson dependencies in my gradle file:

compile group: 'com.fasterxml.jackson.module', name: 'jackson-module-parameter-names', version: '2.9.6'
compile group: 'com.fasterxml.jackson.datatype', name: 'jackson-datatype-jdk8', version: '2.9.6'
compile group: 'com.fasterxml.jackson.datatype', name: 'jackson-datatype-jsr310', version: '2.9.6'

EDIT: This is how JSON looks like

[
    {
        "name": "kek1",
        "value": {
            "name": "kek1",
            "source": "source",
            "destination": "dest",
            "cycle": 1,
            "fixedTime": {
                "year": 2017,
                "month": "APRIL",
                "dayOfYear": 95,
                "dayOfWeek": "WEDNESDAY",
                "dayOfMonth": 5,
                "monthValue": 4,
                "hour": 4,
                "minute": 20,
                "second": 0,
                "nano": 0,
                "chronology": {
                    "id": "ISO",
                    "calendarType": "iso8601"
                }
            },
            "submitDate": {
                "year": 2017,
                "month": "APRIL",
                "dayOfYear": 95,
                "dayOfWeek": "WEDNESDAY",
                "dayOfMonth": 5,
                "monthValue": 4,
                "hour": 4,
                "minute": 20,
                "second": 0,
                "nano": 0,
                "chronology": {
                    "id": "ISO",
                    "calendarType": "iso8601"
                }
            }
        }
    },
    {
        "name": "kek2",
        "value": {
            "name": "kek2",
            "source": "source",
            "destination": "dest",
            "cycle": 1,
            "fixedTime": {
                "year": 2017,
                "month": "APRIL",
                "dayOfYear": 93,
                "dayOfWeek": "MONDAY",
                "dayOfMonth": 3,
                "monthValue": 4,
                "hour": 5,
                "minute": 10,
                "second": 0,
                "nano": 0,
                "chronology": {
                    "id": "ISO",
                    "calendarType": "iso8601"
                }
            },
            "submitDate": {
                "year": 2017,
                "month": "APRIL",
                "dayOfYear": 93,
                "dayOfWeek": "MONDAY",
                "dayOfMonth": 3,
                "monthValue": 4,
                "hour": 5,
                "minute": 10,
                "second": 0,
                "nano": 0,
                "chronology": {
                    "id": "ISO",
                    "calendarType": "iso8601"
                }
            }
        }
    }
]
Lisek
  • 753
  • 2
  • 11
  • 31
  • Why are the dates serialized like that? Can you change it? – cassiomolin Jul 17 '18 at 11:54
  • @CassioMazzochiMolin I used `LocalDateTime.of()` in the constructor before uploading those objects into MongoDB. Later I am trying to retrieve them and this is what I am getting. Is that not ok? – Lisek Jul 17 '18 at 11:56
  • I would store the dates as string in MongoDB. – cassiomolin Jul 17 '18 at 11:58
  • Do you use Spring Data? Morphia? Just the MongoDB Java driver? – cassiomolin Jul 17 '18 at 12:00
  • @CassioMazzochiMolin Yeah I use Spring. I mean I have a separate service responsible for storing objects (it's database is in Mongo) and I am sending an array of objects to this service. The object I am sending can have whatever fields. Basically, I am flexible to use any format for the dates but I must be able to compare them that is why I used `LocalDateTime`. – Lisek Jul 17 '18 at 12:02

3 Answers3

7

First of all, I don't recommend serializing dates like that. I strongly encourage you to stick to the standards and use ISO 8601, which is endorsed by the RFC 3339 and by the xkcd 1179:

xkcd 1179: ISO 8601


If you have control over the JSON serialization

If you use Spring Data MongoDB, you can use MongoCustomConversions to handle the conversion from Date and LocalDateTime for you:

@Configuration
public class MongoConfiguration {

    @Bean
    public MongoCustomConversions customConversions() {
        List<Converter<?, ?>> converters = new ArrayList<>();
        converters.add(new DateToLocalDateTimeConverter());
        converters.add(new LocalDateTimeToDateConverter());
        return new MongoCustomConversions(converters);
    }

    class DateToLocalDateTimeConverter implements Converter<Date, LocalDateTime> {

        @Override
        public LocalDateTime convert(Date source) {
            return source == null ? null : 
                LocalDateTime.ofInstant(source.toInstant(), ZoneId.systemDefault());
        }
    }

    class LocalDateTimeToDateConverter implements Converter<LocalDateTime, Date> {

        @Override
        public Date convert(LocalDateTime source) {
            return source == null ? null : Date.from(source.toInstant());
        }
    }
}

Then you can use LocalDateTime your in your beans and let Jackson and the JavaTimeModule handle the serialization/deserialization:

ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JavaTimeModule());
mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);

// Serialize
List<ConfigKeyValue> list = null;
String json = mapper.writeValueAsString(list);

// Deserialize
TypeReference<List<ConfigKeyValue>> typeRef = new TypeReference<>() {};
list = mapper.readValue(json, typeRef);

Working with what you have

If you don't have control over the JSON, then you'll need a custom deserializer. The implementation can be like:

public class CustomLocalDateTimeDeserializer extends JsonDeserializer<LocalDateTime> {

    @Override
    public LocalDateTime deserialize(JsonParser jp, DeserializationContext ctxt)
            throws IOException {

        JsonNode tree = jp.getCodec().readTree(jp);
        int year = tree.get("year").asInt();
        int month = tree.get("monthValue").asInt();
        int dayOfMonth = tree.get("dayOfMonth").asInt();
        int hour = tree.get("hour").asInt();
        int minute = tree.get("minute").asInt();
        int second = tree.get("second").asInt();
        int nano = tree.get("nano").asInt();

        return LocalDateTime.of(year, month, dayOfMonth, hour, minute, second, nano);
    }
}

Then annotate your fields to use the deserializer defined above:

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@JsonIgnoreProperties(ignoreUnknown = true)
public class Config {

    // Other fields

    @JsonDeserialize(using = CustomLocalDateTimeDeserializer.class)
    private LocalDateTime fixedTime;

    @JsonDeserialize(using = CustomLocalDateTimeDeserializer.class)
    private LocalDateTime submitDate;
}

And finally parse your JSON document:

ObjectMapper mapper = new ObjectMapper();

TypeReference<List<ConfigKeyValue>> typeRef = new TypeReference<>() {};
List<ConfigKeyValue> list = mapper.readValue(json, typeRef);
Community
  • 1
  • 1
cassiomolin
  • 124,154
  • 35
  • 280
  • 359
  • I do not have direct access to the Mongo database. I can store and get items from it via service. The JSON I posted is what I get if I fetch data from it with simple REST HTTP GET. I must be able to work only on the JSON itself. – Lisek Jul 17 '18 at 12:18
  • @Lisek Then you need a custom deserializer for Jackson. – cassiomolin Jul 17 '18 at 12:23
0

You have to add the module to your ObjectMapper

private ObjectMapper getMapper() {
    ObjectMapper mapper = new ObjectMapper();
    mapper.configure(SerializationFeature.INDENT_OUTPUT,false);
    mapper.setSerializationInclusion(Include.NON_NULL);
    mapper.registerModule(new JavaTimeModule());
    mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
    return mapper;
}

Since the last versions of jackson the module is JavaTimeModule, It used to be JSR310Module

Edit: It should be used for both serialisation/deserialisation, what you mis is maybe mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);

The JSON format should be in ISO:

{
    "name": "kek2",
    "value": {
        "name": "kek2",
        "source": "source",
        "destination": "dest",
        "cycle": 1,
        "fixedTime": "2018-07-17T15:10:55"
...
pdem
  • 3,880
  • 1
  • 24
  • 38
-2

Jackson doesn't have a way to deserialize a LocalDateTime object from any JSON string value.

Change LocalDateTime object to "String".

  • I would not be so sure of this: https://stackoverflow.com/questions/27952472/serialize-deserialize-java-8-java-time-with-jackson-json-mapper – Lisek Jul 17 '18 at 12:04