JAX-RS (Jersey) で Moshi (moshi-kotlin
) 使ってて、雰囲気でレスポンスをreturnさせたら以下のように怒られた:
java.lang.IllegalArgumentException: Expected a Class, ParameterizedType, or GenericArrayType, but <null> is of type null at com.squareup.moshi.Types.getRawType(Types.java:167) at com.squareup.moshi.ClassJsonAdapter$1.createFieldBindings(ClassJsonAdapter.java:83) at com.squareup.moshi.ClassJsonAdapter$1.create(ClassJsonAdapter.java:75) at com.squareup.moshi.Moshi.adapter(Moshi.java:100) at com.squareup.moshi.Moshi.adapter(Moshi.java:58) at com.squareup.moshi.CollectionJsonAdapter.newArrayListAdapter(CollectionJsonAdapter.java:52) at com.squareup.moshi.CollectionJsonAdapter$1.create(CollectionJsonAdapter.java:36) at com.squareup.moshi.Moshi.adapter(Moshi.java:100) at com.squareup.moshi.ClassJsonAdapter$1.createFieldBindings(ClassJsonAdapter.java:91) at com.squareup.moshi.ClassJsonAdapter$1.create(ClassJsonAdapter.java:75) at com.squareup.moshi.Moshi.adapter(Moshi.java:100) at com.squareup.moshi.Moshi.adapter(Moshi.java:58) at com.jakewharton.moshi.rs.MoshiMessageBodyWriter.writeTo(MoshiMessageBodyWriter.java:57) at org.glassfish.jersey.message.internal.WriterInterceptorExecutor$TerminalWriterInterceptor.invokeWriteTo(WriterInterceptorExecutor.java:266)
そんなnull
で埋めてる値なんて無いしなぁとか思いながら昨日の深夜3時頃は 諦めてラーメン食べ行った のだけど、さっき気分でMoshiの実装読んでたら納得。勘が悪かった。
要約すると:
javax.ws.rs.core.Response
を使ってたList<E>
のようなParameterized Typeなオブジェクトを直接entityとして食わせるとダメという話。
具体的にはこういうコードのことだ:
// lateinit var ebean: EbeanServer @GET @Path("/list") @Produces(MediaType.APPLICATION_JSON) fun listItems(): Response { val items = QMyItm(ebean) // ebean-querybean .deletedAt.isNull() .findList() if(anErrorOccured()) { return Response.serverError().build() } return Response.ok(items).build() // `IllegalArgumentException` } // fun anErrorOccured(): Boolean = false
これはJavaの仕様であるType Erasureによるもの。勘の良い人であればResponse
のentity
が Any!
(Object
) になっている時点で気付けたかも。
Type Erasureについては既に語られ尽くしているため詳細な解説を避けるが、今回のケースの場合、要は
Response
へentityとしてmyObj: List<MyItm>
が与えられるAny!
(Object
) なので、Moshiは型情報を取ろうとする。List.class
であることがわかるList
の場合はParameterized Typeのindex: 0
を元に展開する必要があるindex: 0
を取ろうとしたが、これは既にコンパイル時点で消去されている (Type Erasure)IllegalArgumentException
を投げるということ。
手っ取り早く解決する方法は、
List<E>
のようにする (= Response
使わない)MyResponse#getItems
で取れるような形でwrapする (= Parameterized Typeでないオブジェクトにする)など。
恐らくResponse
を使っている理由はstatus codeなんかを適宜変えたいからだと思うのだけど、ExceptionMapper
を実装してあげて自前のThrowableを投げればレスポンスを変えられるので、前者を取ることをお勧めする。
たとえば以下のような形になる:
// lateinit var ebean: EbeanServer @GET @Path("/list") @Produces(MediaType.APPLICATION_JSON) fun listItems(): List<MyItm>? { val items = QMyItm(ebean) // ebean-querybean .deletedAt.isNull() .findList() if(anErrorOccured()) { throw MySomethingWrongException() } return items } // fun anErrorOccured(): Boolean = false // class MySomethingWrongException : RuntimeException()
// もしResponse<T>
のような仕様だったら、こういった事は起こらなかったのかも