How to send a formatted Zoneddatetime in JSON?

Asked

Viewed 115 times

1

I need to send the following:

"tempo": {
    "inicio": "2019-06-24T20:00-03:00[America/Recife]",
    "fim": "2019-06-25T00:00-03:00[America/Recife]"
}

I created the two dates in my pojo:

@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
public class TempoPojo implements Serializable {
    private ZonedDateTime start;
    private ZonedDateTime stop;

}

And I’m setting the dates that way:

Calendar calendar = Calendar.getInstance();

TempoPojo tempoPojo = new TimeSlotPojo();

final ZoneId zoneId = ZoneId.systemDefault();

calendar.setTime(model.getTempo());
calendar.set(Calendar.HOUR, 10);
calendar.set(Calendar.MINUTE, 30);
calendar.set(Calendar.SECOND, 30);
tempoPojo.setStart(ZonedDateTime.ofInstant(calendar.toInstant(), zoneId));

But my JSON is getting like this:

"tempo":{"inicio":{"date_time":{"date":{"year":2019,"month":6,"day":24},"time":{"hour":20,"minute":0,"second":0,"nano":0}},"offset":{"total_seconds":-10800},"zone":{"id":"America/Recife"}},"fim":{"date_time":{"date":{"year":2019,"month":6,"day":25},"time":{"hour":0,"minute":0,"second":0,"nano":0}},"offset":{"total_seconds":-10800},"zone":{"id":"America/Recife"}}}}

There’s a way to turn all that into a date like this: "2019-06-25T00:00-03:00[America/Recife]"?

2 answers

1

First, remember to include the JSR-310 module from Jackson, so that he can deal with the classes of java.time.

Then set the following in ObjectMapper:

ObjectMapper om = new ObjectMapper();
// adicionar o módulo java.time
JavaTimeModule module = new JavaTimeModule();
om.registerModule(module);
// não converter o timezone
om.configure(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE, false);

The option ADJUST_DATES_TO_CONTEXT_TIME_ZONE should be disabled so that there are no Timezone conversions, and it uses whatever is on ZonedDateTime.

In the case of Spring, you can also do this via application properties.

And now you have 2 options:


Option 1, use the Serializer default

Just add these options to ObjectMapper:

om.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
om.configure(SerializationFeature.WRITE_DATES_WITH_ZONE_ID, true);

And put in your fields:

@JsonProperty("inicio")
private ZonedDateTime start;

@JsonProperty("fim")
private ZonedDateTime stop;

With this, JSON will stay like this:

{
  "inicio" : "2019-06-24T20:00:00-03:00[America/Recife]",
  "fim" : "2019-06-25T00:00:00-03:00[America/Recife]"
}

Notice that in the hours also the second are placed. But you only wanted "hours:minutes", without the seconds, so in this case you will have to create a Serializer customized.


Option 2, to use Serializer customized

In that case you don’t need to change the options WRITE_DATES_AS_TIMESTAMPS and WRITE_DATES_WITH_ZONE_ID. Just create a Serializer customized:

public class CustomZonedDateTimeSerializer extends JsonSerializer<ZonedDateTime> {
    private static DateTimeFormatter FMT = DateTimeFormatter.ofPattern("uuuu-MM-dd'T'HH:mmXXX'['VV']'");

    @Override
    public void serialize(ZonedDateTime value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException {
        jgen.writeString(FMT.format(value));
    }
}

And in the fields, configure them to use this class:

@JsonProperty("inicio")
@JsonSerialize(using = CustomZonedDateTimeSerializer.class)
private ZonedDateTime start;

@JsonProperty("fim")
@JsonSerialize(using = CustomZonedDateTimeSerializer.class)
private ZonedDateTime stop;

With this, JSON will show the hours and minutes, but not the seconds:

{
  "inicio" : "2019-06-24T20:00-03:00[America/Recife]",
  "fim" : "2019-06-25T00:00-03:00[America/Recife]"
}

Just check if this is really what you need, because when omitting the seconds, whoever is going to receive this JSON will assume that they are zero (usually the API’s, when seeing that the seconds are not present, set the value to zero - the java.time does that, for example). The same goes for fractions of a second.


Taking advantage, you don’t need to create a Calendar and then create the ZonedDateTime.

Like you did calendar.setTime(model.getTempo()), then I understand that model.getTempo() returns a java.util.Date. So just convert it to Instant and then use atZone to set the ZoneId.

Then to change the time, use the method with passing a java.time.LocalTime. One detail is that in your code you change the time, minute and second, but the fractions of a second are kept, so you have to remember to keep them when creating the LocalTime. Would look like this:

ZoneId zoneId = ZoneId.systemDefault();
// converte o Date para ZonedDateTime, usando o ZoneId indicado
ZonedDateTime zdt = model.getTempo().toInstant().atZone(zoneId);
// muda o horário (preservando as frações de segundo)
tempoPojo.setStart(zdt.with(LocalTime.of(10, 30, 30, zdt.getNano())));

// ou, você pode mudar cada campo individualmente
tempoPojo.setStart(zdt.withHour(10).withMinute(30).withSecond(30));

But as said above, if you choose the second option (which does not show the seconds in JSON), then it is useless to set the value of the seconds, because these will be lost when generating JSON (the same goes for the fractions of a second).

  • My Json remains in the same format, even after the changes... Did not miss my pojo somehow?

  • @Anandalima Which of the above options have you tried, the 1 or the 2? Note that in each one you have to put Annotations in POJO fields also (@JsonProperty and/or @JsonSerialize). It was the only change I made

  • The first option... At first, I only put a Jsonproperty as you suggested, but I believe that only modifies the name of the field in JSON, right? @Jsonserialize would be the second option I haven’t tried yet... OR am I forgetting something in the first one? My pojo looks like this: @Jsonproperty("end") private Zoneddatetime stop;

  • @Anandalima Good, you have to check if the ObjectMapper has been configured correctly with all options, if the java.time module has been added, etc.. 'Cause I tested it here and it worked, maybe it’s one of those missing settings

  • The Objectmapper setting I left inside the method that arrow my date (the first part of the explanation + om.configure of the first option). The annotations I put on Pojo were just jsonProperty and I just put the dependency on my pom... Those were all the changes I made... I just think that the settings of the Objectmapper can not stay within the method, apart from that, everything is the same as what you indicated.

  • Here is everything (variable names and demonstration methods only) https://www.codepile.net/pile/DBvD7dLX

  • @Anandalima Follow the example I made, only with the changes I mentioned works: https://repl.it/@hkotsubo/Zoneddatetimejsontest#Main.java

Show 2 more comments

0

I assume you’re using a project with dependencies managed by Maven.

Include dependency Jackson-datatype-jsr310:

  <dependency>
    <groupId>com.fasterxml.jackson.datatype</groupId>
    <artifactId>jackson-datatype-jsr310</artifactId>
    <version>2.9.9</version>
  </dependency>

In the serializable object, include the desired date format:

@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
class TempoPojo implements Serializable {

    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mmxxx'['VV']'")
    private ZonedDateTime start;
    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mmxxx'['VV']'")
    private ZonedDateTime stop;

}

Register the Javatimemodule in your Objectmapper:

public class Main {

    public static void main(String[] args) throws IOException {

        TempoPojo tempoPojo = new TempoPojo();
        final ZoneId zoneId = ZoneId.systemDefault();

        Calendar calendar = Calendar.getInstance();
        calendar.setTime(new Date());
        calendar.set(Calendar.HOUR, 10);
        calendar.set(Calendar.MINUTE, 30);
        calendar.set(Calendar.SECOND, 30);
        tempoPojo.setStart(ZonedDateTime.ofInstant(calendar.toInstant(), zoneId));      
        tempoPojo.setStop(ZonedDateTime.ofInstant(calendar.toInstant(), zoneId));       
        
        ObjectMapper mapper = new ObjectMapper();
        mapper.registerModule(new JavaTimeModule());
        System.out.println(mapper.writeValueAsString(tempoPojo));
    }
}

Browser other questions tagged

You are not signed in. Login or sign up in order to post.