概要
Scala の Json ライブラリ で json4s を使っているが、とある JValue を、ある case class に
extract しようとした際、予想外に extract できないケースがあった。
こんなケース
JsonExtractTest.scala
import org.json4s.DefaultFormats
import org.json4s.JsonAST.JValue
object JsonExtractTest {
implicit val formats = DefaultFormats
def extract(json: JValue) = {
json.extract[Test]
}
case class Test(item: Option[List[Int]])
}
REPL で試してみる
試しに REPL にペーストして実行してみると
scala> :paste
// Entering paste mode (ctrl-D to finish)
import org.json4s.DefaultFormats
import org.json4s.JsonAST.JValue
object JsonExtractTest {
implicit val formats = DefaultFormats
def extract(json: JValue) = {
json.extract[Test]
}
case class Test(item: Option[List[Int]])
}
// Exiting paste mode, now interpreting.
defined object JsonExtractTest
scala> import org.json4s.native.JsonMethods._
import org.json4s.native.JsonMethods._
scala> JsonExtractTest.extract(parse("""{"item":[4,5,6]}"""))
JsonExtractTest.Test = Test(Some(List(4, 5, 6)))
予想通り。
Specs2 で確認してみる
よし、うまくいくこと確認できたぞー!と実装してSpecs2も実装
JsonExtractTestSpec.scala
import org.specs2.mutable.Specification
import org.json4s.native.JsonMethods._
class JsonExtractTestSpec extends Specification {
"extract method" should {
"return item" in {
val jsonString = """{"item":[4,5,6]}"""
val result = JsonExtractText.extract(parse(jsonString))
result.item must beSome(List(4, 5, 6))
}
}
}
テスト流してみる。
> test
[info] JsonExtractTestSpec
[info]
[info] extract method should
[info] ! return item
[error] MappingException: : Can't find constructor for ExtensionRequest (ScalaSigReader.scala:27)
[error] org.json4s.reflect.ScalaSigReader$.readConstructor(ScalaSigReader.scala:27)
[error] org.json4s.reflect.Reflector$ClassDescriptorBuilder.ctorParamType(Reflector.scala:108)
[error] org.json4s.reflect.Reflector$ClassDescriptorBuilder$$anonfun$6.apply(Reflector.scala:98)
[error] org.json4s.reflect.Reflector$ClassDescriptorBuilder$$anonfun$6.apply(Reflector.scala:95)
...(snip)...
ほわーーーーーーーーーい!ジャパニーズピーポー!!
なんで REPL だとうまくいったのにテストコケるんだよー!!
調査
ぐぐったらソッコー出てきた。。
https://github.com/json4s/json4s/issues/125
リフレクションの関係でクラス内で定義された case class には extract できないよってことらしい。。
修正、そして結果
object ないで定義した case class Test を外に出す。
そしてテストをもっかい実行してみる。
> test
[info] JsonExtractTestSpec
[info]
[info] extract method should
[info] + return item
できた。。
まとめ
この挙動は知らなかったので勢いで投稿してしまった。
item の型を Option[List[String]] にすると case class をクラス内に定義しても extract できるという謎がありますが、いったんここまで。。