如何在Scala中将JSON对象(列表)解析为简单的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 ofscala.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响应创建一个自定义类?或者我可以直接遍历这个JSON文档(我只想从该数组中的一个对象中提取一个值(并提取该值吗?

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))))

Scatie: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))))

Scatie:https://scastie.scala-lang.org/bSSZdLPyTJWcup2KIb4zAw

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

使用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聊天中寻求帮助。

您可以使用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

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

val jsPath = JsPath \ "id"

然后应用它:

jsPath(Json.parse(jsonString))

代码在Scastie运行。

最新更新