如何将 JSON 对象(这是一个列表)解析为 Scala 中的简单 Scala class 对象?

How to parse a JSON object (which is a list) into simple Scala class objects in scala?

我花了太多时间尝试完成这项工作,我是 Scala 的新手。

基本上我向 API 发出请求并得到以下响应:

[
  {
    "id": "bde585ea-43ad-4e62-9f20-ea721193e0a5",
    "clientId": "account",
    "realm":"test-realm-uqrw"
    "name": "${client_account}",
    "rootUrl": "${authBaseUrl}",
    "baseUrl": "/realms/test-realm-uqrw/account/",
    "surrogateAuthRequired": false,
    "enabled": true,
    "alwaysDisplayInConsole": false,
    "clientAuthenticatorType": "client-secret",
    "defaultRoles": [
      "manage-account",
      "view-profile"
    ],
    "redirectUris": [
      "/realms/test-realm-uqrw/account/*"
    ],
    "webOrigins": [],
    "protocol": "openid-connect",
    "attributes": {},
    "authenticationFlowBindingOverrides": {},
    "fullScopeAllowed": false,
    "nodeReRegistrationTimeout": 0,
    "defaultClientScopes": [
      "web-origins",
      "role_list",

    ],

    "access": {
      "view": true,
      "configure": true,
      "manage": true
    }
  },
  {..another object of the same type, different values },
  {..another object of the same type, different values }
]

我只需要从任何这些对象中提取 "id" 字段(稍后我会匹配 realm 属性)。有没有一种简单的方法可以将 json 列表转换为 Map[String, Any]List[]?我说 Any 是因为值的类型多种多样 - 布尔值、字符串、映射、列表。

我尝试了几种方法(内部工具)和 Jackson(错误:com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of scala.collection.immutable.List(no Creators, like default construct, exist): abstract types either need to be mapped to concrete types, have custom deserializer, or contain additional type information),我得到的最接近的是一个奇怪的 Tuples 给我错误的结果(因为处理不正确)。

执行此操作的简单方法是什么?还是我注定要为此 API 响应创建自定义 class?或者我可以直接查看这个 JSON 文档(我只想从该数组中的 one 个对象中获取 one 值)并提取值?

本地和现代之一是 Circe,在您的情况下,解决方案可能类似于:

import io.circe._, io.circe.parser._, io.circe.generic.auto._, io.circe.syntax._

case class Response(
    id: String,
    clientId: String,
    realm: String,
    name: String,
    rootUrl: String,
    baseUrl: String,
    surrogateAuthRequired: Boolean,
    enabled: Boolean,
    alwaysDisplayInConsole: Boolean,
    clientAuthenticatorType: String,
    defaultRoles: List[String],
    redirectUris: List[String],
    webOrigins: List[String],
    protocol: String,
    fullScopeAllowed: Boolean,
    nodeReRegistrationTimeout: Int,
    defaultClientScopes: List[String],
    access: Access
)

case class Access(view: Boolean, configure: Boolean, manage: Boolean)

val json =
  s"""
       |[
       |  {
       |    "id": "bde585ea-43ad-4e62-9f20-ea721193e0a5",
       |    "clientId": "account",
       |    "realm":"test-realm-uqrw",
       |    "name": "client_account",
       |    "rootUrl": "authBaseUrl",
       |    "baseUrl": "/realms/test-realm-uqrw/account/",
       |    "surrogateAuthRequired": false,
       |    "enabled": true,
       |    "alwaysDisplayInConsole": false,
       |    "clientAuthenticatorType": "client-secret",
       |    "defaultRoles": [
       |      "manage-account",
       |      "view-profile"
       |    ],
       |    "redirectUris": [
       |      "/realms/test-realm-uqrw/account/*"
       |    ],
       |    "webOrigins": [],
       |    "protocol": "openid-connect",
       |    "fullScopeAllowed": false,
       |    "nodeReRegistrationTimeout": 0,
       |    "defaultClientScopes": [
       |      "web-origins",
       |      "role_list"
       |    ],
       |
       |    "access": {
       |      "view": true,
       |      "configure": true,
       |      "manage": true
       |    }
       |  }
       |]
       |""".stripMargin

println(parse(json).flatMap(_.as[List[Response]]))

将打印输出:

Right(List(Response(bde585ea-43ad-4e62-9f20-ea721193e0a5,account,test-realm-uqrw,client_account,authBaseUrl,/realms/test-realm-uqrw/account/,false,true,false,client-secret,List(manage-account, view-profile),List(/realms/test-realm-uqrw/account/*),List(),openid-connect,false,0,List(web-origins, role_list),Access(true,true,true))))

斯卡蒂:https://scastie.scala-lang.org/5OpAUTjSTEWWTrH4X24vAg

最大的优势 - 与 Jackson 不同,它不是基于运行时反射,而是基于编译时推导。

更新

正如@LuisMiguelMejíaSuárez 在评论部分中正确建议的那样,如果您只想获取 id 字段,则无需完整的模型解析即可完成,例如:

import io.circe._, io.circe.parser._

val json =
  s"""
       |[
       |  {
       |    "id": "bde585ea-43ad-4e62-9f20-ea721193e0a5"
       |  },
       |  {
       |    "id": "bde585ea-43ad-4e62-9f20-ea721193e0a6"
       |  }
       |]
       |""".stripMargin

println(parse(json).map(_.hcursor.values.map(_.map(_.hcursor.downField("id").as[String]))))

打印出来:

Right(Some(Vector(Right(bde585ea-43ad-4e62-9f20-ea721193e0a5), Right(bde585ea-43ad-4e62-9f20-ea721193e0a6))))

斯卡蒂:https://scastie.scala-lang.org/bSSZdLPyTJWcup2KIb4zAw

但要小心 - 手动 JSON 操作,通常用于边缘情况。即使对于简单的情况,我也建议使用模型推导。

你可以用playjson,很简单

import play.api.libs.json._

case class Access(view: Boolean, configure: Boolean, manage: Boolean)
case class Response(
    id: String,
    clientId: String,
    realm: String,
    name: String,
    rootUrl: String,
    baseUrl: String,
    surrogateAuthRequired: Boolean,
    enabled: Boolean,
    alwaysDisplayInConsole: Boolean,
    clientAuthenticatorType: String,
    defaultRoles: List[String],
    redirectUris: List[String],
    webOrigins: List[String],
    protocol: String,
    fullScopeAllowed: Boolean,
    nodeReRegistrationTimeout: Int,
    defaultClientScopes: List[String],
    access: Access
)



val string =
  s"""
       |[
       |  {
       |    "id": "bde585ea-43ad-4e62-9f20-ea721193e0a5",
       |    "clientId": "account",
       |    "realm":"test-realm-uqrw",
       |    "name": "client_account",
       |    "rootUrl": "authBaseUrl",
       |    "baseUrl": "/realms/test-realm-uqrw/account/",
       |    "surrogateAuthRequired": false,
       |    "enabled": true,
       |    "alwaysDisplayInConsole": false,
       |    "clientAuthenticatorType": "client-secret",
       |    "defaultRoles": [
       |      "manage-account",
       |      "view-profile"
       |    ],
       |    "redirectUris": [
       |      "/realms/test-realm-uqrw/account/*"
       |    ],
       |    "webOrigins": [],
       |    "protocol": "openid-connect",
       |    "fullScopeAllowed": false,
       |    "nodeReRegistrationTimeout": 0,
       |    "defaultClientScopes": [
       |      "web-origins",
       |      "role_list"
       |    ],
       |
       |    "access": {
       |      "view": true,
       |      "configure": true,
       |      "manage": true
       |    }
       |  }
       |]
       |""".stripMargin

implicit val ac = Json.format[Access]
implicit val res = Json.format[Response]

println(Json.parse(string).asInstanceOf[JsArray].value.map(_.as[Response])) 

避免异常-

val responseOpt = Json.parse(string) match {
        case JsArray(value: collection.IndexedSeq[JsValue]) => value.map(_.asOpt[Response])
        case _ => Seq.empty
      }

参见:https://scastie.scala-lang.org/RBUHhxxIQAGcKgk9a9iwIA

这是文档:https://www.playframework.com/documentation/2.8.x/ScalaJson

使用jsoniter-scala FTW!

推导得心应手,运行时效率最高。提取 JSON 个值是它最闪耀的地方。

请添加以下依赖项:

libraryDependencies ++= Seq(
  // Use the %%% operator instead of %% for Scala.js  
  "com.github.plokhotnyuk.jsoniter-scala" %% "jsoniter-scala-core"   % "2.6.4",
  // Use the "provided" scope instead when the "compile-internal" scope is not supported  
  "com.github.plokhotnyuk.jsoniter-scala" %% "jsoniter-scala-macros" % "2.6.4" % "compile-internal"
)

然后仅 id 个值无需为其他值定义字段或数据结构。

只需定义一个最简单的数据结构并立即对其进行解析:

import com.github.plokhotnyuk.jsoniter_scala.macros._
import com.github.plokhotnyuk.jsoniter_scala.core._
import java.util.UUID

val json = """[
             |  {
             |    "id": "bde585ea-43ad-4e62-9f20-ea721193e0a5",
             |    "clientId": "account",
             |    "realm":"test-realm-uqrw",
             |    "name": "${client_account}",
             |    "rootUrl": "${authBaseUrl}",
             |    "baseUrl": "/realms/test-realm-uqrw/account/",
             |    "surrogateAuthRequired": false,
             |    "enabled": true,
             |    "alwaysDisplayInConsole": false,
             |    "clientAuthenticatorType": "client-secret",
             |    "defaultRoles": [
             |      "manage-account",
             |      "view-profile"
             |    ],
             |    "redirectUris": [
             |      "/realms/test-realm-uqrw/account/*"
             |    ],
             |    "webOrigins": [],
             |    "protocol": "openid-connect",
             |    "attributes": {},
             |    "authenticationFlowBindingOverrides": {},
             |    "fullScopeAllowed": false,
             |    "nodeReRegistrationTimeout": 0,
             |    "defaultClientScopes": [
             |      "web-origins",
             |      "role_list",
             |
             |    ],
             |
             |    "access": {
             |      "view": true,
             |      "configure": true,
             |      "manage": true
             |    }
             |  }
             |]""".stripMargin.getBytes("UTF-8")

case class Response(id: UUID)

implicit val codec: JsonValueCodec[List[Response]] = JsonCodecMaker.make

val responses = readFromArray(json)

println(responses)
println(responses.map(_.id))

预期输出:

List(Response(bde585ea-43ad-4e62-9f20-ea721193e0a5))
List(bde585ea-43ad-4e62-9f20-ea721193e0a5)

如果需要以不同方式或更有效地处理您的数据,请随时在此处或 gitter chat 中寻求帮助。

另一个使用play-json的选项是定义一个路径:

val jsPath = JsPath \ "id"

然后应用它:

jsPath(Json.parse(jsonString))

代码 运行 在 Scastie