多态JSON节点转换为Scala Scalaz Tree的实现方案咨询
适用库选型
- JSON解析继续用你已经熟悉的Circe即可,它原生支持带类型标记的多态类型解码,刚好匹配你给出的
键名即为节点类型的JSON结构 - 不可变树结构可以直接用Scalaz Tree,或者Cats提供的
cats.collections.Tree,两者都自带节点增删改、遍历、转换API,所有修改操作都会返回新的不可变实例,完全符合你的需求,不用自己实现基于可变ListBuffer的Tree结构
具体实现步骤
1. 定义节点类型
先把所有节点对应的case class按多态层级定义好:
// 所有节点的公共父特质 sealed trait ShapeNode case class Container(attributes: List[String], children: List[ShapeNode]) extends ShapeNode case class Circle(fill: String, diameter: Int) extends ShapeNode case class Square(name: String, color: String, length: Int, width: Int) extends ShapeNode
2. 实现Circe多态解码器
不用手动遍历Circe的Json结构逐个校验字段,用半自动解码器就能省掉大部分重复代码:
import io.circe._ import io.circe.generic.semiauto._ // 先定义叶子节点的解码器 implicit val circleDecoder: Decoder[Circle] = deriveDecoder[Circle] implicit val squareDecoder: Decoder[Square] = deriveDecoder[Square] // 用懒加载实现递归解码,处理嵌套的Container implicit lazy val shapeNodeDecoder: Decoder[ShapeNode] = Decoder.instance { cursor => cursor.keys.flatMap(_.headOption) match { case Some("Container") => cursor.get[Container]("Container") case Some("circle") => cursor.get[Circle]("circle") case Some("square") => cursor.get[Square]("square") case other => Left(DecodingFailure(s"未知节点类型:$other", cursor.history)) } } // Container的解码器依赖上面的shapeNodeDecoder,要放在后面定义 implicit val containerDecoder: Decoder[Container] = deriveDecoder[Container]
3. 转成Scalaz Tree结构
解码拿到嵌套的ShapeNode结构后,只要写个简单的递归函数就能转成Scalaz Tree:
import scalaz.Tree def nodeToTree(node: ShapeNode): Tree[ShapeNode] = node match { case c: Container => Tree.node(c, c.children.map(nodeToTree).toStream) case leaf: Circle | leaf: Square => Tree.leaf(leaf) }
转换得到的Tree是完全不可变的,你可以直接用Scalaz Tree自带的modifyAt、insertChild、deleteAt等方法完成节点的增删改操作,每次操作都会返回新的Tree实例,不会修改原有数据。
优化说明
你之前手动遍历Circe Json结构校验类型的方案逻辑是通顺的,但是用Circe的半自动解码器可以省去所有字段手动提取的重复代码,只需要处理节点类型的分支判断即可,整体代码量可以减少一半以上。
内容的提问来源于stack exchange,提问作者depappas
相关产品推荐
相关产品推荐

