1. 序章
Circe は、JSONの操作を簡素化するScalaライブラリであり、JSON文字列をScalaオブジェクトに簡単にデコードしたり、ScalaオブジェクトをJSONに変換したりできます。 ライブラリはオブジェクトのエンコーダーとデコーダーを自動的に生成するため、ScalaでJSONを操作するために必要なコード行が削減されます。
2. インストール
ライブラリをインストールするには、build.sbtファイルに数行追加します。
val circeVersion = "0.14.1"
libraryDependencies ++= Seq(
"io.circe" %% "circe-core",
"io.circe" %% "circe-generic",
"io.circe" %% "circe-parser"
).map(_ % circeVersion)
3. JSONの検証
まず、 JSON文字列を検証して、有効なJSONオブジェクトが含まれているかどうかを確認します。 まず、有効なJSONオブジェクトを含む String を定義し、それをparse関数に渡します。
import io.circe._, io.circe.parser._
val jsonString =
"""
|{
| "textField": "textContent",
| "numericField": 123,
| "booleanField": true,
| "nestedObject": {
| "arrayField": [1, 2, 3]
| }
|}
|""".stripMargin
val parseResult: Either[ParsingFailure, Json] = parse(jsonString)
その結果、ParsingErrorまたはJsonオブジェクトのいずれかが取得されます。 次に、 matchステートメントを使用して、戻り値を区別します:
parseResult match {
case Left(parsingError) =>
throw new IllegalArgumentException(s"Invalid JSON object: ${parsingError.message}")
case Right(json) => // here we use the JSON object
}
4. 長い道のりでJSONプロパティにアクセスする
フィールド名を検索し、それらの値を期待されるデータ型に変換することで、解析されたJsonオブジェクトからフィールド値を抽出することができます。 ただし、このような方法は、冗長でエラーが発生しやすいコードを記述する必要があるため、お勧めしません。
前に定義したmatchステートメントを拡張して、neuroFieldの値を抽出してみましょう。
case Right(json) =>
val numbers = json \\ "numericField"
val firstNumber: Option[Option[JsonNumber]] =
numbers.collectFirst{ case field => field.asNumber }
val singleOption: Option[Int] = firstNumber.flatten.flatMap(_.toInt)
\\ 表記は、 findAllByKey 関数のエイリアスであり、Jsonオブジェクトのリストを返すことに注意してください。
その後、 collectFirst 関数を使用して、リストの最初の要素を含む可能性のあるOptionを取得します。 そのため、Optionが別のOptionにネストされてしまいます。 基になる数値を取得するには、 flatMap Option を実行し、JsonNumberをOption[Int]に変換します。
明らかに、この過度に複雑な方法を使用する必要はありません。Circeはすべての複雑さを隠す単純化されたAPIを提供しているからです。
5. ScalaオブジェクトをJSONに変換する
フィールドを手動で抽出して期待される形式に変換するのではなく、Circeコーデックを使用してJSONをScalaオブジェクトとの間で変換できます。
ただし、コーデックでは、解析されたJSON文字列のフィールドに一致するケースクラスを作成する必要があります。
import io.circe._, io.circe.generic.semiauto._
case class Nested(arrayField: List[Int])
case class OurJson(
textField: String,
numericField: Int,
booleanField: Boolean,
nestedObject: Nested
)
ケースクラスが定義されると、クラスからデコーダーを派生させ、それを使用してJSON文字列を解析できます。 最初にNestedクラスのDecoderを定義することに注意してください。
implicit val nestedDecoder: Decoder[Nested] = deriveDecoder[Nested]
implicit val jsonDecoder: Decoder[OurJson] = deriveDecoder[OurJson]
val decoded = decode[OurJson](jsonString)
必要です ネスト JSON文字列では、 入れ子オブジェクト フィールドには別のJSONオブジェクトが含まれています。 したがって、すべてのJSONオブジェクトを個別のScalaオブジェクトに逆シリアル化し、個々のクラスを定義します。
6. JSONをScalaオブジェクトに変換する
同様に、クラスエンコーダーを派生させ、 ScalaオブジェクトをJsonオブジェクトに変換し、後でJSON文字列に変換することができます。 デコードされたケースクラスをJSON文字列に変換して戻しましょう。
implicit val nestedEncoder: Encoder[Nested] = deriveEncoder[Nested]
implicit val jsonEncoder: Encoder[OurJson] = deriveEncoder[OurJson]
decoded match {
case Right(decodedJson) =>
val jsonObject: Json = decodedJson.asJson
val newJsonString = jsonObject.spaces2
}
7. オプションフィールドの操作
一部のJSONフィールドがオプションであるか、値が欠落している場合は、クラス定義を変更し、オプションクラスをフィールドタイプとして使用します。 neuroField 、 booleanField 、およびarrayFieldが欠落しているJSON文字列を見てみましょう。
val jsonStringWithMissingFields =
"""{
| "textField" : "textContent",
| "nestedObject" : {
| "arrayField" : null
| }
|}""".stripMargin
クラス定義を変更せずにデコードしようとすると、「 Left(DecodingFailure(失敗したカーソルの値をデコードしようとしました。List(DownField(numericField))))」エラーが発生します。 したがって、クラス定義を変更する必要があります。
case class Nested(arrayField: Option[List[Int]])
case class OurJson(
textField: String,
numericField: Option[Int],
booleanField: Option[Boolean],
nestedObject: Nested
)
これで、デコーダーは不足している値を問題なく処理できます。
implicit val nestedDecoder: Decoder[Nested] = deriveDecoder[Nested]
implicit val jsonDecoder: Decoder[OurJson] = deriveDecoder[OurJson]
decode[OurJson](jsonStringWithMissingFields)
8. カスタムデコーダーの作成
ListのOptionを使用する代わりに、欠落しているarrayFieldを空のリストに変換したい場合はどうすればよいでしょうか。 このような状況では、カスタムデコーダーを作成する必要があります。 カスタムデコーダーを作成するには、デコーダータイプを実装する必要があります。
この例では、arrayFieldがnullであるかどうかを確認し、 NoneではなくNil(空のリスト)を返します。 フィールドが存在し、配列が含まれている場合は、変更を加えずに返します。
implicit val decodeNested: Decoder[Nested] = (c: HCursor) => for {
arrayField <- c.downField("arrayField").as[Option[List[Int]]]
} yield {
val flattenedArray = arrayField.getOrElse(Nil)
Nested(flattenedArray)
}
9. カスタムデコーダーのテスト
Circeデコーダーとエンコーダーをカスタマイズするときは、コードをテストして、正しく機能することを確認する必要があります。 この例では、ScalaTestライブラリを使用してカスタムコードをテストします。
テストディレクトリで仕様クラスを定義し、入力JSON文字列を準備し、ケースクラスを定義して、デコーダー(カスタムデコーダーと自動生成されたデコーダーの両方)を実装します。
その後、考えられるすべてのケースについて4つのテストを記述します。値を持つ既存の配列、空の配列、nullフィールド、および欠落しているフィールドです。
"A custom decoder" should "decode a JSON with a null array value" in {
decode[OurJson](jsonStringWithNullArray) shouldEqual Right(OurJson("textContent", None, None, Nested(Nil)))
}
it should "decode a JSON with a missing field" in {
decode[OurJson](jsonStringWithMissingArray) shouldEqual Right(OurJson("textContent", None, None, Nested(Nil)))
}
it should "decode a JSON with an existing array value" in {
decode[OurJson](jsonStringWithArray) shouldEqual Right(OurJson("textContent", None, None, Nested(List(1, 2))))
}
it should "decode a JSON with an empty array" in {
decode[OurJson](jsonStringWithEmptyArray) shouldEqual Right(OurJson("textContent", None, None, Nested(Nil)))
}
10. 自動生成されたエンコーダ
デコーダーをカスタマイズする必要がない場合は、自動デコーダー派生を使用して、コードをさらに短縮することができます。
io.circe.generic.auto ._ パッケージをインポートすると、既存のすべてのタイプのデコーダー(およびエンコーダー)が自動的に導出されるため、[を作成せずにJSONパーサーを使用できます。 X197X]デコーダーインスタンス:
import io.circe.generic.auto._, io.circe.parser
parser.decode[OurJson](jsonString)
11. 結論
Circeは、シンプルなAPIで実装の詳細を非表示にすることで、JSONの操作を簡素化するScalaライブラリです。 ただし、カスタムエンコーダーまたはカスタムデコーダーを作成するか、フィールド抽出コードを直接使用することで、いつでもその動作を変更できます。
いつものように、これらの例のコードはGitHubでから入手できます。