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

2

12

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.

Osmium answered 12/2, 2013 at 15:29 Comment(0)
L
12

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)

Leporine answered 12/2, 2013 at 16:15 Comment(2)
This method would probably work, but it has me writing each individual field. I'm trying to come up with a solution that I could apply to larger classes that have many more fields.Osmium
You may do it with annotations as well, but it is not that flexible as custom serializers. I have updated the answer with the annotation based example.Leporine
K
4

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.

Kanter answered 12/2, 2013 at 15:58 Comment(4)
This method would probably work, but it has me writing each individual field. I'm trying to come up with a solution that I could apply to larger classes that have many more fields.Osmium
Use reflection and annotations to locate the fields to write and build your generator from a couple of helper methods/classes that allow you to reuse the code.Kanter
Alternatively, create a wrapper class that gives you an API which is closer to the output that Jackson should create.Kanter
But it still comes down to the problem of combining fields from different POJOs into a single JSON object. Even with annotations and/or wrapper classes, I cannot think of a way to do that. Maybe you could change your answer to demonstrate this?Osmium

© 2022 - 2024 — McMap. All rights reserved.