使用上下文堆栈而不是使用 JsonIdentityInfo 或 JsonBackReference 和 JsonManagedReference 处理 Jackson 循环依赖
Handling Jackson circular dependencies using a context stack instead of using JsonIdentityInfo, or JsonBackReference & JsonManagedReference
我正在使用 Jackson 来序列化 JSON,但我的 JSON 客户端不是 Java,并且不要使用 Jackson。
我想避免循环引用序列化无限递归,每当序列化整个 bean 会导致无限递归时,序列化 bean 的 id 而不是整个 bean。
这可以通过维护当前序列化上下文的堆栈来实现。
每当 Jackson 开始序列化一个 bean 时,它应该查看堆栈是否包含该 bean。
如果bean已经在栈中,序列化id而不是整个bean,从而避免无限递归。
如果bean不在栈中,则将其压入栈中,并正常序列化整个bean。
Jackson 完成 bean 的序列化后,将 bean 弹出堆栈。
因此,我想将下面的Java序列化为下面的JSON:
Java:
class A {
String id;
B b;
B c;
}
class B {
String id;
A a;
}
class Main {
public static void main(String[] args) {
A a = new A();
B b = new B();
a.id = "a1";
b.id = "b1";
a.b = b;
a.c = b;
b.a = a;
ObjectMapper m = new ObjectMapper();
System.out.println(m.writeValueAsString(a));
System.out.println(m.writeValueAsString(b));
}
}
JSON:
{
"id": "a1",
"b": {
"id": "b1",
"a": "a1"
},
"c": {
"id": "b1",
"a": "a1"
}
}
{
"id": "b1",
"a": {
"id": "a1",
"b": "b1"
}
}
我只知道 Jackson 内置的两种处理循环引用的方法,如果我理解正确的话,这两种方法都不能输出上面的 JSON:
@JsonIdentityInfo
,在最佳设置时,用以下方式注释 A
和 B
:
@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
会输出(带有说明问题的注释):
{
"id": "a1",
"b": {
"id": "b1",
"a": "a1"
},
"c": "b1" // problem: value of "c" is b.id instead of b with b.a = a.id
}
{
"id": "b1",
"a": {
"id": "a1",
"b": "b1"
}
}
@JsonManagedReference
& @JsonBackReference
,为此我必须指定一侧为前向,另一侧为后向。假设我选择 A
-> B
属性作为转发,然后我得到:
{
"id": "a1",
"b": {
"id": "b1",
"a": "a1"
},
"c": {
"id": "b1",
"a": "a1"
}
}
{
"id": "b1"
// problem: "a" isn't serialized
}
是否有任何内置的 Jackson 方式可以按照我的意愿进行序列化?
如果没有,Jackson 是否维护序列化堆栈?
如果是,我如何访问它并以某种方式修改序列化输出?
如果不是,我如何连接到 Jackson 以在我自己的 类 中维护一个实现一些 Jackson 接口/扩展一些 Jackson 类 的堆栈,然后使用该堆栈影响序列化?
Jackson 确实保持了几个堆栈。较旧的是 JsonGenerator
级别,参见 getOuputContext()
。这让您可以看到物理上正在输出什么,即哪些范围(对象、数组)是打开的。
但是 Jackson 2.5 添加了新方法 JsonGenerator.getCurrentValue()
和来自数据绑定的匹配调用,这应该另外为您提供正在序列化的对象图视图,至少在将两者结合时是这样。
要访问当前值的祖先,您需要遍历 "output context",向输出堆栈询问当前值。
我正在使用 Jackson 来序列化 JSON,但我的 JSON 客户端不是 Java,并且不要使用 Jackson。
我想避免循环引用序列化无限递归,每当序列化整个 bean 会导致无限递归时,序列化 bean 的 id 而不是整个 bean。
这可以通过维护当前序列化上下文的堆栈来实现。
每当 Jackson 开始序列化一个 bean 时,它应该查看堆栈是否包含该 bean。
如果bean已经在栈中,序列化id而不是整个bean,从而避免无限递归。
如果bean不在栈中,则将其压入栈中,并正常序列化整个bean。
Jackson 完成 bean 的序列化后,将 bean 弹出堆栈。
因此,我想将下面的Java序列化为下面的JSON:
Java:
class A {
String id;
B b;
B c;
}
class B {
String id;
A a;
}
class Main {
public static void main(String[] args) {
A a = new A();
B b = new B();
a.id = "a1";
b.id = "b1";
a.b = b;
a.c = b;
b.a = a;
ObjectMapper m = new ObjectMapper();
System.out.println(m.writeValueAsString(a));
System.out.println(m.writeValueAsString(b));
}
}
JSON:
{
"id": "a1",
"b": {
"id": "b1",
"a": "a1"
},
"c": {
"id": "b1",
"a": "a1"
}
}
{
"id": "b1",
"a": {
"id": "a1",
"b": "b1"
}
}
我只知道 Jackson 内置的两种处理循环引用的方法,如果我理解正确的话,这两种方法都不能输出上面的 JSON:
@JsonIdentityInfo
,在最佳设置时,用以下方式注释A
和B
:@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
会输出(带有说明问题的注释):
{ "id": "a1", "b": { "id": "b1", "a": "a1" }, "c": "b1" // problem: value of "c" is b.id instead of b with b.a = a.id } { "id": "b1", "a": { "id": "a1", "b": "b1" } }
@JsonManagedReference
&@JsonBackReference
,为此我必须指定一侧为前向,另一侧为后向。假设我选择A
->B
属性作为转发,然后我得到:{ "id": "a1", "b": { "id": "b1", "a": "a1" }, "c": { "id": "b1", "a": "a1" } } { "id": "b1" // problem: "a" isn't serialized }
是否有任何内置的 Jackson 方式可以按照我的意愿进行序列化?
如果没有,Jackson 是否维护序列化堆栈?
如果是,我如何访问它并以某种方式修改序列化输出?
如果不是,我如何连接到 Jackson 以在我自己的 类 中维护一个实现一些 Jackson 接口/扩展一些 Jackson 类 的堆栈,然后使用该堆栈影响序列化?
Jackson 确实保持了几个堆栈。较旧的是 JsonGenerator
级别,参见 getOuputContext()
。这让您可以看到物理上正在输出什么,即哪些范围(对象、数组)是打开的。
但是 Jackson 2.5 添加了新方法 JsonGenerator.getCurrentValue()
和来自数据绑定的匹配调用,这应该另外为您提供正在序列化的对象图视图,至少在将两者结合时是这样。
要访问当前值的祖先,您需要遍历 "output context",向输出堆栈询问当前值。