填坑,解决json对象循环引用,在复杂案例中的应用
3 年前
· 来自专栏
编程之坑
本篇模拟一个真实案例,为了填坑,代码比较复杂。
还没搞清最基础原理的,可先看入坑篇:
// 这是Kotlin代码,Java也是一样原理,都是用@JsonView实现单向透明。
// 原理:使用@JsonView和withView(JvXxx) 让jackson从某一方向扫描时,直接忽略某些字段。
// 所以一对多/多对多的循环引用问题都能解决,从任意方向开始引用的都行。
// 由于这种玩法似乎挤占了JsonView的原有用途,所以是有代价的,本篇讨论了代价及如何避坑。
// 高级篇:模拟真实案例中JsonView的复杂关系,估计没几个人会遇到这么复杂的情况。
import com.fasterxml.jackson.annotation.JsonView
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
// 各种各样的 JsonView,跟数据库的View是同样用途
private interface JvClientMini //给客户端的简略信息
private interface JvClient : JvClientMini //给客户端的完整信息
private interface JvPersist //用于数据存储
// 为了从各个方向都能随心所欲地切断循环引用,
// 下边又定义了一批以JvUser和JvRole为前缀的JsonView。
// 这些定义本来有可能不必要的,但这就是代价。
// 不过代价倒也不算太高,毕竟输出一个个符合特定要求的Json也可能要费不少代码。
private interface JvUserClientMini : JvClientMini
private interface JvUserClient : JvUserClientMini, JvClient
private interface JvUserPersist : JvPersist
private interface JvRoleClient : JvClient
private interface JvRolePersist : JvPersist
private class User(
var id: Long = 0,
var name: String = "",
@JsonView(value = [JvUserPersist::class])
var password: String = "123456",
@JsonView(value = [JvUserClient::class, JvUserPersist::class])
var roles: List<Role> = listOf()
private class Role(
var id: Long = 0,
var role: String = "",
@JsonView(value = [JvPersist::class])
var auth: String = "all",
@JsonView(value = [JvRoleClient::class])
var users: List<User> = listOf()
@JsonView(value = [JvClient::class])
fun usersCount() = users.size
private var writer = jacksonObjectMapper().writer().withDefaultPrettyPrinter()
private fun printJsonWithView(obj: Any, jv: Class<out Any>) {
println("JsonView: ${jv.simpleName}");
writer.withView(jv).writeValueAsString(obj).let { println(it) }
fun main() {
//construct many to many reference
val list = listOf(1, 2)
val users = list.map { User(it.toLong(), "name-$it") }
val roles = list.map { Role(it.toLong(), "role-$it") }
users.forEach { it.roles = roles }
roles.forEach { it.users = users }
//use @JsonView & withView(FromXXX) to resolve recursion reference
println("解决json一对多&多对多循环引用问题,从任意方向开始引用的都没问题")
println("user -> roles -> users -> roles ... OK")
printJsonWithView(users[0], JvUserClientMini::class.java)
printJsonWithView(users[0], JvUserClient::class.java)
printJsonWithView(users[0], JvUserPersist::class.java)
println("role -> users -> roles -> users ... OK")
printJsonWithView(roles[0], JvRoleClient::class.java)
printJsonWithView(roles[0], JvRolePersist::class.java)
// Exception in thread "main" com.fasterxml.jackson.databind.JsonMappingException:
// Infinite recursion (StackOverflowError) (through reference chain:
// println(writer.writeValueAsString(users[0]))
// println(writer.writeValueAsString(roles[0]))
// 解决jackson循环引用问题,网上其他常见的套路:
// 一对多可行,单向引用可行,多向引用比较麻烦
// @JsonBackReference
// @JsonManagedReference
// @JsonIdentityReference
// @JsonIdentityInfo
/* 输出结果:
解决json一对多&多对多循环引用问题,从任意方向开始引用的都没问题
user -> roles -> users -> roles ... OK
JsonView: JvUserClientMini
"id" : 1,
"name" : "name-1"
JsonView: JvUserClient
"id" : 1,
"name" : "name-1",
"roles" : [ {
"id" : 1,
"role" : "role-1",
"usersCount" : 2
"id" : 2,
"role" : "role-2",
"usersCount" : 2
JsonView: JvUserPersist
"id" : 1,
"name" : "name-1",
"password" : "123456",
"roles" : [ {
"id" : 1,
"role" : "role-1",
"auth" : "all"
"id" : 2,
"role" : "role-2",
"auth" : "all"
role -> users -> roles -> users ... OK
JsonView: JvRoleClient
"id" : 1,
"role" : "role-1",
"users" : [ {
"id" : 1,
"name" : "name-1"
"id" : 2,
"name" : "name-2"
"usersCount" : 2
JsonView: JvRolePersist
"id" : 1,
"role" : "role-1",
"auth" : "all"