web-dev-qa-db-fra.com

Jackson - Comment traiter (désérialiser) les fichiers JSON imbriqués?

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

J'ai un objet Vendor qui peut être correctement désérialisé à partir d'un seul json "vendor", mais je souhaite le désérialiser dans un Vendor[], Je n'arrive pas à comprendre comment faire coopérer Jackson. Des conseils?

40
Sam Stern

Vos données sont problématiques car vous avez des objets wrapper internes dans votre tableau. Vraisemblablement, votre objet Vendor est conçu pour gérer id, name, company_id, mais chacun de ces objets est également encapsulé dans un objet avec une propriété unique vendor.

Je suppose que vous utilisez le modèle de Jackson Data Binding .

Si oui, il y a deux choses à considérer:

Le premier utilise une propriété spéciale de configuration de Jackson. Jackson - depuis la version 1.9, cela n’est peut-être pas disponible si vous utilisez une ancienne version de Jackson - fournit UNWRAP_ROOT_VALUE . Il est conçu pour les cas où vos résultats sont encapsulés dans un objet de propriété unique de niveau supérieur que vous souhaitez supprimer.

Alors, jouez avec:

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

La seconde utilise des objets wrapper. Même après avoir supprimé l'objet enveloppe externe, le problème de vos objets Vendor est toujours encapsulé dans un objet à propriété unique. Utilisez un wrapper pour contourner ceci:

class VendorWrapper
{
    Vendor vendor;

    // gettors, settors for vendor if you need them
}

De même, au lieu d'utiliser UNWRAP_ROOT_VALUES, vous pouvez également définir une classe wrapper pour gérer l’objet externe. En supposant que vous ayez le bon objet Vendor, VendorWrapper, vous pouvez définir:

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); 

L'arborescence des objets de VendorsWrapper est analogue à votre JSON:

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

Enfin, vous pouvez utiliser le modèle d'arbre de ) de Jackson pour analyser ceci dans JsonNodes, en ignorant le nœud externe, et pour chaque JsonNode dans le ArrayNode, appelez:

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

Cela pourrait entraîner moins de code, mais cela ne semble pas moins maladroit que d'utiliser deux wrappers.

26
pb2q

Voici une solution brute mais plus déclarative. Je n'ai pas réussi à le résumer à une seule annotation, mais cela semble bien fonctionner. Aussi pas sûr de la performance sur les grands ensembles de données.

Étant donné ce JSON:

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

Et ces objets modèles:

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

et

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

Où le voodoo de Jackson est implémenté comme:

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

et

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

J'espère que cela est utile à quelqu'un!

32
Patrick

@ Patrick j'améliorerais un peu votre solution

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

Ça marche plus vite :)

11
Alexey