11

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.

CFL_Jeff
  • 2,589
  • 3
  • 31
  • 49

2 Answers2

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)

R. Oosterholt
  • 7,720
  • 2
  • 53
  • 77
Alex Vayda
  • 6,154
  • 5
  • 34
  • 50
  • 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. – CFL_Jeff Feb 12 '13 at 16:17
  • 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. – Alex Vayda Feb 12 '13 at 17:59
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.

Aaron Digulla
  • 321,842
  • 108
  • 597
  • 820
  • 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. – CFL_Jeff Feb 12 '13 at 16:15
  • 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. – Aaron Digulla Feb 12 '13 at 16:16
  • Alternatively, create a wrapper class that gives you an API which is closer to the output that Jackson should create. – Aaron Digulla Feb 12 '13 at 16:18
  • 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? – CFL_Jeff Feb 12 '13 at 16:25