Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 549
CFL_Jeff Avatar asked Feb 12 '13 15:02

CFL_Jeff


People also ask

What does JsonUnwrapped do?

JsonUnwrapped is used to indicate that a property should be serialized unwrapped, i.e. the target property will not be serialized as JSON object but its properties will be serialized as flattened properties of its containing Object.

Does Jackson use Java serialization?

This short tutorial shows how the Jackson library can be used to serialize Java object to XML and deserialize them back to objects.

What is Jackson serializer?

Jackson is a powerful and efficient Java library that handles the serialization and deserialization of Java objects and their JSON representations.


2 Answers

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)

like image 174
Alex Vayda Avatar answered Oct 02 '22 20:10

Alex Vayda


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.

like image 33
Aaron Digulla Avatar answered Oct 02 '22 18:10

Aaron Digulla