Джексон - Как обрабатывать (десериализовать) вложенный JSON?
{
vendors: [
{
vendor: {
id: 367,
name: "Kuhn-Pollich",
company_id: 1,
}
},
{
vendor: {
id: 374,
name: "Sawayn-Hermann",
company_id: 1,
}
}]
}
У меня есть объект Vendor, который можно корректно десериализовать из одного "вендора" json, но я хочу десериализовать его в Vendor[]
, я просто не могу понять, как заставить Джексона сотрудничать. Любые советы?
Ответы
Ответ 1
Ваши данные проблематичны в том, что у вас есть внутренние объекты оболочки в вашем массиве. Предположительно, ваш объект Vendor
предназначен для обработки id
, name
, company_id
, но каждый из этих нескольких объектов также завернут в объект с единственным свойством Vendor
.
Я предполагаю, что вы используете модель Jackson Data Binding.
Если это так, то нужно рассмотреть две вещи:
Первый использует специальное свойство конфигурации Jackson. Джексон - с 1.9, я считаю, что это может быть недоступно, если вы используете старую версию Jackson - предоставляет UNWRAP_ROOT_VALUE
. Он предназначен для случаев, когда ваши результаты завернуты в объект одноуровневого объекта верхнего уровня, который вы хотите отменить.
Итак, поиграйте с:
objectMapper.configure(SerializationConfig.Feature.UNWRAP_ROOT_VALUE, true);
Во-вторых, используются объекты-обертки. Даже после отбрасывания внешнего объекта обертки у вас по-прежнему возникает проблема с вашими объектами Vendor
, обернутыми в объект с одним свойством. Используйте обертку, чтобы обойти это:
class VendorWrapper
{
Vendor vendor;
// gettors, settors for vendor if you need them
}
Аналогично, вместо использования UNWRAP_ROOT_VALUES
вы также можете определить класс-оболочку для обработки внешнего объекта. Предполагая, что у вас есть правильный объект Vendor
, VendorWrapper
, вы можете определить:
class VendorsWrapper
{
List<VendorWrapper> vendors = new ArrayList<VendorWrapper>();
// gettors, settors for vendors if you need them
}
// in your deserialization code:
ObjectMapper mapper = new ObjectMapper();
JsonNode rootNode = mapper.readValue(jsonInput, VendorsWrapper.class);
Дерево объектов для VendorsWrapper аналогично вашему JSON:
VendorsWrapper:
vendors:
[
VendorWrapper
vendor: Vendor,
VendorWrapper:
vendor: Vendor,
...
]
Наконец, вы можете использовать "Древовидную модель" Jackson для анализа этого в JsonNodes
, отбрасывая внешний node и для каждого JsonNode
в ArrayNode
, вызывающий:
mapper.readValue(node.get("vendor").getTextValue(), Vendor.class);
Это может привести к уменьшению количества кода, но оно кажется не менее неуклюжим, чем использование двух оберток.
Ответ 2
Вот грубое, но более декларативное решение. Я не смог довести его до одной аннотации, но это, похоже, хорошо работает. Также не уверены в производительности на больших наборах данных.
Учитывая этот JSON:
{
"list": [
{
"wrapper": {
"name": "Jack"
}
},
{
"wrapper": {
"name": "Jane"
}
}
]
}
И эти объекты модели:
public class RootObject {
@JsonProperty("list")
@JsonDeserialize(contentUsing = SkipWrapperObjectDeserializer.class)
@SkipWrapperObject("wrapper")
public InnerObject[] innerObjects;
}
и
public class InnerObject {
@JsonProperty("name")
public String name;
}
Где Джеквудский вуду реализован как:
@Retention(RetentionPolicy.RUNTIME)
@JacksonAnnotation
public @interface SkipWrapperObject {
String value();
}
и
public class SkipWrapperObjectDeserializer extends JsonDeserializer<Object> implements
ContextualDeserializer {
private Class<?> wrappedType;
private String wrapperKey;
public JsonDeserializer<?> createContextual(DeserializationContext ctxt,
BeanProperty property) throws JsonMappingException {
SkipWrapperObject skipWrapperObject = property
.getAnnotation(SkipWrapperObject.class);
wrapperKey = skipWrapperObject.value();
JavaType collectionType = property.getType();
JavaType collectedType = collectionType.containedType(0);
wrappedType = collectedType.getRawClass();
return this;
}
@Override
public Object deserialize(JsonParser parser, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
ObjectMapper mapper = new ObjectMapper();
ObjectNode objectNode = mapper.readTree(parser);
JsonNode wrapped = objectNode.get(wrapperKey);
Object mapped = mapIntoObject(wrapped);
return mapped;
}
private Object mapIntoObject(JsonNode node) throws IOException,
JsonProcessingException {
JsonParser parser = node.traverse();
ObjectMapper mapper = new ObjectMapper();
return mapper.readValue(parser, wrappedType);
}
}
Надеюсь, это кому-то полезно!
Ответ 3
@Patrick
Я бы немного улучшил ваше решение
@Override
public Object deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
ObjectNode objectNode = jp.readValueAsTree();
JsonNode wrapped = objectNode.get(wrapperKey);
JsonParser parser = node.traverse();
parser.setCodec(jp.getCodec());
Vendor mapped = parser.readValueAs(Vendor.class);
return mapped;
}
Он работает быстрее:)