【Scala】Scala ~ JSON ~

■ はじめに

https://dk521123.hatenablog.com/entry/2023/03/16/012034

で、ScalaでYAMLファイルを扱ったが、
今回は、JSONについて、調べた。

目次

【0】Scala で JSON
 補足:scala.util.parsing.json.JSON
【1】Gson
 1)インストール
 2)サンプル
【2】Json4s
 1)インストール
 2)サンプル
【3】spray-json
 1)インストール
 2)サンプル
【4】circe
 1)インストール
 2)サンプル
【5】その他のライブラリ
 1)argonaut 
 2)uPickle / uJSON
 3)sjson

【0】ScalaJSON

Scala2.12以降では、外部ライブラリを使う必要がありそう。

補足:scala.util.parsing.json.JSON

* version 1.0.6より前は、 scala.util.parsing.json.JSON があったが
 現在では非推奨になっている(Scala2.12だと、もうない?)

https://www.scala-lang.org/api/2.12.6/scala-parser-combinators/scala/util/parsing/json/JSON$.html

より抜粋
~~~~
Deprecated
(Since version 1.0.6)
 Use The Scala Library Index to find alternatives: https://index.scala-lang.org/
~~~~

【1】Gson

* Google 製の JSONライブラリ

https://dzone.com/articles/lets-unblock-read-json-using-gson-in-scala

1)インストール

libraryDependencies ++= Seq(
  "com.google.code.gson" % "gson" % "2.10.1",

https://mvnrepository.com/artifact/com.google.code.gson/gson

2)サンプル

import com.google.gson.{Gson, JsonObject}

case class Person(name: String, sex: String, age: Int)

object Main extends App {
  val gson = new Gson
  val source = """{ "name": "Mike", "age": 17 }"""
  // Serializing the Json String to the Person Object in Scala.
  val result = gson.fromJson(source, classOf[Person])
  println(result)
  println(result.name)
  println(result.sex) // null
  println(result.age)
}

【2】Json4s

* 個人的には、これがいいと思う。
* 詳細は、以下を参照

https://www.qoosky.io/techs/aba44d3e38
和訳
https://gist.github.com/takungsk/4025974

1)インストール

libraryDependencies ++= Seq(
  "org.json4s" %% "json4s-jackson" % "4.1.0-M2",

2)サンプル

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

object Main extends App {
  val json1 = """{ "numbers" : [1, 2, 3, 4, 5] }"""
  val json1Values = parse(json1)
  val map1Values = json1Values.asInstanceOf[JObject].values
  println(map1Values) //=> Map(numbers -> List(1, 2, 3, 4, 5))
  println(map1Values.get("numbers")) //=> Some(List(1, 2, 3, 4, 5))

  val json2 = """{ "name" : "Mike", "age" : 24 }"""
  val json2Values = parse(json2)
  val map2Values = json2Values.asInstanceOf[JObject].values
  println(map2Values) //=> Map(name -> Mike, price -> 24)
  println(map2Values.get("age")) //=> Some(24)
}

【3】spray-json

* Latest「Nov 11, 2020」で
 2年半近くメンテされていないのがデメリット、、、

https://riptutorial.com/scala/example/7718/json-with-spray-json
https://qiita.com/takutoki/items/03cdbf698c51123ac606

1)インストール

https://github.com/spray/spray-json
build.sbt

// インストール
libraryDependencies ++= Seq(
  "io.spray" %%  "spray-json" % "1.3.6",

2)サンプル

例1:Mapへの変換

import spray.json._
import DefaultJsonProtocol._

object Main extends App {
  val res = """{ "hello": "World!!" }""".parseJson
  val results = res.convertTo[Map[String, String]]
  println(s"results = ${results}")
  println(s"results.get(hello) = ${results.get("hello").get}")
  println("Done...")
}

例2:クラスへの変換

import spray.json._

case class Person(name: String, age: Int)

object DemoJsonProtocol extends DefaultJsonProtocol {
  // jsonFormatX (X: フィールドの数に対応.今回、Personは name/age の2)
  implicit val personFormat = jsonFormat2(Person)
}
import DemoJsonProtocol._

object Main extends App {
  val source = """{ "name": "Mike", "age": 17 }""".parseJson
  val json =  source.convertTo[Person]
  println(s"json = ${json}")
  println(s"name = ${json.name}")
  println(s"age = ${json.age}")
  println("Done...")
}

例2:クラスへの変換(remarksがOptional)

import spray.json._

case class Person(name: String, age: Int, remarks: String)

object JsonPurserUtil {
  def int(value: JsValue, key: String): Option[Int] = value match {
    case JsObject(x) => x.get(key).flatMap(asInt)
    case _ => None
  }
  def asInt(value: JsValue): Option[Int] = value match {
    case JsNumber(x) if Int.MinValue <= x && x <= Int.MaxValue => Some(x.toInt)
    case _ => None
  }

  def str(value: JsValue, key: String): Option[String] = value match {
    case JsObject(x) => x.get(key).flatMap(asStr)
    case _ => None
  }

  def asStr(value: JsValue): Option[String] = value match {
    case JsString(x) => Some(x)
    case _ => None
  }
}

object PersonJsonProtocol extends DefaultJsonProtocol {
  // jsonFormatX (X: フィールドの数に対応.今回、Personは name/age/remarks の3)
  implicit object personJsonFormat extends RootJsonFormat[Person] {
    override def write(obj: Person): JsValue = Map(
      "name" -> obj.name.toJson,
      "age" -> obj.age.toJson,
      "remarks" -> obj.remarks.toJson
    ).toJson

    override def read(json: JsValue): Person = {
      val name = JsonPurserUtil.str(json, "name").getOrElse("")
      val age = JsonPurserUtil.int(json, "age").getOrElse(-1)
      val remarks = JsonPurserUtil.str(json, "remarks").getOrElse("")
      Person(
        name = name,
        age = age,
        remarks = remarks
      )
    }
  }
}

import PersonJsonProtocol._

object Main extends App {
  val source = """{ "name": "Mike", "age": 17 }""".parseJson
  val json =  source.convertTo[Person]
  println(s"json = ${json}")
  println(s"name = ${json.name}")
  println(s"age = ${json.age}")
  println(s"remarks = ${json.remarks}")
  println("Done...")
}

【4】circe

* 詳細は、以下を参照

https://dev.classmethod.jp/articles/circe-first-guide/

1)インストール

build.sbt

// インストール
val circeVersion = "0.14.1"
libraryDependencies ++= Seq(
  "io.circe" %% "circe-core",
  "io.circe" %% "circe-generic",
  "io.circe" %% "circe-parser"
).map(_ % circeVersion)

2)サンプル

val jsonValue =
  """
    |{
    |   "name": "Mike",
    |   "email": "mike@demo.com"
    |}
  """.stripMargin

// results の型:Either[io.circe.Error, Map[String, String]]
val results = decode[Map[String, String]](jsonValue)

【5】その他のライブラリ

* 他にも色々ありそう、、、

1)argonaut

https://zenn.dev/110416/books/a94a86a2c696d1/viewer/810485

2)uPickle / uJSON

https://mungingdata.com/scala/read-write-json/

3)sjson

http://eed3si9n.com/ja/sjson-type-class-based-json/

関連記事

Scala ~ 環境構築編 ~
https://dk521123.hatenablog.com/entry/2023/03/10/193805
Scala ~ 入門編 ~
https://dk521123.hatenablog.com/entry/2023/03/12/184331
ScalaYAML
https://dk521123.hatenablog.com/entry/2023/03/16/012034
ScalaAWS SDK / Secrets Managerサンプル ~
https://dk521123.hatenablog.com/entry/2023/04/03/012600