Используя генератор Jackson JSON, как я могу написать несколько объектов в одно поле?
Предположим, что у меня есть следующие три класса (убиратели и сеттеры опущены для краткости):
@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
Я пропустил что-то маленькое или я полностью об этом не ошибаюсь?
ПРИМЕЧАНИЕ.. Несколько предлагаемых решений пока включают в себя запись каждого отдельного поля InfoType1
и InfoType2
. Я ищу решение, которое не требует этого, потому что я хотел бы использовать решение для огромных классов со многими полями.
Ответы
Ответ 1
Вместо вызова 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
. См. Функции сериализации)
Ответ 2
В будущем, когда у вас есть трассировка стека, сообщите нам, в какой строке появляется проблема.
Тем не менее, исправление возможно:
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
в цикле, чтобы записать результаты.