使用Jackson JSON Generator,如何将多个对象写入一个字段?

CFL*_*eff 11 java json jsonserializer jackson

假设我有以下三个类(为了简洁省略了getter和setter):

@JsonAutoDetect
public class InfoCollection{
    private InfoType1 info1;
    private InfoType2 info2;
}

@JsonAutoDetect
public class InfoType1{
    private String fieldA;
}

@JsonAutoDetect
public class InfoType2{
    private String fieldB;
}
Run Code Online (Sandbox Code Playgroud)

我正在尝试编写一个以这种格式JsonSerializer.serialize()序列化InfoCollection对象的函数:

{
    "allInfo":{
        "fieldA":"foo",
        "fieldB":"bar"
    }
}
Run Code Online (Sandbox Code Playgroud)

这就是我现在拥有的:

jsonGenerator.writeStartObject();
jsonGenerator.writeFieldName("allInfo");
jsonGenerator.writeObject(myInfoCollection.getInfo1());
jsonGenerator.writeObject(myInfoCollection.getInfo2());
jsonGenerator.writeEndObject();
Run Code Online (Sandbox Code Playgroud)

这导致以下异常:

 org.codehaus.jackson.JsonGenerationException: Can not start an object, expecting field name
Run Code Online (Sandbox Code Playgroud)

我错过了一些小事情,还是我完全错误地采取了这种做法?

注:一对夫妇提出的解决方案迄今涉及编写的每一个人领域InfoType1InfoType2.我正在寻找一个不需要这个的解决方案,因为我想在具有许多领域的大型类中使用该解决方案.

Ale*_*yda 9

而不是调用writeFieldName("allInfo")你应该调用writeObjectFieldStart("allInfo")因为"allInfo"是另一个JSON对象.因此,您的自定义序列化程序应该采用以下方式:

public void serialize(InfoCollection infoCollection, JsonGenerator jgen, SerializerProvider provider) throws IOException{
    jgen.writeStartObject();
    jgen.writeObjectFieldStart("allInfo");
    jgen.writeObjectField("fieldA", infoCollection.getInfo1().getFieldA());
    jgen.writeObjectField("fieldB", infoCollection.getInfo2().getFieldB());
    jgen.writeEndObject();
    jgen.writeEndObject();
}
Run Code Online (Sandbox Code Playgroud)

或者您可以尝试基于注释的方法:

@JsonRootName("allInfo")
public class InfoCollection {
    @JsonUnwrapped
    private InfoType1 info1;
    @JsonUnwrapped
    private InfoType2 info2;

    /* getters, setters */
}
Run Code Online (Sandbox Code Playgroud)

(您需要启用SerializationConfig.Feature.WRAP_ROOT_VALUE功能才能使其正常工作.请参阅序列化功能)


Aar*_*lla 4

将来,当您有堆栈跟踪时,请让我们知道问题出现在哪一行。

也就是说,解决办法可能是:

jsonGenerator.writeStartObject();
jsonGenerator.writeFieldName("allInfo");

jsonGenerator.writeStartObject(); // start nested object
jsonGenerator.writeFieldName("fieldA"); // start field
jsonGenerator.writeObject(myInfoCollection.getInfo1().fieldA);

jsonGenerator.writeFieldName("fieldB"); // start fieldB
jsonGenerator.writeObject(myInfoCollection.getInfo2().fieldB);

jsonGenerator.writeEndObject(); // end nested object

jsonGenerator.writeEndObject();
Run Code Online (Sandbox Code Playgroud)

使用包装对象的解决方案:

@JsonAutoDetect
public class Wrapper {
    private transient InfoCollection data; // transient makes Jackson ignore this

    public String getFieldA() { return data.info1.fieldA; }
    public String getFieldB() { return data.info1.fieldB; }
}
Run Code Online (Sandbox Code Playgroud)

这让杰克逊只看到你想要什么以及你想要如何得到它。

或者,使用反射递归收集所有字段及其名称:

List<Pair<String, Object>> data = collectFields( myInfoCollection );
Run Code Online (Sandbox Code Playgroud)

collectFields应该检查所有字段并将所有内容添加到列表中,该列表可以是原始规则,也可以是field.getType().getName().startsWith("java.lang")您需要的位置规则或任何其他规则。

如果该字段是引用,则递归collectFields()调用。

有了列表后,只需jsonGenerator循环调用即可写入结果。