Converting asp.net/MS proprietary json Dateformat to java8 LocalDateTime with jackson when deserializing json to object

I am calling a webservice from a Spring Boot application using jackson-jsr-310 as a maven dependency to be able to use LocalDateTime

:

RestTemplate restTemplate = new RestTemplate();
HttpHeaders httpHeaders = this.createHeaders();
ResponseEntity<String> response;
response  = restTemplate.exchange(uri,HttpMethod.GET,new HttpEntity<Object>(httpHeaders),String.class);
ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.UNWRAP_ROOT_VALUE, true);
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
mapper.registerModule(new JavaTimeModule());
mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
BusinessPartner test = mapper.readValue(response.getBody(), BusinessPartner.class);

      

My problem is on the last line, the code is throwing this error:

java.time.format.DateTimeParseException: Text '/ Date (591321600000) /' could not be parsed at index 0

The resulting JSON response.getBody()

looks like this:

{  
    "d":{  
        ...
        "Address":{...},
        "FirstName":"asd",
        "LastName":"asd",
        "BirthDate":"\/Date(591321600000)\/",
    }
}

      

And in my model class, I have the following member:

@JsonProperty("BirthDate")
private LocalDateTime birthDate;

      

So after doing a little searching here, I found out that this /Date(...)/

one seems to be a proprietary Dateformat that Jackson cannot deserialize into a default object.

Some questions advise to create a custom one SimpleDateFormat

and apply it to the opbject mapper which I was trying to do, but I think I missed the correct syntax formapper.setDateFormat(new SimpleDateFormat("..."));

I tried using for example mapper.setDateFormat(new SimpleDateFormat("/Date(S)/"));

or at the end even mapper.setDateFormat(new SimpleDateFormat("SSSSSSSSSSSS)"));

but that doesn't seem to work either, so I'm not in the know right now and hope some people here can help me.

change 1:

further exploration seems to be one way is to write a custom one DateDeSerializer

for jackson. So I tried this:

@Component
public class JsonDateTimeDeserializer extends JsonDeserializer<LocalDateTime> {

private DateTimeFormatter formatter;

private JsonDateTimeDeserializer() {
    this(DateTimeFormatter.ISO_LOCAL_DATE_TIME);
}

public JsonDateTimeDeserializer(DateTimeFormatter formatter) {
    this.formatter = formatter;
}

@Override
public LocalDateTime deserialize(JsonParser parser, DeserializationContext context) throws IOException
{
    if (parser.hasTokenId(JsonTokenId.ID_STRING)) {
        String unixEpochString = parser.getText().trim();
        unixEpochString = unixEpochString.replaceAll("[^\\d.]", "");

        long unixTime = Long.valueOf(unixEpochString);
        if (unixEpochString.length() == 0) {
            return null;
        }

        LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(unixTime), ZoneId.systemDefault());
        localDateTime.format(formatter);

        return localDateTime;
    }
    return null;
}

      

}

which actually returns almost what I want, annotating my fields in the model using

@JsonDeserialize(using = JsonDateTimeDeserializer.class)

      

but not exactly: This code returns a value LocalDateTime

: 1988-09-27T01:00

. But in the thirdparty system, the xml value is 1988-09-27T00:00:00

.

As obvious the ZoneId is here:

LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(unixTime), ZoneId.systemDefault());

      

- problem other than wrong date format.

So can someone here please help me on how to switch to always use zeros for time

-part and get the date format correct? It would be great!

+3


source to share


1 answer


I am assuming the number 591321600000

is epoch milli (number of milliseconds from 1970-01-01T00:00:00Z

).

If so, I think I SimpleDateFormat

can't help you (at least I couldn't find a way to parse a date from milli epochs using this class). The template S

(as per the javadoc ) is used to format or parse the milliseconds time field (so its max value is 999) and won't work for your case.

The only way I can get it to work is to create a custom deserializer.

I first created this class:

public class SimpleDateTest {

    @JsonProperty("BirthDate")
    private LocalDateTime birthDate;

    // getter and setter
}

      

Then I created my own deserializer and added it to the custom module:

// I'll explain all the details below
public class CustomDateDeserializer extends JsonDeserializer<LocalDateTime> {

    @Override
    public LocalDateTime deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
        String s = p.getText(); // s is "/Date(591321600000)/"

        // assuming the format is always /Date(number)/
        long millis = Long.parseLong(s.replaceAll("\\/Date\\((\\d+)\\)\\/", "$1"));

        Instant instant = Instant.ofEpochMilli(millis); // 1988-09-27T00:00:00Z

        // instant is in UTC (no timezone assigned to it)
        // to get the local datetime, you must provide a timezone
        // I'm just using system default, but you must use whatever timezone your system uses
        return instant.atZone(ZoneId.systemDefault()).toLocalDateTime();
    }
}

public class CustomDateModule extends SimpleModule {

    public CustomDateModule() {
        addDeserializer(LocalDateTime.class, new CustomDateDeserializer());
    }
}

      

Then I added this module to my cartographer and it worked:

// using reduced JSON with only the relevant field
String json = "{ \"BirthDate\": \"\\/Date(591321600000)\\/\" }";
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JavaTimeModule());
// add my custom module
mapper.registerModule(new CustomDateModule());

SimpleDateTest value = mapper.readValue(json, SimpleDateTest.class);
System.out.println(value.getBirthDate()); // 1988-09-26T21:00

      


Now a few comments about the deserializer method.



I first converted millis 591321600000

to Instant

(class representing instantaneous UTC). 591321600000

in milliseconds is equivalent 1988-09-27T00:00:00Z

.

But it is UTC date / time. To get the local date and time , you have to know which time zone you are in, because each time zone has a different date and time (everyone in the world is at the same instant, but their local date / time may be different , depending on where they are).

In my example, I just used ZoneId.systemDefault()

that gets the default timezone for my system. But if you don't want to depend on the default and want to use a specific timezone then use the method ZoneId.of("timezone name")

(you can get a list of all available timezone names with ZoneId.getAvailableZoneIds()

- this method returns all valid names accepted by the method ZoneId.of()

).

Since my default timezone America/Sao_Paulo

, this code sets birthDate

to 1988-09-26T21:00

.

If you don't want to convert to a specific time zone, you can use ZoneOffset.UTC

. So in the deserializer method, the last line would be:

   return instant.atZone(ZoneOffset.UTC).toLocalDateTime();

      

Now the local date will be 1988-09-27T00:00

- since we are using UTC offset, there is no time zone change and the local date / time does not change.


PS: if you need to convert birthDate

back to MS custom format, you can write your own serializer and add to custom module. To convert LocalDateTime

to this format, you can do:

LocalDateTime birthDate = value.getBirthDate();
// you must know in what zone you are to convert it to epoch milli (using default as an example)
Instant instant = birthDate.atZone(ZoneId.systemDefault()).toInstant();
String msFormat = "/Date(" + instant.toEpochMilli() + ")/";
System.out.println(msFormat); // /Date(591321600000)/

      

Note that in order to convert a LocalDateTime

to, Instant

you need to know what time zone you are in. In this case, I recommend using the same timezone for serialization and deserialization (in your case, you can use ZoneOffset.UTC

instead ZoneId.systemDefault()

.

+4


source







All Articles