Kurian Vithayathil
Kurian Vithayathil

Reputation: 837

Configure Jackson to parse multiple date formats

I am working on a project where the date formats returned in JSON payloads aren't consistent (that's another issue all together). The project I'm working on uses Jackson to parse the JSON responses. Right now I've written a few de/serializers to handle it but it's not elegant.

I want to know whether there's a way to configure Jackson with a set of possible date formats to parse for a particular response rather than writing several separate deserializers for each format. Similar to how GSON handles the problem in this question

Upvotes: 22

Views: 32017

Answers (5)

kavai77
kavai77

Reputation: 6616

If you want to deserialize LocalDateTime, here is a similar solution to @rouble's

import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.TreeNode;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
import com.fasterxml.jackson.databind.node.TextNode;

import java.io.IOException;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.Arrays;
import java.util.stream.Collectors;

public class MultiDateDeserializer extends StdDeserializer<LocalDateTime> {
    private static final long serialVersionUID = 1L;

    private static final DateTimeFormatter[] DATE_FORMATTERS = new DateTimeFormatter[]{
            DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"),
            DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm")
    };

    public MultiDateDeserializer() {
        this(null);
    }

    public MultiDateDeserializer(Class<?> vc) {
        super(vc);
    }

    @Override
    public LocalDateTime deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
        TreeNode treeNode = jp.getCodec().readTree(jp);
        TextNode textNode = treeNode instanceof TextNode
                ? (TextNode) treeNode
                : ((TextNode) treeNode.get(""));
        final String date = textNode.textValue();

        for (var formatter : DATE_FORMATTERS) {
            try {
                return LocalDateTime.parse(date, formatter);
            } catch (DateTimeParseException e) {
            }
        }
        throw new JsonParseException(jp, "Unparseable date: \"" + date + "\". Supported formats: " +
                Arrays.stream(DATE_FORMATTERS).map(DateTimeFormatter::toString).collect(Collectors.joining("; ")));
    }
}

Upvotes: 1

rouble
rouble

Reputation: 18171

Here is a Jackson Multi Date Format Serializer.

import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;

import java.io.IOException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.Instant;
import java.util.Arrays;
import java.util.stream.Collectors;

/**
 * https://stackoverflow.com/a/42567051/11152683
 */
public class MultiDateDeserializer extends StdDeserializer<Date> {
    private static final long serialVersionUID = 1L;

    private static final SimpleDateFormat[] DATE_FORMATTERS = new SimpleDateFormat[]{
            new SimpleDateFormat("yyyy-MM-dd HH:mm:ss Z"),
            new SimpleDateFormat("yyyy-MM-dd HH:mm' UTC'")
    };

    public MultiDateDeserializer() {
        this(null);
    }

    public MultiDateDeserializer(Class<?> vc) {
        super(vc);
    }

    @Override
    public Date deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
        JsonNode node = jp.getCodec().readTree(jp);
        final String date = node.textValue();

        for (SimpleDateFormat formatter : DATE_FORMATTERS) {
            try {
                return formatter.parse(date); //.toInstant();
            } catch (ParseException e) {
            }
        }
        throw new JsonParseException(jp, "Unparseable date: \"" + date + "\". Supported formats: " +
                Arrays.stream(DATE_FORMATTERS).map(SimpleDateFormat::toPattern).collect(Collectors.joining("; ")));
    }
}

You can use this simply by annotating a field as follows:

@JsonProperty("date") @JsonDeserialize(using = MultiDateDeserializer.class) final Date date,

Upvotes: 36

SPee
SPee

Reputation: 674

You can use the optional section to define multiple formats:

public class DateStuff {
    @JsonFormat(pattern="[MMM dd, yyyy HH:mm:ss][MMM dd, yyyy][yyyy-MM-dd'T'HH:mm:ssZ]")
    public Date creationTime;
    @JsonFormat(pattern="MMM dd, yyyy[ HH:mm:ss]")
    public Date anotherCreationTime;
}

The first defines two completely different formats. The second where only a small portion is optional.

Upvotes: 7

Code_Worm
Code_Worm

Reputation: 4470

A better solution is to use StdDateFormat instead. It's Jackson's built-in Date formatter and supports most of the variations of Date formats. Use it like below

StdDateFormat isoDate = new StdDateFormat();
ObjectMapper mapper = new ObjectMapper();
mapper.setDateFormat(isoDate);

Upvotes: 2

Keno
Keno

Reputation: 382

In the meanwhile, an annotation became available for a much simpler solution:

public class DateStuff {
    @JsonFormat(shape=JsonFormat.Shape.STRING, pattern="yyyy-MM-dd,HH:00", timezone="CET")
    public Date creationTime;
}

Upvotes: 5

Related Questions