Skip to content

Instantly share code, notes, and snippets.

@takungsk
Last active January 12, 2018 09:32
Show Gist options
  • Star 25 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save takungsk/4025974 to your computer and use it in GitHub Desktop.
Save takungsk/4025974 to your computer and use it in GitHub Desktop.
JSON4Sの日本語Readme 私訳

JSON4S Build Status

今現在 java json ライブラリを含めないで 少なくとも 6つの scala向け json ライブラリがあります。

すべてのそれらのライブラリは非常に似たASTを持っています。このプロジェクトでは他のscala json ライブラリで使用されるただひとつのASTを提供することを目指しています。

今現在 lift-json 由来の AST を使用するアプローチを取っており、 native パッケージは 事実 lift-json ですが、しかし lift プロジェクトの外部プロジェクトです。

Lift JSON

このプロジェクトは lift-json に lift フレームワークより課されてたリリーススケジュールから開放することを試みます。

Lift フレームワークは 多くの依存関係を含んでおり、それらは 新しい scala のバージョンがリリースされた時に たいてい、多くの他 scala プロジェクトへの妨げるになる。

このライブラリの native パッケージは 実際 文字通り 違うパッケージ名の lift-json である。これは このライブラリを使う場合 インポートステートメントが変わることを意味します。

import org.json4s._
import org.json4s.native.JsonMethods._

あとは すべて lift-json と全く同じように動作する。

Jackson

native パーサーに加えて ASTをパースするのに jackson を使った実装もあります。

jackson モジュールは ほとんどの jackson-module-scala 機能を含んでおり、 lift-json AST を共に使用することができる。

native パーサーに代わって jackson を使うには

import org.json4s._
import org.json4s.jackson.JsonMethods._

jackson 統合のデフォルトの動作では ストリームが完了するとそれがクローズされる動作となることをご留意ください。
もし変更したい場合には:

import com.fasterxml.jackson.databind.SerializationFeature
org.json4s.jackson.JsonMethods.mapper.configure(SerializationFeature.CLOSE_CLOSEABLE, false)

ガイド

JSON の 分析と フォーマット をするためのユーティリティ

lift-json ライブラリの中心となる概念は、 構文木 として JSONドキュメント構造モデル化した Json AST である。

sealed abstract class JValue
case object JNothing extends JValue // 'zero' for JValue
case object JNull extends JValue
case class JString(s: String) extends JValue
case class JDouble(num: Double) extends JValue
case class JDecimal(num: BigDecimal) extends JValue
case class JInt(num: BigInt) extends JValue
case class JBool(value: Boolean) extends JValue
case class JObject(obj: List[JField]) extends JValue
case class JArray(arr: List[JValue]) extends JValue

type JField = (String, JValue)

すべての機能は上記 AST の用語で実現されている。 機能は AST それ自体の変更、あるいは AST と違うフォーマット間の変換に使用される。一般的な変換は以下の図に要約される。 Json AST

機能概要:

  • 高速な JSON 解析
  • LINQ スタイルのクエリ
  • 解析された JSON から値を抽出するのに case class を使うことができる。
  • 差分 と 結合
  • 有効なJSONを生成するDSL
  • XPath ライクな式 と JSON を操作する HOFs
  • コンパクトに整えられた出力形式
  • XML 変換
  • シリアル化
  • 低レベル pull parser API

インストール

以下の方法で json4s の依存関係を追加できます。
注: {latestVersion} を 適切な Json4s のバージョンに置き換えてください。

入手可能なバージョンは こちらから見つけることが出来ます。: http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22org.json4s%22

SBT ユーザ

native を使用する場合 以下の 依存関係をプロジェクトの description に追加してください:

val json4sNative = "org.json4s" %% "json4s-native" % "{latestVersion}"

Jackson を使用する場合 以下の 依存関係をプロジェクトの description に追加してください:

val json4sJackson = "org.json4s" %% "json4s-jackson" % "{latestVersion}"

Maven ユーザ

native を使用する場合、以下の依存関係を pom に追加してください:

<dependency>
  <groupId>org.json4s</groupId>
  <artifactId>json4s-native_${scala.version}</artifactId>
  <version>{latestVersion}</version>
</dependency>

jackson を使用する場合、以下の依存関係を pom に追加してください:

<dependency>
  <groupId>org.json4s</groupId>
  <artifactId>json4s-jackson_${scala.version}</artifactId>
  <version>{latestVersion}</version>
</dependency>

追加

Enum, Joda-Time,… のサポート

Scalaz による アプリカティブスタイル解析

Box のサポート

旧バージョンからの移行

3.3.0 ->

json4s 3.3 は基本的には 3.2.x とソースコード互換であるべきです。 json4s 3.3.0 以降 我々はここに記載のあるバイナリ互換性の問題を繰り返さない為に バイナリ互換性の確認の為に MiMa を使い始めました。

JValueの .toOption の動作が変更されました。 現在は JNothingJNull の両方が None を返します。 以前の動作の為には JNothing だけが None になる toSome を使えます。

すべてのマージされた プルリクエスト: https://github.com/json4s/json4s/pulls?q=is%3Apr+is%3Aclosed+milestone%3A3.3

3.0.0 ->

JField は もはや JValue ではありません。これは さらに 型安全であることを意味し、インスタンスのために直接的にJArrayに加えられたJFieldの 不正なJSONの生成はもはや可能ではない。 この変更のもっとも大きな影響は 2種類の map、transform、find、filter です:

def map(f: JValue => JValue): JValue
def mapField(f: JField => JField): JValue
def transform(f: PartialFunction[JValue, JValue]): JValue
def transformField(f: PartialFunction[JField, JField]): JValue
def find(p: JValue => Boolean): Option[JValue]
def findField(p: JField => Boolean): Option[JField]
//...

JSON の中の field を渡すには *Filed 関数を使います。そして JSONの中の値を渡すときには 名前に'filed'が無い関数を使用します。

2.2 ->

Path 式はバージョン2.2以降 変わりました。以前のバージョンでは式の使用で 不必要な複雑な JField が返っていた。もし以下のようなパターンマッチングの path式を使用している場合:

val JField("bar", JInt(x)) = json \ "foo" \ "bar"

次のように変更が必要です:

val JInt(x) = json \ "foo" \ "bar"

JSON の解析

有効な json は内部ASTフォーマットに解析されることができる。 native を使用した場合:

scala> import org.json4s._
scala> import org.json4s.native.JsonMethods._

scala> parse(""" { "numbers" : [1, 2, 3, 4] } """)
res0: org.json4s.JsonAST.JValue =
      JObject(List((numbers,JArray(List(JInt(1), JInt(2), JInt(3), JInt(4))))))

scala> parse("""{"name":"Toy","price":35.35}""", useBigDecimalForDouble = true)
res1: org.json4s.package.JValue =
      JObject(List((name,JString(Toy)), (price,JDecimal(35.35))))

jackson を使用した場合:

scala> import org.json4s._
scala> import org.json4s.jackson.JsonMethods._

scala> parse(""" { "numbers" : [1, 2, 3, 4] } """)
res0: org.json4s.JsonAST.JValue =
      JObject(List((numbers,JArray(List(JInt(1), JInt(2), JInt(3), JInt(4))))))

scala> parse("""{"name":"Toy","price":35.35}""", useBigDecimalForDouble = true)
res1: org.json4s.package.JValue =
      JObject(List((name,JString(Toy)), (price,JDecimal(35.35))))

JSON の生成

json を DoubleMode あるいは BigDecimalMode の2つのモードで 生成することが出来る。前者は すべての 10進数の値をJDouble にマップする、後者は JDecimalにマップする。

double モードDSLの使用:

import org.json4s.JsonDSL._
// or
import org.json4s.JsonDSL.WithDouble._

big decimalモードDSLの使用:

import org.json4s.JsonDSL.WithBigDecimal._

DSL ルール

  • プリミティブ型は JSON プリミティブにマップする。
  • いずれの seq は JSON配列を生成する。
scala> val json = List(1, 2, 3)

scala> compact(render(json))

res0: String = [1,2,3]
  • Tuple2[String, A] は フィールドを生成する。
scala> val json = ("name" -> "joe")

scala> compact(render(json))
res1: String = {"name":"joe"}
  • ~ 演算子は フィールドに組み合わされたオブジェクトを生成する。
scala> val json = ("name" -> "joe") ~ ("age" -> 35)

scala> compact(render(json))
res2: String = {"name":"joe","age":35}
  • いずれの値も Option化できる。 フィールドと値はそれが値を持たない時には取り除かれる。
scala> val json = ("name" -> "joe") ~ ("age" -> Some(35))

scala> compact(render(json))
res3: String = {"name":"joe","age":35}

scala> val json = ("name" -> "joe") ~ ("age" -> (None: Option[Int]))

scala> compact(render(json))
res4: String = {"name":"joe"}
  • dsl拡張 dslを拡張 あなた自身のクラスと共に スコープ内に引数の適合する暗黙的変換が必要:
type DslConversion = T => JValue

object JsonExample extends App {
  import org.json4s._
  import org.json4s.JsonDSL._
  import org.json4s.jackson.JsonMethods._

  case class Winner(id: Long, numbers: List[Int])
  case class Lotto(id: Long, winningNumbers: List[Int], winners: List[Winner], drawDate: Option[java.util.Date])

  val winners = List(Winner(23, List(2, 45, 34, 23, 3, 5)), Winner(54, List(52, 3, 12, 11, 18, 22)))
  val lotto = Lotto(5, List(2, 45, 34, 23, 7, 5, 3), winners, None)

  val json =
    ("lotto" ->
      ("lotto-id" -> lotto.id) ~
      ("winning-numbers" -> lotto.winningNumbers) ~
      ("draw-date" -> lotto.drawDate.map(_.toString)) ~
      ("winners" ->
        lotto.winners.map { w =>
          (("winner-id" -> w.id) ~
           ("numbers" -> w.numbers))}))

  println(compact(render(json)))
}
scala> JsonExample
{"lotto":{"lotto-id":5,"winning-numbers":[2,45,34,23,7,5,3],"winners":
[{"winner-id":23,"numbers":[2,45,34,23,3,5]},{"winner-id":54,"numbers":[52,3,12,11,18,22]}]}}

サンプルは 以下の書式を揃えられたJSONを生成します。注意 draw-date フィールドはその値がNoneの為 出力されていない:

scala> pretty(render(JsonExample.json))

{
  "lotto":{
    "lotto-id":5,
    "winning-numbers":[2,45,34,23,7,5,3],
    "winners":[{
      "winner-id":23,
      "numbers":[2,45,34,23,3,5]
    },{
      "winner-id":54,
      "numbers":[52,3,12,11,18,22]
    }]
  }
}

結合と差分

Please see more examples in MergeExamples.scala and DiffExamples.scala.

二つのJSONはお互いに結合したり、差分を取ったりすることが可能。 詳細はサンプル MergeExamples.scalaDiffExamples.scalaを参照ください。

scala> import org.json4s._

scala> import org.json4s.jackson.JsonMethods._

scala> val lotto1 = parse("""{
         "lotto":{
           "lotto-id":5,
           "winning-numbers":[2,45,34,23,7,5,3],
           "winners":[{
             "winner-id":23,
             "numbers":[2,45,34,23,3,5]
           }]
         }
       }""")

scala> val lotto2 = parse("""{
         "lotto":{
           "winners":[{
             "winner-id":54,
             "numbers":[52,3,12,11,18,22]
           }]
         }
       }""")

scala> val mergedLotto = lotto1 merge lotto2

scala> pretty(render(mergedLotto))
res0: String =
{
  "lotto":{
    "lotto-id":5,
    "winning-numbers":[2,45,34,23,7,5,3],
    "winners":[{
      "winner-id":23,
      "numbers":[2,45,34,23,3,5]
    },{
      "winner-id":54,
      "numbers":[52,3,12,11,18,22]
    }]
  }
}

scala> val Diff(changed, added, deleted) = mergedLotto diff lotto1
changed: org.json4s.JsonAST.JValue = JNothing
added: org.json4s.JsonAST.JValue = JNothing
deleted: org.json4s.JsonAST.JValue = JObject(List((lotto,JObject(List(JField(winners,
JArray(List(JObject(List((winner-id,JInt(54)), (numbers,JArray(
List(JInt(52), JInt(3), JInt(12), JInt(11), JInt(18), JInt(22))))))))))))))

JSON へのクエリ

"LINQ" style

JSON は for-comprehension を用いて抽出することが出来る。 詳細はサンプルJsonQueryExamples.scalaを参照ください。

scala> import org.json4s._
scala> import org.json4s.native.JsonMethods._

scala> val json = parse("""
         { "name": "joe",
           "children": [
             {
               "name": "Mary",
               "age": 5
             },
             {
               "name": "Mazy",
               "age": 3
             }
           ]
         }
       """)

scala> for {
         JObject(child) <- json
         JField("age", JInt(age))  <- child
       } yield age
res0: List[BigInt] = List(5, 3)

scala> for {
         JObject(child) <- json
         JField("name", JString(name)) <- child
         JField("age", JInt(age)) <- child
         if age > 4
       } yield (name, age)
res1: List[(String, BigInt)] = List((Mary,5))

XPath + HOFs

Json AST XPATHライクな機能をつかって問い合わせされることが出来る。 下記のREPLセッションは '\','\\','find','filter','transform','remove','values'関数の使用方法である。

JSON サンプル:
{
  "person": {
    "name": "Joe",
    "age": 35,
    "spouse": {
      "person": {
        "name": "Marilyn",
        "age": 33
      }
    }
  }
}

DSL構文への変換:

scala> import org.json4s._
scala> import org.json4s.native.JsonMethods._

あるいは

scala> import org.json4s.jackson.JsonMethods._
scala> import org.json4s.JsonDSL._

scala> val json =
  ("person" ->
    ("name" -> "Joe") ~
    ("age" -> 35) ~
    ("spouse" ->
      ("person" ->
        ("name" -> "Marilyn") ~
        ("age" -> 33)
      )
    )
  )

scala> json \\ "spouse"
res0: org.json4s.JsonAST.JValue = JObject(List(
      (person,JObject(List((name,JString(Marilyn)), (age,JInt(33)))))))

scala> compact(render(res0))
res1: String = {"person":{"name":"Marilyn","age":33}}

scala> compact(render(json \\ "name"))
res2: String = {"name":"Joe","name":"Marilyn"}

scala> compact(render((json removeField { _ == JField("name", JString("Marilyn")) }) \\ "name"))
res3: String = "Joe"

scala> compact(render(json \ "person" \ "name"))
res4: String = "Joe"

scala> compact(render(json \ "person" \ "spouse" \ "person" \ "name"))
res5: String = "Marilyn"

scala> json findField {
         case JField("name", _) => true
         case _ => false
       }
res6: Option[org.json4s.JsonAST.JValue] = Some((name,JString(Joe)))

scala> json filterField {
         case JField("name", _) => true
         case _ => false
       }
res7: List[org.json4s.JsonAST.JField] = List(JField(name,JString(Joe)), JField(name,JString(Marilyn)))

scala> json transformField {
         case JField("name", JString(s)) => ("NAME", JString(s.toUpperCase))
       }
res8: org.json4s.JsonAST.JValue = JObject(List((person,JObject(List(
(NAME,JString(JOE)), (age,JInt(35)), (spouse,JObject(List(
(person,JObject(List((NAME,JString(MARILYN)), (age,JInt(33)))))))))))))

scala> json.values
res8: scala.collection.immutable.Map[String,Any] = Map(person -> Map(name -> Joe, age -> 35, spouse -> Map(person -> Map(name -> Marilyn, age -> 33))))

インデックス化された path式も動作し、値は型式(データ型を表す式)を使ってアンボックスできます。

scala> val json = parse("""
         { "name": "joe",
           "children": [
             {
               "name": "Mary",
               "age": 5
             },
             {
               "name": "Mazy",
               "age": 3
             }
           ]
         }
       """)

scala> (json \ "children")(0)
res0: org.json4s.JsonAST.JValue = JObject(List((name,JString(Mary)), (age,JInt(5))))

scala> (json \ "children")(1) \ "name"
res1: org.json4s.JsonAST.JValue = JString(Mazy)

scala> json \\ classOf[JInt]
res2: List[org.json4s.JsonAST.JInt#Values] = List(5, 3)

scala> json \ "children" \\ classOf[JString]
res3: List[org.json4s.JsonAST.JString#Values] = List(Mary, Mazy)

値の抽出

case class は解析されたJSONから値を抽出し使用することが出来る。存在しない値は scala.Option型に抽出され、文字列は自動的に java.util.Dates 型に変換される。 詳細はサンプルExtractionExampleSpec.scalaを参照ください。

scala> import org.json4s._
scala> import org.json4s.jackson.JsonMethods._

scala> implicit val formats = DefaultFormats // Brings in default date formats etc.

scala> case class Child(name: String, age: Int, birthdate: Option[java.util.Date])
scala> case class Address(street: String, city: String)
scala> case class Person(name: String, address: Address, children: List[Child])

scala> val json = parse("""
         { "name": "joe",
           "address": {
             "street": "Bulevard",
             "city": "Helsinki"
           },
           "children": [
             {
               "name": "Mary",
               "age": 5,
               "birthdate": "2004-09-04T18:06:22Z"
             },
             {
               "name": "Mazy",
               "age": 3
             }
           ]
         }
       """)

scala> json.extract[Person]
res0: Person = Person(joe,Address(Bulevard,Helsinki),List(Child(Mary,5,Some(Sat Sep 04 18:06:22 EEST 2004)), Child(Mazy,3,None)))

scala> val addressJson = json  \ "address"  // Extract address object
scala> addressJson.extract[Address]
res1: Address = Address(Bulevard,Helsinki)

scala> (json \ "children").extract[List[Child]]  // Extract list of objects
res2: List[Child] = List(Child(Mary,5,Some(Sat Sep 04 23:36:22 IST 2004)), Child(Mazy,3,None))

デフォルトでは コンストラクタ パラメータ名は json フィールド名に一致する必要があります。しかし 時々 json フィールド名が Scalaの識別子として許されていない文字を含んでいます。 二つの解決方法があります。(サンプルLottoExample.scala for bigger example)を参照ください)

back ticks(`)を使う.

scala> case class Person(`first-name`: String)

AST後処理に transform 関数を使う。

scala> case class Person(firstname: String)
scala> json transformField {
         case ("first-name", x) => ("firstname", x)
       }

もし json フィールド名が スネークケース(例: separated_by_underscores)を使用していて ケースクラスが キャメルケース(例:firstLetterLowercaseAndNextWordsCapitalized)を使っている場合 抽出時にcamelizeKeys メソッドを使って変換することができます。

scala> import org.json4s._
scala> import org.json4s.native.JsonMethods._
scala> implicit val formats = DefaultFormats
scala> val json = parse("""{"first_name":"Mary"}""")
scala> case class Person(firstName: String)

scala> json.camelizeKeys.extract[Person]
res0: Person = Person(Mazy)

キャメルケースのフィールドをスネークケースのキーのjson に変換する方法の詳細はシリアライズの項目を見てください。

抽出関数は case class が補助コンストラクターを持っている場合、もっとも一致するコンストラクターを見つけることを試みます。 例えば JSON {"price":350} を次のcase class に抽出する場合 主コンストラクタの代わりに補助コンストラクタを使用します。

scala> case class Bike(make: String, price: Int) {
         def this(price: Int) = this("Trek", price)
       }
scala> parse(""" {"price":350} """).extract[Bike]
res0: Bike = Bike(Trek,350)

プリミティブ値はJSONプリミティブ あるいは フィールドに抽出されます。

scala> (json \ "name").extract[String]
res0: String = "joe"

scala> ((json \ "children")(0) \ "birthdate").extract[Date]
res1: java.util.Date = Sat Sep 04 21:06:22 EEST 2004

日付フォーマットは 'DefaultFormats'(あるいは 暗黙的トレイト 'Format')によって上書きされ変更されます。

scala> implicit val formats = new DefaultFormats {
         override def dateFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'")
       }

JSON オブジェクトは Map[String, _]に抽出することもできる。おのおののフィールドはMapの(キー、値)のペアとなる。

scala> val json = parse("""
         {
           "name": "joe",
           "addresses": {
             "address1": {
               "street": "Bulevard",
               "city": "Helsinki"
             },
             "address2": {
               "street": "Soho",
               "city": "London"
             }
           }
         }""")

scala> case class PersonWithAddresses(name: String, addresses: Map[String, Address])
scala> json.extract[PersonWithAddresses]
res0: PersonWithAddresses("joe", Map("address1" -> Address("Bulevard", "Helsinki"),
                                     "address2" -> Address("Soho", "London")))

シリアル化

Case class はシリアライズ、デシリアライズされることができる。
詳細はサンプル SerializationExamples.scala を参照ください。

scala> import org.json4s._
scala> import org.json4s.native.Serialization
scala> import org.json4s.native.Serialization.{read, write}

scala> implicit val formats = Serialization.formats(NoTypeHints)

scala> val ser = write(Child("Mary", 5, None))

scala> read[Child](ser)
res1: Child = Child(Mary,5,None)

もし native の代わりに jackson を使っている場合:

scala> import org.json4s._
scala> import org.json4s.jackson.Serialization
scala> import org.json4s.jackson.Serialization.{read, write}

scala> implicit val formats = Serialization.formats(NoTypeHints)

scala> val ser = write(Child("Mary", 5, None))

scala> read[Child](ser)
res1: Child = Child(Mary,5,None)

もし クラスが キャメルケースフィールド(例:firstLetterLowercaseAndNextWordsCapitalized)を使っていて、 スネークケース(例: separated_by_underscores)の json を生成したくないときは snakizeKeys メソッドを使えます。

scala> val ser = write(Person("Mary"))
ser: String = {"firstName":"Mary"}
シリアル化 サポー:

scala> compact(render(parse(ser).snakizeKeys))
res0: String = {"first_name":"Mary"}
  • 任意の 深い case class グラフ
  • BigInt,Symbol を含む すべてのプリミティブ型
  • List, Seq, Array, Set と Map (注意, マップのキーは String であることが必要: Map[String, _])
  • scala.Option
  • java.util.Date
  • 多相型List (下を参照)
  • 再帰データ型
  • クラスフィールドのシリアル化(下を参照)
  • サポートされていない型に対する カスタム シリアライザー 関数(下を参照)

多相型Listのシリアル化

多相型(や異なる型を持つ )Listのシリアル化をする際には Type hint が必要です。 シリアル化されたJSONオブジェクトは 'jsonClass' という名前の追加のフィールドを得ます。

scala> trait Animal
scala> case class Dog(name: String) extends Animal
scala> case class Fish(weight: Double) extends Animal
scala> case class Animals(animals: List[Animal])

scala> implicit val formats = Serialization.formats(ShortTypeHints(List(classOf[Dog], classOf[Fish])))

scala> val ser = write(Animals(Dog("pluto") :: Fish(1.2) :: Nil))
ser: String = {"animals":[{"jsonClass":"Dog","name":"pluto"},{"jsonClass":"Fish","weight":1.2}]}

scala> read[Animals](ser)
res0: Animals = Animals(List(Dog(pluto), Fish(1.2)))

ShortTypeHints は 構成されたオブジェクトのすべてのインスタンスのための短いクラス名を出力する。 FullTypeHints は フルクラス名を出力します。 そのほかの方法は TypeHints トレイトを拡張することで実装可能です。

クラスフィールドのシリアル化

フィールドのシリアル化を有効にするには、FieldSerializerで いくつかの型を追加できる:

implicit val formats = DefaultFormats + FieldSerializer[WildDog]()

WildDog型(と すべてのサブ型)は すべてのフィールド(+ コンストラクタパラメータ)と共にシリアル化される。 FieldSerializerは、フィールドのシリアル化を傍受するために使用できる2つの省略可能なパラメータを受け取ります:

case class FieldSerializer[A: Manifest](
  serializer:   PartialFunction[(String, Any), Option[(String, Any)]] = Map(),
  deserializer: PartialFunction[JField, JField] = Map()
)

それらの 部分関数は フィールドがシリアル化されるか デシリアル化されるその前に呼ばれます。有用な 名前を変える(rename)、フィールドを無視する(ignore) 部分関数が提供されています:

val dogSerializer = FieldSerializer[WildDog](
  renameTo("name", "animalname") orElse ignore("owner"),
  renameFrom("animalname", "name"))

implicit val formats = DefaultFormats + dogSerializer

trait,class で定義された シリアライズ クラス

われわれは trait に定義された case class のサポートを追加しました。しかし それらには カスタム フォーマットが必要である。「なぜ」 と 「どうやって」 を説明します。

Why?

trait の中で定義されたクラスについては そのコンパニオンオブジェクトを取得するのは少し難しいので、デフォルト値を与える必要がある。
われわれは それらをあてはめることはできたが それは コンパイラが そんな case class の コンストラクタに余分なフィールドを生成するという、次の問題を引き起こす。
それらの case class のコンストラクタの はじめのフィールドは $outer と呼ばれ 定義している trait の型である。 ともかく われわれは そのオブジェクトの インスタンスを取得する必要がある、単純な方法では われわれは すべての class をスキャンし、 trait で定義されている クラスを集めるられるが、しかし 一つ以上ある場合 どれをとればいいか?

How?

私は それらの case class 為の コンパニオン マッピングのリストを含むようにフォーマットを拡張するということを選択しました。
つまり あなたのモジュールに属しているフォーマットを持つことで、そこでマッピングを保持することができます。 そうすることで デフォルト値が動作し $outer フィールドに必要な多くのものを提供するようになります。

trait SharedModule {
  case class SharedObj(name: String, visible: Boolean = false)
}

object PingPongGame extends SharedModule
implicit val formats: Formats =
  DefaultFormats.withCompanions(classOf[PingPongGame.SharedObj] -> PingPongGame)

val inst = PingPongGame.SharedObj("jeff", visible = true)
val extr = Extraction.decompose(inst)
extr must_== JObject("name" -> JString("jeff"), "visible" -> JBool(true))
extr.extract[PingPongGame.SharedObj] must_== inst

サポートされていない型のシリアル化

あらゆる型への カスタム シリアライザ と デシリアライザ 関数を差し込むことが可能。 非 case class である Interval クラスがある場合(かくて、 デフォルトではサポートされていない)われわれは 次の提供されたシリアライザによってシリアル化可能。

scala> class Interval(start: Long, end: Long) {
         val startTime = start
         val endTime = end
       }

scala> class IntervalSerializer extends CustomSerializer[Interval](format => (
         {
           case JObject(JField("start", JInt(s)) :: JField("end", JInt(e)) :: Nil) =>
             new Interval(s.longValue, e.longValue)
         },
         {
           case x: Interval =>
             JObject(JField("start", JInt(BigInt(x.startTime))) ::
                     JField("end",   JInt(BigInt(x.endTime))) :: Nil)
         }
       ))

scala> implicit val formats = Serialization.formats(NoTypeHints) + new IntervalSerializer

カスタム シリアライザは 二つの部分関数によって提供される。はじめにそれがJSONからのデータをアンパックできるか値を評価する。 次に 型が適合した場合 望ましいJSONを生成します。

拡張

json4s-ext モジュールは抽出とシリアル化の拡張を含んでいます。以下のタイプがサポートされています。

// Lift's box
implicit val formats = org.json4s.DefaultFormats + new org.json4s.native.ext.JsonBoxSerializer

// Scala enums
implicit val formats = org.json4s.DefaultFormats + new org.json4s.ext.EnumSerializer(MyEnum)
// or
implicit val formats = org.json4s.DefaultFormats + new org.json4s.ext.EnumNameSerializer(MyEnum)

// Joda Time
implicit val formats = org.json4s.DefaultFormats ++ org.json4s.ext.JodaTimeSerializers.all

XML サポート

JSON 構造と XMLノードと相互の変換が可能です。
詳細はサンプル XmlExamples.scala を参照ください。

scala> import org.json4s.Xml.{toJson, toXml}
scala> val xml =
         <users>
           <user>
             <id>1</id>
             <name>Harry</name>
           </user>
           <user>
             <id>2</id>
             <name>David</name>
           </user>
         </users>

scala> val json = toJson(xml)
scala> pretty(render(json))
res3: String =
{
  "users":{
    "user":[{
      "id":"1",
      "name":"Harry"
    },{
      "id":"2",
      "name":"David"
    }]
  }
}

現在、上のサンプルには2つの問題があります。1つ目 id は Int に変換して欲しかったが String に変換された。これはJString(s) から JInt(s.toInt) へのマッピングをつかい容易に修正が可能です。二つ目はもっと巧妙です。変換機能はJSON配列を使うことを決めます、なぜなら XMLの中に一つ以上の userエレメントがあるからです。それゆえ ただ一つのuserエレメントを持つことが起こる構造的に等しいXMLドキュメントはJSON配列なしの JSONドキュメントを生成します。これは めったに望ましい結果とはならない。これら両方の問題は以下の変換関数によって修正可能である。

scala> json transformField {
         case ("id", JString(s)) => ("id", JInt(s.toInt))
         case ("user", x: JObject) => ("user", JArray(x :: Nil))
       }

他方へもサポートされる。 JSONからXMLへの変換:

scala> toXml(json)
res5: scala.xml.NodeSeq = NodeSeq(<users><user><id>1</id><name>Harry</name></user><user><id>2</id><name>David</name></user></users>)

低レベル pull parser API

極限のパフォーマンスが必要される場合のために Pull パーサ APIは提供される。 それは二つの方法で 解析のパフォーマンスを改善します。一つ目は 中間ASTを生成しない。二つ目は 残りのストリームの解析をいつでも停止、スキップできる。注意 この解析スタイルは最適化の場合にのみ推奨される。上で述べられた 関数 API の使用は容易である。

次のサンプルみて どのように JSONからフィールド値を解析するか 検討してください。

scala> val json = """
  {
    ...
    "firstName": "John",
    "lastName": "Smith",
    "address": {
      "streetAddress": "21 2nd Street",
      "city": "New York",
      "state": "NY",
      "postalCode": 10021
    },
    "phoneNumbers": [
      { "type": "home", "number": "212 555-1234" },
      { "type": "fax", "number": "646 555-4567" }
    ],
    ...
  }"""

scala> val parser = (p: Parser) => {
         def parse: BigInt = p.nextToken match {
           case FieldStart("postalCode") => p.nextToken match {
             case IntVal(code) => code
             case _ => p.fail("expected int")
           }
           case End => p.fail("no field named 'postalCode'")
           case _ => parse
         }

         parse
       }

scala> val postalCode = parse(json, parser)
postalCode: BigInt = 10021

Pull パーサは Parser => A 関数です このサンプルの中では 具体的には Parser => BigInt です。 構造化解析は FieldStart("postalCode") トークンを見つけるまで再帰的にトークンを読み込む。 その後 次のトークンは IntValでなければならない、さもなくば 分析は失敗する。それは 解析された Integer を返し、そして すぐに 解析を中止します。

FAQ

Q1: 私はJSON オブジェクトがあり、それを case class に抽出したい。

scala> case class Person(name: String, age: Int)
scala> val json = """{"name":"joe","age":15}"""

しかし、抽出に失敗します。:

scala> parse(json).extract[Person]
org.json4s.MappingException: Parsed JSON values do not match with class constructor

A1:

抽出は REPL の中で定義された classに対しては動作しません。 scalac で定義された case class をコンパイルし、それを REPL から インポートしてください。

謝辞

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment