Jackson – ¿Cómo se procesa (deserializa) el JSON nested?

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

Tengo un objeto Proveedor que puede deserializarse adecuadamente de un único proveedor, pero quiero deserializarlo en un Vendor[] , simplemente no puedo entender cómo hacer que Jackson coopere. ¿Algun consejo?

Sus datos son problemáticos porque tiene objetos de envoltura internos en su matriz. Presumiblemente, su objeto Vendor está diseñado para manejar id , name , company_id , pero cada uno de esos objetos múltiples también está envuelto en un objeto con un único vendor propiedades.

Supongo que estás usando el modelo Jackson Data Binding .

Si es así, hay dos cosas a considerar:

El primero es usar una propiedad especial de configuración de Jackson. Jackson, desde 1.9, creo que esto puede no estar disponible si estás usando una versión anterior de Jackson, proporciona UNWRAP_ROOT_VALUE . Está diseñado para casos en los que sus resultados se envuelven en un objeto de propiedad única de primer nivel que desea descartar.

Entonces, juega con:

 objectMapper.configure(SerializationConfig.Feature.UNWRAP_ROOT_VALUE, true); 

El segundo es usar objetos de envoltura. Incluso después de descartar el objeto envoltorio externo, todavía tiene el problema de que los objetos del Vendor se envuelven en un objeto de propiedad única. Use un envoltorio para evitar esto:

 class VendorWrapper { Vendor vendor; // gettors, settors for vendor if you need them } 

De forma similar, en lugar de usar UNWRAP_ROOT_VALUES , también podría definir una clase contenedora para manejar el objeto externo. Suponiendo que tiene el Vendor correcto, objeto VendorWrapper , puede definir:

 class VendorsWrapper { List vendors = new ArrayList(); // gettors, settors for vendors if you need them } // in your deserialization code: ObjectMapper mapper = new ObjectMapper(); JsonNode rootNode = mapper.readValue(jsonInput, VendorsWrapper.class); 

El árbol de objetos de VendorsWrapper es análogo a su JSON:

 VendorsWrapper: vendors: [ VendorWrapper vendor: Vendor, VendorWrapper: vendor: Vendor, ... ] 

Finalmente, puede usar el modelo de Jackson Tree para analizar esto en JsonNodes , descartar el nodo externo y, para cada JsonNode en ArrayNode , llamar:

 mapper.readValue(node.get("vendor").getTextValue(), Vendor.class); 

Eso podría dar como resultado menos código, pero parece no menos torpe que usar dos envoltorios.

Aquí hay una solución aproximada pero más declarativa. No he podido llegar a una sola anotación, pero parece que funciona bien. Además, no estoy seguro sobre el rendimiento en grandes conjuntos de datos.

Dado este JSON:

 { "list": [ { "wrapper": { "name": "Jack" } }, { "wrapper": { "name": "Jane" } } ] } 

Y estos objetos modelo:

 public class RootObject { @JsonProperty("list") @JsonDeserialize(contentUsing = SkipWrapperObjectDeserializer.class) @SkipWrapperObject("wrapper") public InnerObject[] innerObjects; } 

y

 public class InnerObject { @JsonProperty("name") public String name; } 

Donde el vudú de Jackson se implementa como:

 @Retention(RetentionPolicy.RUNTIME) @JacksonAnnotation public @interface SkipWrapperObject { String value(); } 

y

 public class SkipWrapperObjectDeserializer extends JsonDeserializer 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); } } 

¡Espero que sea útil para alguien!

@Patrick mejoraría un poco tu solución

 @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; } 

Funciona más rápido 🙂