简体   繁体   中英

Using Jackson JSON Generator, how can I write multiple objects to one field?

Suppose I have the following three classes (getters and setters left out for brevity):

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

@JsonAutoDetect
public class InfoType1{
    private String fieldA;
}

@JsonAutoDetect
public class InfoType2{
    private String fieldB;
}

I"m trying to write a JsonSerializer.serialize() function that serializes an InfoCollection object in this format:

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

This is what I have now:

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

which is causing the following exception:

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

Am I missing something small or am I totally going about this the wrong way?

NOTE: A couple of the proposed solutions so far involve writing each individual field of InfoType1 and InfoType2 . I am looking for a solution that does not require this because I'd like to use the solution on huge classes with many fields.

Instead of calling writeFieldName("allInfo") you should call writeObjectFieldStart("allInfo") because "allInfo" is another JSON object. So your custom serializer should look the following way:

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();
}

Or you may try annotation based approach:

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

    /* getters, setters */
}

(You need to enable SerializationConfig.Feature.WRAP_ROOT_VALUE feature in order for this to work. See Serialization features )

In the future, when you have a stack trace, let us know in which line the problem shows up.

That said, the fix is probably:

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();

Solution using a wrapper object:

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

That makes Jackson see only what you want and how you want it.

Alternatively, use reflection to recursively collect all fields and their names:

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

collectFields should examine all fields and add everything to the list which is either a primitive or, say, where field.getType().getName().startsWith("java.lang") or any other rules you need.

If the field is a reference, call collectFields() recursively.

When you have the list, just call jsonGenerator in a loop to write the results.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM