9 September 2023

Processing LocalDateTime with Moshi in Java

Moshi is a library for marshalling/unmarshalling JSON. The Moshi API is very similar to the API of its predecessor Gson. Just like Gson, Moshi does not have support for LocalDate(Time) processing. This is the recommended Java Date/Time representation, so Moshi does not work out of the box for Java, and that's a shame. 

Here's how you can solve this, for LocalDateTime. The procedure for LocalDate is similar.

Option 1: write an Adapter from scratch

1. Write the Adapter class

public class LocalDateTimeAdapter {
  // Specify in which format you want your DateTime 
private final DateTimeFormatter formatter = DateTimeFormatter.ISO_LOCAL_DATE_TIME;

@ToJson
public String toText(LocalDateTime dateTime) {
return dateTime.format(formatter);
}

@FromJson
public LocalDateTime fromText(String text) {
return LocalDateTime.parse(text, formatter);

}
}

2. Add the adapter when building Moshi

Moshi moshi = new Moshi.Builder()
.add(new LocalDateTimeAdapter())
.build();
// That's all!
// Now just parse (or serialise) your data as normal
// Here serialising a Ticket object
JsonAdapter<Ticket> jsonAdapter = moshi.adapter( Ticket.class);
System.out.println(jsonAdapter.indent(" ").toJson(ticket));

Option 2: write an Adapter that delegates to Moshi's Date adapter

Here we are using the MoshiRfc3339DateJsonAdapter, that is in the Moshi adapter's library. It can marshall/unmarshall java.util.Date.

1. Add the Moshi adapters library

I'm using gradle and add the dependency to build.gradle.kt, in addition to the moshi library that was already there
dependencies {
implementation("com.squareup.moshi:moshi:1.15.0")
implementation("com.squareup.moshi:moshi-adapters:1.15.0")
//...
}

2. Write the Adapter class

In the adapter I'm converting LocalDateTime to/from Date and then use the Moshi adapter to convert the Date to/from JSON.

//...
// Do NOT import this from com.squareup.moshi.Rfc3339DateJsonAdapter, that one's deprecated
import com.squareup.moshi.adapters.Rfc3339DateJsonAdapter;

public class DelegatingLocalDateTimeAdapter {

// adding .nullSafe() to the adapter allows it to write null dates in json rather than skipping them
private static final JsonAdapter<Date> ADAPTER = new Rfc3339DateJsonAdapter().nullSafe();

@ToJson
public String toJson(LocalDateTime timestamp) {
return ADAPTER.toJson(Date.from(timestamp.atZone(ZoneId.systemDefault()).toInstant()));
}

@FromJson
public LocalDateTime fromJson(String json) throws IOException {
return LocalDateTime.ofInstant( ADAPTER.fromJson(json).toInstant(), ZoneId.systemDefault());
}
}

3. Add the adapter when building Moshi

This step is the same as the last step in Option 1
Moshi moshi = new Moshi.Builder()
.add(new DelegatingLocalDateTimeAdapter())
.build();
// That's all!
// Now just parse (or serialise) your data as normal
// Here serialising a Ticket object
JsonAdapter<Ticket> jsonAdapter = moshi.adapter( Ticket.class);
System.out.println(jsonAdapter.indent(" ").toJson(ticket));

No comments:

Post a Comment