web-dev-qa-db-fra.com

ObjectMapper Jackson levant une exception NullPointerException même avec NON_NULL

Lorsque le code JSON suivant est utilisé et que "phones" ou "emailAddresses" ont la valeur null, je reçois une exception NullPointerException.

JSON:

{
  "item": {
    "messages": {
      "user.phone.missing": {
        "type": "warning",
        "key": "user.phone.missing",
        "message": "User profile does not have a phone number",
        "code": null
      },
      "user.email.missing": {
        "type": "warning",
        "key": "user.email.missing",
        "message": "User profile does not have an email address",
        "code": null
      },
      "user.es.sync.failed": {
        "type": "error",
        "key": "user.es.sync.failed",
        "message": "Unable to sync user",
        "code": null
      }
    },
    "user": {
      "firstName": "Test",
      "middleInitial": null,
      "lastName": "User",
      "createdDt": "2016-04-20 19:50:03+0000",
      "updatedDt": null,
      "lastVerifiedDt": null,
      "status": "DEACTIVATED",
      "tokens": [
        {
          "tokenHash": "test hash",
          "tokenValue": "test dn",
          "createdDt": "2016-04-20 19:50:03+0000",
          "updatedDt": null,
          "status": "ENABLED"
        }
      ],
      "phones": null,
      "emailAddresses": null
    }
  },
  "status": "SUCCESS",
  "errors": []
}

Et voici le stacktrace:

Exception in thread "main" com.fasterxml.jackson.databind.JsonMappingException: N/A (through reference chain: com.test.message.cte.CteItemResponse["item"]->com.test.message.cte.CteUserContext["user"]->com.test.User["phones"])
    at com.fasterxml.jackson.databind.deser.SettableBeanProperty._throwAsIOE(SettableBeanProperty.Java:510)
    at com.fasterxml.jackson.databind.deser.SettableBeanProperty._throwAsIOE(SettableBeanProperty.Java:493)
    at com.fasterxml.jackson.databind.deser.impl.MethodProperty.set(MethodProperty.Java:116)
    at com.fasterxml.jackson.databind.deser.impl.MethodProperty.deserializeAndSet(MethodProperty.Java:98)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.Java:295)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.Java:121)
    at com.fasterxml.jackson.databind.deser.SettableBeanProperty.deserialize(SettableBeanProperty.Java:464)
    at com.fasterxml.jackson.databind.deser.impl.MethodProperty.deserializeAndSet(MethodProperty.Java:98)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.Java:295)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.Java:121)
    at com.fasterxml.jackson.databind.deser.SettableBeanProperty.deserialize(SettableBeanProperty.Java:464)
    at com.fasterxml.jackson.databind.deser.impl.MethodProperty.deserializeAndSet(MethodProperty.Java:98)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.Java:295)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.Java:121)
    at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.Java:2888)
    at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.Java:2041)
    at com.test.Test.main(Test.Java:20)
    at Sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at Sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.Java:62)
    at Sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.Java:43)
    at Java.lang.reflect.Method.invoke(Method.Java:498)
    at com.intellij.rt.execution.application.AppMain.main(AppMain.Java:144)
Caused by: Java.lang.NullPointerException
    at com.test.User.setPhones(User.Java:202)
    at Sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at Sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.Java:62)
    at Sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.Java:43)
    at Java.lang.reflect.Method.invoke(Method.Java:498)
    at com.fasterxml.jackson.databind.deser.impl.MethodProperty.set(MethodProperty.Java:114)
    ... 19 more

Je configure mon ObjectMapper personnalisé comme ceci:

package com.test;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.util.ISO8601DateFormat;

public class MigrationObjectMapper extends ObjectMapper {
    private MigrationObjectMapper() {
        // do not serialize null value fields
        this.setSerializationInclusion(JsonInclude.Include.NON_NULL);

        configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
    }

    public static MigrationObjectMapper getMigrationObjectMapper(){
        return new MigrationObjectMapper();
    }
}

Ce qui signifie que CteUserContext cteUserContext = MigrationObjectMapper.getMigrationObjectMapper().convertValue(item.getItem(), new TypeReference<CteUserContext>(){}); renvoie l'erreur.

Inside CteUserContext est un objet User, qui contient List et List. Ne devraient-ils pas simplement être sérialisés en fonction de la configuration du mappeur d'objets?

5
ev0lution37

Commentaire de Per Rocki:

setSerializationInclusion ignorera les valeurs NULL uniquement pour la sérialisation non la désérialisation.

Je ne gérais pas si la valeur "phones" entrants était nulle dans ma classe User, elle renvoyait donc le NPE. J'ai mis à jour pour confirmer que ce n'est pas nul. Si c'est le cas, il court-circuiter toute autre logique:

/**
 * @param phones The phones
 */
@JsonProperty("phones")
public void setPhones(List<Phone> phones) {
    if ((phones != null) && (phones.size() > 1)) {
        int primaryIndex = -1;
        Phone primaryPhone = null;

        for (Phone p : phones) {
            if (p.getIsPrimary()) {
                primaryIndex = phones.indexOf(p);
                primaryPhone = p;
                break;
            }
        }

        phones.remove(primaryIndex);
        phones.add(0, primaryPhone);
    }
    this.phones = phones;
} 
5
ev0lution37

Je me demande si le fait que la propriété phones soit réellement présente dans votre code JSON signifie que votre mappeur ne la traite pas comme étant NON_NULL (c'est-à-dire qu'il ne manque pas, il est présent mais vous lui avez explicitement attribué la valeur null)

Essayez d'utiliser JsonInclude.Include.NON_DEFAULT à la place.

Sinon, avez-vous essayé d'utiliser l'annotation sur la classe à la place de this.setSerializationInclusion ()?

@JsonInclude(Include.NON_DEFAULT)
public class MigrationObjectMapper {
    ....
}
1
Brad