で、lift-jsonでJsonからモデルにマッピングするにはどうするのかなーと思って検索してみると、大抵こちらに行き着きます。
Lift Json の case class への変換がとても便利な件 | scalaとか・・・
これ、すっげー便利なんですけど、実際に使ってみると、そのままじゃうまくいかない場面が出てくるんですよ。
なので、今回は「うまくいかない場面」 = 「痒いところ」に手を伸ばしてみます。
うまくいかない場面1: case objectへのマッピング
例えばこんな風に、Javaでいう列挙型みたいなモデルを作った場合。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
sealed trait Carrier | |
case object PC extends Carrier | |
case object Mobile extends Carrier | |
case object Smartphone extends Carrier |
で、この3つにそれぞれ0,1,2が割り当てられていて、Jsonでは例えば
{ carrier: 0 }といった表現がされているとします。
この場合は、この型に変換する機構をlift-jsonは持っていないので、作ってやる必要があります。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import net.liftweb.json._ | |
lazy val CarrierSerializer = new Serializer[Carrier] { | |
override def deserialize(implicit format: Formats) = { | |
case (TypeInfo(c, _), JInt(i)) if c == classOf[Carrier] && i == 0 => PC | |
case (TypeInfo(c, _), JInt(i)) if c == classOf[Carrier] && i == 1 => Mobile | |
case (TypeInfo(c, _), JInt(i)) if c == classOf[Carrier] && i == 2 => Smartphone | |
} | |
override def serialize(implicit format: Formats) = { | |
case PC => JInt(0) | |
case Mobile => JInt(1) | |
case Smartphone => JInt(2) | |
} | |
} |
このようにSerializerを作っておくと、Formatsに組み込むことができるので、以下のように自作Serializerを組み込んだ新しいFormatsを使ってextractできます。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import net.liftweb.json._ | |
val jsonStr = """[0, 1, 2]""" | |
implicit lazy val myformats = DefaultFormats + CarrierSerializer // 自作Serializerの組み込み | |
val result = parse(jsonStr).extract[List[Carrier]] // List(PC, Mobile, Smartphone) |
うまくいかない場面2: Jsonの構造がちょっと違う
例:楽天ウェブサービスの市場商品検索2
これ、返りのJsonが以下のような構造をしています
{ xxx: yyy ... Items: [ { Item: { } }, { Item: { } } ] }
なぜかItemsとItemだけ頭文字が大文字なのかとか、ItemsのArrayの中身がItemそのものではなく、Itemというフィールドを持ったオブジェクトなのかとかツッコミたい感じです。ツッコミたいだけならいいんですけど、デフォルトのextract一発変換に頼ろうと思うとこういうのが結構ネックになるんですよね。
これ、「うまくいかない場面1」と同じようにSerializerを作っちゃってもいいんですけど、他にもたくさんの要素を含んでいて、ほんの一部だけを修正するためにSerializerを作るのはめんどうです。なので、こういう場合はJsonAST側を都合の良いようにいじっちゃった方が早いです。
変換にはtransformを使います。こいつ、再帰的にJsonASTを操作してくれるので、簡単にASTをいじれます。
詳しくはlift-jsonのソース見ちゃったほうが早いです。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import net.liftweb.json._ | |
val arrange: PartialFunction[JValue, JValue] = { | |
case JObject(List(JField("Item", item))) => item | |
case JField("Items", items) => JField("items", items) | |
} | |
def extract(jsonStr: String) = | |
parse(jsonStr) | |
.transform(arrange) | |
.extract[Result] |
こんな感じ。