如何反序列化 Java/Scala 中的 Json 字符串并继承 class 并持有不同的属性
How to deserialize Json string in Java/Scala with inherit class holding different attributes
我正在尝试在 scala 中反序列化以下字符串
{ "nodeid": 30, "depth": 6, "split": 65, "split_condition": -9.53674316e-07, "yes": 43, "no": 44, "missing": 43 , "children": [
{ "nodeid": 43, "leaf": 0.000833201397 },
{ "nodeid": 44, "leaf": -0.00145038881 }
]}
它是一个树结构,我们可以认为有非叶子节点和叶子节点,这两者都扩展了一个抽象节点类型。他们拥有不同的属性。 (这个字符串是从 XGBoost 转储的,我想通过反序列化它来创建我自己的数据结构)
我尝试使用 Jackson,但它只需要一种 class 类型。如果我定义一个包含所有这些属性的 class,它肯定可以工作,但之后我无法转储为相同的格式。
那么除了自定义覆盖deserialize
,还有其他更好的选择吗?
如果答不出问题,可以参考我下面的例子
我希望将字符串反序列化为具有以下定义的 Node
class
class Node {
var nodeid: Int = 0
}
class NotLeaf extends Node {
var depth: Int = 0
var split: Int = 0
...
var children: List[Node] = null
}
class Leaf extends Node {
var leaf: Float = 0
}
对于我试过的代码,我可以定义 class
class Node {
var nodeid: Int = 0
var depth: Int = 0
var split: Int = 0
var split_condition: Float = 0
var yes: Int = 0
var no: Int = 0
var missing: Int = 0
var children: List[XGBoostFormat] = null
var leaf: Float = 0
}
并且反序列化结果将具有所有属性,如果我序列化回来,它将是
{
"nodeid" : 30,
"depth" : 6,
"split" : 65,
"split_condition" : -9.536743E-7,
"yes" : 43,
"no" : 44,
"missing" : 43,
"children" : [ {
"nodeid" : "43",
"depth" : 0,
"split" : 0,
"split_condition" : 0.0,
"yes" : 0,
"no" : 0,
"missing" : 0,
"children" : null,
"leaf" : 8.332014E-4
}, {
"nodeid" : "44",
"depth" : 0,
"split" : 0,
"split_condition" : 0.0,
"yes" : 0,
"no" : 0,
"missing" : 0,
"children" : null,
"leaf" : -0.0014503888
} ],
"leaf" : 0.0
}
在 Scala 中,最好使用基于编译时反射而不是 运行时间反射的 Scala JSON 库。它们可以自动生成编解码器——只要您坚持 Scala 使用 sealed traits
(或 sealed abstract class
es)和 case class
es 建模数据的方式。你也不应该使用 var
s 和 null
s.
可以解析您的数据的库有(除其他外):Circe、Play JSON、Jsoniter Scala、Json4s。其中前两个可以这样使用:
import cats.syntax.functor._ // for Decoder widen
import io.circe.Decoder
import io.circe.generic.extras.Configuration
import io.circe.generic.extras.semiauto._
import io.circe.parser
import play.api.libs.json._
sealed trait Tree extends Product with Serializable {
val nodeid: Int
}
object Tree {
// Circe config for derivation
private implicit val circeConfig: Configuration =
Configuration.default.withSnakeCaseMemberNames
// PlayJson config for derivation
private implicit val playJsonConfig: JsonConfiguration =
JsonConfiguration(JsonNaming.SnakeCase)
final case class Node(
nodeid: Int,
depth: Int,
split: Int,
splitCondition: Float,
yes: Int,
no: Int,
children: List[Tree]
) extends Tree
object Node {
implicit def nodeCirceDecoder: Decoder[Node] =
deriveConfiguredDecoder
implicit def nodePlayJsonDecoder: Reads[Node] =
Json.reads[Node]
}
final case class Leaf(
nodeid: Int,
leaf: Float
) extends Tree
object Leaf {
implicit val leafCirceDecoder: Decoder[Leaf] =
deriveConfiguredDecoder
implicit val leafPlayJsonDecoder: Reads[Leaf] =
Json.reads[Leaf]
}
// combined manually because no discriminator field
implicit val treeCirceDecoder: Decoder[Tree] =
Decoder[Leaf].widen[Tree] or Decoder[Node].widen[Tree]
// normally I'd use orElse on Reads but it's not by-name (lazy)
implicit val treePlayJsonDecoder: Reads[Tree] = Reads[Tree] { json =>
implicitly[Reads[Leaf]].widen[Tree].reads(json) orElse implicitly[Reads[Node]].widen[Tree].reads(json)
}
}
val input = """{ "nodeid": 30, "depth": 6, "split": 65, "split_condition": -9.53674316e-07, "yes": 43, "no": 44, "missing": 43 , "children": [
| { "nodeid": 43, "leaf": 0.000833201397 },
| { "nodeid": 44, "leaf": -0.00145038881 }
|]}""".stripMargin
io.circe.parser.decode[Tree](input)
Json.fromJson[Tree](Json.parse(input))
- 使用宏来派生编解码器(你的情况是特定的,因为你有带子类型的递归数据结构并且没有区分字段 - 有区分字段就不需要手动组合编解码器)
- 将它们放入伴随对象中,这样您就不必将它们导入范围
- 运行 解析代码 returns
Either[SomeError, YourType]
(示例使用两个库来演示如何使用它们,但您只需要选择一个)。
由于对象是一次性构建或完全构建的,因此没有理由使其字段可变并用空值初始化。
我还建议使用 Double
s,或者更好的 BigDecimal
s,而不是 Float
s,如果你使用这样精确的值。
如果你想用 Java 方式,使用空值、变量、注释和 运行时间反射 (Jackson),那么最好用 Java 问这个问题标记为 Scala 开发人员积极避免这些。
我正在尝试在 scala 中反序列化以下字符串
{ "nodeid": 30, "depth": 6, "split": 65, "split_condition": -9.53674316e-07, "yes": 43, "no": 44, "missing": 43 , "children": [
{ "nodeid": 43, "leaf": 0.000833201397 },
{ "nodeid": 44, "leaf": -0.00145038881 }
]}
它是一个树结构,我们可以认为有非叶子节点和叶子节点,这两者都扩展了一个抽象节点类型。他们拥有不同的属性。 (这个字符串是从 XGBoost 转储的,我想通过反序列化它来创建我自己的数据结构)
我尝试使用 Jackson,但它只需要一种 class 类型。如果我定义一个包含所有这些属性的 class,它肯定可以工作,但之后我无法转储为相同的格式。
那么除了自定义覆盖deserialize
,还有其他更好的选择吗?
如果答不出问题,可以参考我下面的例子
我希望将字符串反序列化为具有以下定义的 Node
class
class Node {
var nodeid: Int = 0
}
class NotLeaf extends Node {
var depth: Int = 0
var split: Int = 0
...
var children: List[Node] = null
}
class Leaf extends Node {
var leaf: Float = 0
}
对于我试过的代码,我可以定义 class
class Node {
var nodeid: Int = 0
var depth: Int = 0
var split: Int = 0
var split_condition: Float = 0
var yes: Int = 0
var no: Int = 0
var missing: Int = 0
var children: List[XGBoostFormat] = null
var leaf: Float = 0
}
并且反序列化结果将具有所有属性,如果我序列化回来,它将是
{
"nodeid" : 30,
"depth" : 6,
"split" : 65,
"split_condition" : -9.536743E-7,
"yes" : 43,
"no" : 44,
"missing" : 43,
"children" : [ {
"nodeid" : "43",
"depth" : 0,
"split" : 0,
"split_condition" : 0.0,
"yes" : 0,
"no" : 0,
"missing" : 0,
"children" : null,
"leaf" : 8.332014E-4
}, {
"nodeid" : "44",
"depth" : 0,
"split" : 0,
"split_condition" : 0.0,
"yes" : 0,
"no" : 0,
"missing" : 0,
"children" : null,
"leaf" : -0.0014503888
} ],
"leaf" : 0.0
}
在 Scala 中,最好使用基于编译时反射而不是 运行时间反射的 Scala JSON 库。它们可以自动生成编解码器——只要您坚持 Scala 使用 sealed traits
(或 sealed abstract class
es)和 case class
es 建模数据的方式。你也不应该使用 var
s 和 null
s.
可以解析您的数据的库有(除其他外):Circe、Play JSON、Jsoniter Scala、Json4s。其中前两个可以这样使用:
import cats.syntax.functor._ // for Decoder widen
import io.circe.Decoder
import io.circe.generic.extras.Configuration
import io.circe.generic.extras.semiauto._
import io.circe.parser
import play.api.libs.json._
sealed trait Tree extends Product with Serializable {
val nodeid: Int
}
object Tree {
// Circe config for derivation
private implicit val circeConfig: Configuration =
Configuration.default.withSnakeCaseMemberNames
// PlayJson config for derivation
private implicit val playJsonConfig: JsonConfiguration =
JsonConfiguration(JsonNaming.SnakeCase)
final case class Node(
nodeid: Int,
depth: Int,
split: Int,
splitCondition: Float,
yes: Int,
no: Int,
children: List[Tree]
) extends Tree
object Node {
implicit def nodeCirceDecoder: Decoder[Node] =
deriveConfiguredDecoder
implicit def nodePlayJsonDecoder: Reads[Node] =
Json.reads[Node]
}
final case class Leaf(
nodeid: Int,
leaf: Float
) extends Tree
object Leaf {
implicit val leafCirceDecoder: Decoder[Leaf] =
deriveConfiguredDecoder
implicit val leafPlayJsonDecoder: Reads[Leaf] =
Json.reads[Leaf]
}
// combined manually because no discriminator field
implicit val treeCirceDecoder: Decoder[Tree] =
Decoder[Leaf].widen[Tree] or Decoder[Node].widen[Tree]
// normally I'd use orElse on Reads but it's not by-name (lazy)
implicit val treePlayJsonDecoder: Reads[Tree] = Reads[Tree] { json =>
implicitly[Reads[Leaf]].widen[Tree].reads(json) orElse implicitly[Reads[Node]].widen[Tree].reads(json)
}
}
val input = """{ "nodeid": 30, "depth": 6, "split": 65, "split_condition": -9.53674316e-07, "yes": 43, "no": 44, "missing": 43 , "children": [
| { "nodeid": 43, "leaf": 0.000833201397 },
| { "nodeid": 44, "leaf": -0.00145038881 }
|]}""".stripMargin
io.circe.parser.decode[Tree](input)
Json.fromJson[Tree](Json.parse(input))
- 使用宏来派生编解码器(你的情况是特定的,因为你有带子类型的递归数据结构并且没有区分字段 - 有区分字段就不需要手动组合编解码器)
- 将它们放入伴随对象中,这样您就不必将它们导入范围
- 运行 解析代码 returns
Either[SomeError, YourType]
(示例使用两个库来演示如何使用它们,但您只需要选择一个)。
由于对象是一次性构建或完全构建的,因此没有理由使其字段可变并用空值初始化。
我还建议使用 Double
s,或者更好的 BigDecimal
s,而不是 Float
s,如果你使用这样精确的值。
如果你想用 Java 方式,使用空值、变量、注释和 运行时间反射 (Jackson),那么最好用 Java 问这个问题标记为 Scala 开发人员积极避免这些。