相关文章推荐
打篮球的手术刀  ·  WPF ...·  1 年前    · 
体贴的松树  ·  第1章 ...·  1 年前    · 

填坑,解决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"