1

I am implementing an application that gets the data from certin endponts in json formats and tries to deserialize tem to Java Objects but I have problems with the parsing of the date in the JSON.This is how the Date looks like in the JSON: "/Date(1633122000000+0300)/" and I cannot find information in Google how to successfully parse this format.

{
  "Date": "/Date(1633122000000+0300)/",
  "Filled": 0,
  "Needed": 0,
  "Paid": 0
}

This is the pojo I use to deserialize the data to using Jackson ObjectMapper:

import java.util.Date;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class TimeByDateSheet {

    @JsonProperty("Date")
    @JsonFormat(timezone = "GMT+03:00")
    @JsonDeserialize(using = DateDeserializer.class, as=Date.class)
    private Date date;
    
    @JsonProperty("Filled")
    private Long filled;
 
    @JsonProperty("Needed")
    private Long needed;
    
   @JsonProperty("Paid")
   private Integer paid;

}

And here is my DateDeserializer:

@SuppressWarnings("serial")
public class DateDeserializer extends JsonDeserializer<Date> {

@Override
public Date deserialize(JsonParser jsonParser, DeserializationContext context) 
throws IOException, JsonProcessingException {
    SimpleDateFormat simpleDateFormat = 
         new SimpleDateFormat("yyyy-MM-dd HH:mm:ss zzz", Locale.getDefault());
    String dateStr = jsonParser.getText();
    Date date;
    try{
        date = simpleDateFormat.parse(dateStr);
    }catch(ParseException e){
        throw new RuntimeException(e);
    }
    return date;
}

}

But it does not work correctly. I get the following exception:

Connected to the target VM, address: '127.0.0.1:52760', transport: 'socket' SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder". SLF4J: Defaulting to no-operation (NOP) logger implementation SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details. Exception in thread "main" com.fasterxml.jackson.databind.JsonMappingException: java.text.ParseException: Unparseable date: "/Date(1633035600000+0300)/" (through reference chain: java.util.ArrayList[0]->com.dataart.forecasts.pojo.timebydate.TimeByDateSheet["Date"]) at com.fasterxml.jackson.databind.JsonMappingException.wrapWithPath(JsonMappingException.java:392) at com.fasterxml.jackson.databind.JsonMappingException.wrapWithPath(JsonMappingException.java:351) at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.wrapAndThrow(BeanDeserializerBase.java:1821) at com.fasterxml.jackson.databind.deser.BeanDeserializer.vanillaDeserialize(BeanDeserializer.java:315) at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:176) at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer._deserializeFromArray(CollectionDeserializer.java:355) at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:244) at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:28) at com.fasterxml.jackson.databind.deser.DefaultDeserializationContext.readRootValue(DefaultDeserializationContext.java:322) at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4675) at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3630) at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3613) at com.dataart.forecasts.DataProcessor.deserializeTimeByDateSheetsList(DataProcessor.java:198) at com.dataart.forecasts.ForecastReportApplication.main(ForecastReportApplication.java:50) Caused by: java.lang.RuntimeException: java.text.ParseException: Unparseable date: "/Date(1633035600000+0300)/" at com.dataart.forecasts.DateDeserializer.deserialize(DateDeserializer.java:28) at com.dataart.forecasts.DateDeserializer.deserialize(DateDeserializer.java:16) at com.fasterxml.jackson.databind.deser.impl.MethodProperty.deserializeAndSet(MethodProperty.java:129) at com.fasterxml.jackson.databind.deser.BeanDeserializer.vanillaDeserialize(BeanDeserializer.java:313) ... 10 more Caused by: java.text.ParseException: Unparseable date: "/Date(1633035600000+0300)/" at java.base/java.text.DateFormat.parse(DateFormat.java:395) at com.dataart.forecasts.DateDeserializer.deserialize(DateDeserializer.java:26) ... 13 more

Could someone help me, please. I searched a lot in internet but could not find a solution. Thank you in advance! :)

2

4 Answers 4

1

It looks like there is a problem generating the JSON. I really don't think you want to have the dates formatted like that. Right now, you have some odd text surrounding a unix timestamp in milliseconds followed by a zone offset. You are also using the old and rather frowned-upon Date and SimpleDateFormat classes rather than the newer java.time API. However, it is possible to deserialize your date format. Here is one way:

public class DateDeserializer extends JsonDeserializer<Date> {
    @Override
    public Date deserialize(JsonParser jsonParser, DeserializationContext context) 
            throws IOException, JsonProcessingException {
        Pattern pattern = Pattern.compile("/Date\\((\\d+)([+-]\\d+)\\)/");
        Matcher matcher = pattern.matcher(jsonParser.getText());
        if (matcher.find()) {
            String timestamp = matcher.group(1);
            String offset = matcher.group(2);
            Instant instant = Instant.ofEpochMilli(Long.parseLong(timestamp));
            ZonedDateTime zdt = instant.atZone(ZoneId.of(offset));
            instant = zdt.toInstant();
            return Date.from(instant);
        } else {
          throw new RuntimeException("Invalid format: " + jsonParser.getText());
        }
    }
}
Sign up to request clarification or add additional context in comments.

7 Comments

The odd format is regularly seen with JSON (both with and without the offset). I don’t know where it comes from.
@OleV.V. Hmm. At my company we use JSON and our dates always come out like "2021-11-08T21:36:15.234Z".
@OleV.V. I think the key is that we use our own subclass of ObjectMapper and in the constructor we set super.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); and super.registerModule(new JavaTimeModule());
@DavidConrad Your solution worked very well, just refactored the pattern a bit in order to match some slightly different dates in other jsons.
@DavidConrad Sounds like what I am doing too. :-)
|
1

java.time

For this answer I am assuming:

  1. The time in your JSON may come with or without the UTC offset.
  2. You can go all-in on java.time, the modern Java date and time API, and declare your variable to be of type Instant or OffsetDateTime, for example (not Date).

For JSON that comes with an offset such as +0300 declare your variable an OffsetDateTime. Then use the following deserializer.

public class OdtDeserializer extends JsonDeserializer<OffsetDateTime> {

    private static final DateTimeFormatter JSON_DATE_FORMATTER = new DateTimeFormatterBuilder()
            .appendLiteral("/Date(")
            .appendValue(ChronoField.INSTANT_SECONDS)
            .appendValue(ChronoField.MILLI_OF_SECOND, 3)
            .appendOffset("+HHMM", "Z")
            .appendLiteral(")/")
            .toFormatter();

    @Override
    public OffsetDateTime deserialize(JsonParser jsonParser, DeserializationContext context)
            throws IOException {
        String dateStr = jsonParser.getText();
        return OffsetDateTime.parse(dateStr, JSON_DATE_FORMATTER);
    }

}

For JSON that comes without offset like /Date(1636510000000)/ declare your variable Instant. Use a similar deserializer. Leave out the offset from the formatter. Parse into an Instant — the syntax is a bit different.

public class InstantDeserializerWIthoutOffset extends JsonDeserializer<Instant> {

    private static final DateTimeFormatter JSON_DATE_FORMATTER = new DateTimeFormatterBuilder()
            .appendLiteral("/Date(")
            .appendValue(ChronoField.INSTANT_SECONDS)
            .appendValue(ChronoField.MILLI_OF_SECOND, 3)
            .appendLiteral(")/")
            .toFormatter();

    @Override
    public Instant deserialize(JsonParser jsonParser, DeserializationContext context)
            throws IOException {
        String dateStr = jsonParser.getText();
        return JSON_DATE_FORMATTER.parse(dateStr, Instant::from);
    }

}

For JSON that may come with or without the offset still use Instant and just modify the formatter of the latter deserializer to include an optional offset:

    private static final DateTimeFormatter JSON_DATE_FORMATTER = new DateTimeFormatterBuilder()
            .appendLiteral("/Date(")
            .appendValue(ChronoField.INSTANT_SECONDS)
            .appendValue(ChronoField.MILLI_OF_SECOND, 3)
            .optionalStart()
            .appendOffset("+HHMM", "Z")
            .optionalEnd()
            .appendLiteral(")/")
            .toFormatter();

If you cannot modify your POJO class and need to stay with Date, modify my Instant deserializer into a Date deserializer by changing the declaration and returning a Date like this:

        String dateStr = jsonParser.getText();
        Instant inst = JSON_DATE_FORMATTER.parse(dateStr, Instant::from);
        return Date.from(inst);

Comments

1

Final solution catching both: /Date(1633035600000+0300)/ and /Date(-62135596800000)/ (the latter was also present at one place in oneof the JSONs). Thank you @DavidConrad

@Override
    public Date deserialize(JsonParser jsonParser, DeserializationContext context) throws IOException, JsonProcessingException {
        String dateString = jsonParser.getText();
        Pattern pattern = Pattern.compile("/Date\\((-)?(\\d+)([+-]\\d+)?\\)/");
        Matcher matcher = pattern.matcher(dateString);
        if (!matcher.find()) {
            throw new RuntimeException("Invalid format: " + dateString);
        }
        String timestamp = matcher.group(2);
        String offset = matcher.group(3);
        Instant instant = Instant.ofEpochMilli(Long.parseLong(timestamp));
        if (nonNull(offset)) {
            ZonedDateTime zdt = instant.atZone(ZoneId.of(offset));
            instant = zdt.toInstant();
        }
        return Date.from(instant);
    }

Comments

0

@DavidConrad Thank you, I will try your solution. By the way, for now I made a workaround that works for me for now:

@Override
    public Date deserialize(JsonParser jsonParser, DeserializationContext context) throws IOException, JsonProcessingException {
        SimpleDateFormat dateFormattter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String dateStr = jsonParser.getText();
        String timeZone = dateStr.substring(dateStr.indexOf("+") + 1, dateStr.indexOf(")"));
        String timeZoneShift = String.format("%s:%s",
                timeZone.substring(0, timeZone.length()/2),
                timeZone.substring(timeZone.length()/2));
        dateFormattter.setTimeZone(TimeZone.getTimeZone(String.format("GMT+%s", timeZoneShift)));
        Long millis = 0L;
        if (dateStr.contains("+") && !dateStr.contains("-")) {
            millis = Long.parseLong(dateStr.substring(dateStr.indexOf("(") + 1, dateStr.indexOf("+")));
        } else if (dateStr.contains("+") && !dateStr.contains("-")) {
            millis = Long.parseLong(dateStr.substring(dateStr.indexOf("(") + 1, dateStr.indexOf(")")));
        }
        Date date = new Date(millis);
        String stringDate= dateFormattter.format(date);
        try {
            date = dateFormattter.parse(stringDate);
        } catch (ParseException e) {
            throw new RuntimeException(e);
        }
        return date;
    }

4 Comments

Thanks for sharing your temporary solution. It’s overly complicated. Also the delimiting of the offset (for example +0300) doesn’t seem to take into account that it may begin with a minus rather than a plus. And after what David Conrad wisely said, we’d be more interested in seeing your solution using java.time, the modern Java date and time API.
just refactored the pattern a bit in order to match some slightly different dates in other jsons How are those other JSONs different? It’s hard to tell from your code — are they coming without the UTC offset? As I mentioned I have seen that before.
here is one particular date that was in that format: /Date(-62135596800000)/ so i had to modify the regex to catch it too: /Date((-)?(\d+)([+-]?\d+)?)
Ah, year 1. 0001-01-01T00:00:00Z. Silly to represent a dummy value that way, but I guess you will have to live with it? I haven’t tested the code in my answer with negative milliseconds since the epoch, results may be inaccurate.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.