24

with Gson you would do this

Gson gson = new GsonBuilder()
            .setDateFormat("yyyy-MM-dd'T'HH:mm")
            .create();

and pass it to the retrofit builder and Gson would make a Date object for you, Is there some way to get Moshi to do this too in a kotlin class?

tyczj
  • 71,600
  • 54
  • 194
  • 296
  • 3
    Will https://github.com/square/moshi/tree/master/adapters/src/main/java/com/squareup/moshi do what you want? That adapter is published in the moshi-adapters artifact. – Eric Cochran Jun 10 '17 at 16:32

2 Answers2

52

If you’d like to use a standard ISO-8601/RFC 3339 date adapter (you probably do) then you can use the built-in adapter:

Moshi moshi = new Moshi.Builder()
    .add(Date.class, new Rfc3339DateJsonAdapter().nullSafe())
    .build();

JsonAdapter<Date> dateAdapter = moshi.adapter(Date.class);
assertThat(dateAdapter.fromJson("\"1985-04-12T23:20:50.52Z\""))
    .isEqualTo(newDate(1985, 4, 12, 23, 20, 50, 520, 0));

You’ll need this Maven dependency to make that work:

<dependency>
  <groupId>com.squareup.moshi</groupId>
  <artifactId>moshi-adapters</artifactId>
  <version>1.5.0</version>
</dependency>

If you want to use a custom format (you probably don’t), there’s more code. Write a method that accepts a date and formats it to a string, and another method that accepts a string and parses it as a date.

Object customDateAdapter = new Object() {
  final DateFormat dateFormat;
  {
    dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm");
    dateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));
  }

  @ToJson synchronized String dateToJson(Date d) {
    return dateFormat.format(d);
  }

  @FromJson synchronized Date dateToJson(String s) throws ParseException {
    return dateFormat.parse(s);
  }
};

Moshi moshi = new Moshi.Builder()
    .add(customDateAdapter)
    .build();

JsonAdapter<Date> dateAdapter = moshi.adapter(Date.class);
assertThat(dateAdapter.fromJson("\"1985-04-12T23:20\""))
    .isEqualTo(newDate(1985, 4, 12, 23, 20, 0, 0, 0));

You need to remember to use synchronized because SimpleDateFormat is not thread-safe. Also you need to configure a time zone for the SimpleDateFormat.

Jesse Wilson
  • 39,078
  • 8
  • 121
  • 128
  • 5
    Thank you for the answer. :) Here are Gradle dependencies, for convenience: `implementation("com.squareup.moshi:moshi:1.8.0") implementation("com.squareup.moshi:moshi-adapters:1.8.0")` – Filip Savic Jun 28 '19 at 10:33
16

In kotlin you can extend JsonAdapter class and create your own adapter:

class CustomDateAdapter : JsonAdapter<Date>() {
    private val dateFormat = SimpleDateFormat(SERVER_FORMAT, Locale.getDefault())

    @FromJson
    override fun fromJson(reader: JsonReader): Date? {
        return try {
            val dateAsString = reader.nextString()
            synchronized(dateFormat) {
                dateFormat.parse(dateAsString)
            }
        } catch (e: Exception) {
            null
        }
    }

    @ToJson
    override fun toJson(writer: JsonWriter, value: Date?) {
        if (value != null) {
            synchronized(dateFormat) {
                writer.value(value.toString())
            }
        }
    }

    companion object {
        const val SERVER_FORMAT = ("yyyy-MM-dd'T'HH:mm") // define your server format here
    }
}

Then, in your Retrofit initialization you can set the adapter with Moshi.Builder by doing:

 private val moshiBuilder = Moshi.Builder().add(CustomDateAdapter()) // Your custom date adapter here

        val service: ApiService by lazy {
            val loggingInterceptor = HttpLoggingInterceptor()
            loggingInterceptor.level = HttpLoggingInterceptor.Level.BODY

            val httpClient = OkHttpClient.Builder()
                .addInterceptor(loggingInterceptor)
                .build()

            val retrofit = Retrofit.Builder()
                .baseUrl(BuildConfig.API_URL)
                .client(httpClient)
                .addConverterFactory(MoshiConverterFactory.create(moshiBuilder.build())) // And don`t forget to add moshi class when creating MoshiConverterFactory 
                .addCallAdapterFactory(CoroutineCallAdapterFactory())
                .build()

            retrofit.create(ApiService::class.java)
        }
AutonomousApps
  • 4,229
  • 4
  • 32
  • 42
Nicola Gallazzi
  • 7,897
  • 6
  • 45
  • 64
  • Hi, thanks for your reply, it helped me a lot indeed! I wonder about one thing: When exactly does toJson work? `fromJson` works while fetching property and returns a `Date` however I am unable to manipulate this `Date` according to the new format. I have these formats; ``` private const val SERVER_DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ss.SSSSSS'Z'" private const val APP_DATE_FORMAT = "yyyy-MM-dd" ``` And this code block doesn't work ``` @ToJson override fun toJson(writer: JsonWriter, value: Date?) { writer.value(appFormatter.format(value!!)) } ``` – nuhkoca Oct 14 '19 at 10:14
  • toJson is useful to serialize your POJO class to json format. Pretend need to create a json document from your object, in that case you need the serializer to be added to your moshi builder – Nicola Gallazzi Oct 14 '19 at 12:42