如何通过Json注解将自定义泛型类Tree[T]序列化为Json?
Akka中泛型Tree[T]的JSON序列化解决方案
问题场景与需求
现有代码结构
- 泛型树容器定义:
trait NodeId { def id: Int } trait Tree[T <: NodeId] { def find(id: Int): Option[T] def size: Int def root: T def append(node: T, to: T): Tree[T] def remove(id: Int): Tree[T] } object Tree { val RootId : Int = 0 val EmptyId: Int = -1 def apply[T <: NodeId](root: T): Tree[T] = { if (root == null || root.id != RootId) throw new IllegalArgumentException("") else TreeImpl(root :: Nil) } private case class Node(id: Int, child: Int = EmptyId, sibling: Int = EmptyId) private case class TreeImpl[T <: NodeId](list : List[T], private val tree: List[Node] = Node(RootId) :: Nil) extends Tree[T] { override def find(id: Int): Option[T] = list.find(_.id == id) def size: Int = list.size def root: T = this.find(RootId).get override def append(node: T, to: T): Tree[T] = { /* 实现代码 */ } override def remove(id: Int): Tree[T] = { /* 实现代码 */ } } }
- 派生类与聚合根定义:
case class Stage(id : Int, title : String ) extends NodeId object Stage { val Root: Stage = Stage(id = Tree.RootId, title = "ROOT") } trait Aggregate extends CborSerializable { type State <: Aggregate def id: String } trait Plan extends Aggregate { type State = Plan def projectId: String def stages: Tree[Stage] } final case class PlacedPlan(id: String, projectId: String, stages: Tree[Stage] = Tree[Stage](Stage.Root)) extends Plan { /* 实现代码 */ }
遇到的问题
序列化PlacedPlan时抛出错误:State [PlacedPlan(...),TreeImpl(List(...),List(...)] isn't serializable.,尝试添加@JsonTypeInfo和@JsonSubTypes注解无效。
目标需求
需要将Tree[Stage]序列化为指定JSON结构,对应以下DTO类(匹配JSON字段名):
import com.fasterxml.jackson.annotation.JsonProperty case class ProjectDTO ( projectId: String, @JsonProperty("Stages") stages: StagesDTO ) case class StagesDTO ( @JsonProperty("stage") stage: List[Stage], @JsonProperty("nodes") nodes: List[NodeDTO] ) case class NodeDTO ( id: Int, child: Int, sibling: Int )
目标JSON结构:
{ "projectId": "proj-123", "Stages": { "stage": [ {"id": 0, "title": "ROOT"}, {"id": 1, "title": "Stage 1"} ], "nodes": [ {"id": 0, "child": 1, "sibling": -1}, {"id": 1, "child": -1, "sibling": -1} ] } }
解决方案
1. 实现业务模型与DTO的转换逻辑
由于TreeImpl是私有类,通过扩展方法安全暴露内部数据,避免反射风险:
import scala.reflect.ClassTag object TreeExtensions { implicit class TreeOps[T <: NodeId](tree: Tree[T]) { def toStagesDTO(implicit tag: ClassTag[T]): StagesDTO = { tree match { case impl: Tree.TreeImpl[T] => val nodes = impl.tree.map(node => NodeDTO(node.id, node.child, node.sibling)) StagesDTO(impl.list, nodes) case _ => throw new IllegalArgumentException("Unsupported Tree implementation") } } } // 反序列化:从DTO重建Tree[T] def fromStagesDTO[T <: NodeId](dto: StagesDTO)(implicit tag: ClassTag[T]): Tree[T] = { val root = dto.stage.find(_.id == Tree.RootId) .getOrElse(throw new IllegalArgumentException("Missing root node in DTO")) val treeNodes = dto.nodes.map(n => Tree.Node(n.id, n.child, n.sibling)) Tree.TreeImpl(dto.stage, treeNodes) } }
2. 编写自定义Jackson序列化器
实现Tree[Stage]与StagesDTO之间的序列化/反序列化逻辑:
import com.fasterxml.jackson.core.{JsonGenerator, JsonParser} import com.fasterxml.jackson.databind.{DeserializationContext, SerializerProvider, JsonSerializer, JsonDeserializer} import com.fasterxml.jackson.databind.module.SimpleModule class TreeStageSerializer extends JsonSerializer[Tree[Stage]] { override def serialize(value: Tree[Stage], gen: JsonGenerator, serializers: SerializerProvider): Unit = { import TreeExtensions._ val dto = value.toStagesDTO serializers.defaultSerializeValue(dto, gen) } } class TreeStageDeserializer extends JsonDeserializer[Tree[Stage]] { override def deserialize(p: JsonParser, ctxt: DeserializationContext): Tree[Stage] = { import TreeExtensions._ val dto = ctxt.readValue(p, classOf[StagesDTO]) fromStagesDTO(dto) } }
3. 配置Akka序列化绑定
在application.conf中注册自定义序列化器:
akka { serialization { serializers { tree-stage-json = "your.package.TreeStageSerializer" } bindings { "your.package.Tree[your.package.Stage]" = tree-stage-json "your.package.StagesDTO" = jackson-json "your.package.ProjectDTO" = jackson-json } } }
若使用Akka HTTP的Jackson marshaller,可通过代码注册模块:
import akka.http.scaladsl.marshallers.jackson.Jackson import com.fasterxml.jackson.databind.ObjectMapper val mapper = new ObjectMapper() val module = new SimpleModule() module.addSerializer(classOf[Tree[Stage]], new TreeStageSerializer()) module.addDeserializer(classOf[Tree[Stage]], new TreeStageDeserializer()) mapper.registerModule(module) // 全局生效的Jackson marshaller implicit val jacksonMarshaller = Jackson.marshaller(mapper)
4. 聚合根与DTO的转换(可选)
若需要直接序列化PlacedPlan到目标JSON,添加转换方法:
implicit class PlacedPlanOps(plan: PlacedPlan) { def toProjectDTO: ProjectDTO = { import TreeExtensions._ ProjectDTO(plan.projectId, plan.stages.toStagesDTO) } } // 反序列化示例 def toPlacedPlan(dto: ProjectDTO): PlacedPlan = { import TreeExtensions._ PlacedPlan( id = s"plan-${dto.projectId}", projectId = dto.projectId, stages = fromStagesDTO(dto.stages) ) }
关键说明
- 通过扩展方法访问
TreeImpl内部数据,避免直接反射带来的维护风险 - 自定义序列化器直接对接业务模型与DTO,严格匹配目标JSON结构
- 配置Akka序列化绑定后,框架会自动处理
Tree[Stage]类型的序列化逻辑
内容的提问来源于stack exchange,提问作者Caspar
相关产品推荐
相关产品推荐

