Подтвердить что ты не робот

Джексон - Как обрабатывать (десериализовать) вложенный JSON?

{
  vendors: [
    {
      vendor: {
        id: 367,
        name: "Kuhn-Pollich",
        company_id: 1,
      }
    },
    {
      vendor: {
        id: 374,
        name: "Sawayn-Hermann",
        company_id: 1,
      }
  }]
}

У меня есть объект Vendor, который можно корректно десериализовать из одного "вендора" json, но я хочу десериализовать его в Vendor[], я просто не могу понять, как заставить Джексона сотрудничать. Любые советы?

4b9b3361

Ответ 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;
}

Он работает быстрее:)