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

时间:2013-02-12 15:29:41

标签: java json jackson jsonserializer

假设我有以下三个类(为了简洁省略了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;
}

我正在尝试编写JsonSerializer.serialize()函数,以此格式序列化InfoCollection对象:

{
    "allInfo":{
        "fieldA":"foo",
        "fieldB":"bar"
    }
}

这就是我现在所拥有的:

jsonGenerator.writeStartObject();
jsonGenerator.writeFieldName("allInfo");
jsonGenerator.writeObject(myInfoCollection.getInfo1());
jsonGenerator.writeObject(myInfoCollection.getInfo2());
jsonGenerator.writeEndObject();

导致以下异常:

 org.codehaus.jackson.JsonGenerationException: Can not start an object, expecting field name

我错过了一些小事,或者我是否完全以错误的方式解决这个问题?

注意:到目前为止,提出的一些解决方案涉及编写InfoType1InfoType2的每个字段。我正在寻找一个不需要这个的解决方案,因为我想在很多领域的大型课程上使用该解决方案。

2 个答案:

答案 0 :(得分: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();
}

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

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

    /* getters, setters */
}

(您需要启用SerializationConfig.Feature.WRAP_ROOT_VALUE功能才能实现此功能。请参阅Serialization features

答案 1 :(得分: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();

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

@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; }
}

这让杰克逊只看到你想要的东西以及你想要的东西。

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

List<Pair<String, Object>> data = collectFields( myInfoCollection );

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

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

获得列表后,只需在循环中调用jsonGenerator即可编写结果。