Moshi: Custom Type Adapters sometimes throws "No @ToJson adapter for class annotated []"

Created on 27 Feb 2018  路  5Comments  路  Source: square/moshi

This

data class EventJson(
  val date: String
)

data class Event(
  val date: Date
)

class EventJsonAdapter {
  @FromJson
  fun eventFromJson(eventJson: EventJson): Event {
    return Event(
      date =  SimpleDateFormat("yyyy-MM-dd", Locale.US).parse(eventJson.date)
    )
  }
}

private fun test() {
  val moshi = Moshi.Builder()
    .add(EventJsonAdapter())
    .add(KotlinJsonAdapterFactory())
    .build()

  val jsonAdapter = moshi.adapter(Event::class.java)
  val json = """
                {
                  "date": "2018-02-27"
                }
             """
  Assert.assertEquals(
    Event(date = SimpleDateFormat("yyyy-MM-dd", Locale.US).parse("2018-02-27")),
    jsonAdapter.fromJson(json)
  )
}

will produce

java.lang.IllegalArgumentException: No @ToJson adapter for class Event annotated []

    at com.squareup.moshi.AdapterMethodsFactory.create(AdapterMethodsFactory.java:52)
    at com.squareup.moshi.Moshi.adapter(Moshi.java:100)
    at com.squareup.moshi.Moshi.adapter(Moshi.java:62)
    at AlphaTestKtKt.test(AlphaTestKt.kt:33)
    at AlphaTestKtKt.access$test(AlphaTestKt.kt:1)
    at AlphaTestKt.testAlpha(AlphaTestKt.kt:49)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at junit.framework.TestCase.runTest(TestCase.java:176)
    at junit.framework.TestCase.runBare(TestCase.java:141)
    at junit.framework.TestResult$1.protect(TestResult.java:122)
    at junit.framework.TestResult.runProtected(TestResult.java:142)
    at junit.framework.TestResult.run(TestResult.java:125)
    at junit.framework.TestCase.run(TestCase.java:129)
    at junit.framework.TestSuite.runTest(TestSuite.java:252)
    at junit.framework.TestSuite.run(TestSuite.java:247)
    at org.junit.internal.runners.JUnit38ClassRunner.run(JUnit38ClassRunner.java:86)
    at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
    at org.junit.runner.JUnitCore.run(JUnitCore.java:115)
    at org.junit.vintage.engine.execution.RunnerExecutor.execute(RunnerExecutor.java:42)
    at java.util.stream.ForEachOps$ForEachOp$OfRef.accept(ForEachOps.java:184)
    at java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:193)
    at java.util.Iterator.forEachRemaining(Iterator.java:116)
    at java.util.Spliterators$IteratorSpliterator.forEachRemaining(Spliterators.java:1801)
    at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:481)
    at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:471)
    at java.util.stream.ForEachOps$ForEachOp.evaluateSequential(ForEachOps.java:151)
    at java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential(ForEachOps.java:174)
    at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
    at java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:418)
    at org.junit.vintage.engine.VintageTestEngine.executeAllChildren(VintageTestEngine.java:83)
    at org.junit.vintage.engine.VintageTestEngine.execute(VintageTestEngine.java:74)
    at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:170)
    at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:154)
    at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:90)
    at com.intellij.junit5.JUnit5IdeaTestRunner.startRunnerWithArgs(JUnit5IdeaTestRunner.java:62)
    at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:51)
    at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
    at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at com.intellij.rt.execution.application.AppMainV2.main(AppMainV2.java:131)

Adding "@ToJson" will fix it. Making Event.date a Long instead of Date will fix it also.

moshi: 'com.squareup.moshi:moshi:1.5.0'
moshikotlin: 'com.squareup.moshi:moshi-kotlin:1.5.0'

Most helpful comment

Add @ToJson to fix it? If you need to you can just make it throw UnsupportedOperationException.

Moshi makes you implement both so that you can write test cases, etc.

All 5 comments

Add @ToJson to fix it? If you need to you can just make it throw UnsupportedOperationException.

Moshi makes you implement both so that you can write test cases, etc.

It's inconsistent. It works with Long, but not with Date. Makes you wonder if it is a library bug or your mistake or something else. An explicit crash would be better here if both @ToJson and @FromJson are required.

Also, "annotated []" part is a little bit confusing. I thought it missed a list converter or something.

Long has a built-in converter. Date does not.

The Long/Date thing is very curious. I'll have to experiment to see what causes that.

And agreed, the message could be nicer in the overwhelmingly common case where there are zero annotations.

Like Jake said, you don't need to supply the adapter method if Moshi already has a delegate adapter installed for the type.

For the message, maybe the message could replace "annotated []" with "(with no annotations)" across Moshi?

Was this page helpful?
0 / 5 - 0 ratings