50 votes

ObjectMapper ne peut pas désérialiser sans constructeur par défaut après la mise à niveau vers Spring Boot 2

J'ai les DTOs suivants :

@Value
public class PracticeResults {
    @NotNull
    Map<Long, Boolean> wordAnswers;
}

@Value
public class ProfileMetaDto {

    @NotEmpty
    String name;
    @Email
    String email;
    @Size(min = 5)
    String password;
}

@Value est une annotation Lombok qui génère un constructeur. Ce qui signifie que cette classe n'a pas de constructeur sans argument.

J'ai utilisé Spring Boot 1.4.3.RELEASE et ObjectMapper a été capable de désérialiser un tel objet à partir de JSON.

Après la mise à niveau vers Spring Boot 2.0.0.M7, je reçois l'exception suivante :

com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of PracticeResults (no Creators, like default construct, exist): cannot deserialize from Object value (no delegate- or property-based Creator)

La version de Jackson utilisée dans Spring Boot 1.4.3 est la suivante 2.8.10 et pour Spring Boot 2.0.0.M7 c'est 2.9.2 .

J'ai essayé de chercher ce problème sur Google mais je n'ai trouvé que des solutions avec @JsonCreator o @JsonProperty .

Alors, pourquoi cela fonctionne-t-il avec Spring Boot 1.4.3 et échoue-t-il avec Spring Boot 2 ? Est-il possible de configurer le bean pour qu'il se comporte de la même manière que l'ancienne version ?

0 votes

Avez-vous essayé de mettre à jour Jackson avec la dernière version stable 2.9.4 ? Le journal des modifications contient quelques corrections spécifiques liées à la désérialisation des cartes. github.com/FasterXML/jackson/wiki/Jackson-Release-2.9.3

0 votes

@LuisAguilar J'ai essayé une version plus récente mais rien n'a changé. J'ai différents DTO avec quelques chaînes de caractères qui n'ont pas non plus réussi à se désérialiser. Je pense que c'est lié d'une manière ou d'une autre à la configuration du mappeur d'objets de Spring, mais je n'ai rien trouvé concernant les constructeurs par défaut.

0 votes

Quelle version de Lombok utilisez-vous dans chaque cas ?

62voto

Tobias Points 1350

En raison des modifications apportées à la version 1.16.20 de Lombok, vous devez définir la propriété suivante dans votre fichier lombok.config (si vous n'avez pas ce fichier, vous pouvez le créer dans votre racine de projet) :

lombok.anyConstructor.addConstructorProperties=true

Ceci est décrit dans le changelog de Lombok : https://projectlombok.org/changelog .

Après cela, la @Value devrait être acceptée à nouveau par Jackson.

Vous serez peut-être intéressé par le suivi de la question GitHub correspondante ici, bien qu'il s'agisse de @Data : https://github.com/rzwitserloot/lombok/issues/1563

0 votes

Très bonne remarque ! :) Je vais seulement ajouter ici la description complète du changelog de Lombok : BREAKING CHANGE: lombok config key lombok.anyConstructor.suppressConstructorProperties is now deprecated and defaults to true, that is, by default lombok no longer automatically generates @ConstructorProperties annotations. New config key lombok.anyConstructor.addConstructorProperties now exists; set it to true if you want the old behavior. Oracle more or less broke this annotation with the release of JDK9, necessitating this breaking change.

10voto

solomkinmv Points 810

Une autre façon de résoudre ce problème. Utilisez Jackson module de noms de paramètres qui est inclus par défaut dans Spring Boot 2. Après cela, Jackson peut désérialiser les objets. Mais cela ne fonctionne que si vous avez plus d'une propriété dans l'objet. Dans le cas d'une seule propriété, je reçois le message d'erreur suivant :

com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `SomeClassName` (no Creators, like default construct, exist): cannot deserialize from Object value (no delegate- or property-based Creator)

Pour les raisons suivantes :

Annotation de marqueur qui peut être utilisée pour définir des constructeurs et des méthodes d'usine à utiliser pour instancier de nouvelles instances de la classe associée.

NOTE : lorsque l'on annote des méthodes de créateurs (constructeurs, méthodes de fabrique), la méthode doit être soit :

  • Méthode de constructeur/fabricant à argument unique sans JsonProperty pour l'argument : si c'est le cas, il s'agit d'un "délégué créateur", dans ce cas Jackson lie d'abord JSON dans le type de l'argument, puis appelle creator. Ceci est souvent utilisé en conjonction avec JsonValue (utilisé pour la sérialisation).
  • Méthode de construction/usinage où chaque argument est annoté soit avec JsonProperty o JacksonInject pour indiquer le nom de la propriété à laquelle se lier

Notez également que tous les JsonProperty Les annotations doivent spécifier le nom réel (PAS de chaîne vide pour "default") à moins que vous n'utilisiez un des modules d'extension qui peut détecter le nom du paramètre ; ceci parce que les versions du JDK avant 8 n'ont pas été capables de stocker et/ou de récupérer les noms des paramètres à partir du bytecode. Mais avec le JDK 8 (ou en utilisant des bibliothèques d'aide comme Paranamer, ou d'autres langages JVM comme Scala ou Kotlin), spécifier le nom est facultatif.

Pour gérer ce cas avec Lombok, j'ai utilisé la solution de contournement suivante :

@Value
@AllArgsConstructor(onConstructor = @__(@JsonCreator(mode = JsonCreator.Mode.PROPERTIES)))
class SomeClassName {...}

1 votes

Ne fonctionne pas, lance : java.lang.IllegalArgumentException : Définition de type non valide pour le type com.x.dto.y.PersonDTO : Argument #0 n'a pas de nom de propriété, n'est pas Injectable : ne peut pas être utilisé comme Créateur [constructeur pour com.x.dto.y.PersonDTO, annotations : {interface com.fasterxml.jackson.annotation.JsonCreator=@com.fasterxml.jackson.annotation.JsonCreator(mode=PROPERTIES)}] at [Source : UNKNOWN ; line : -1, column : -1] at com.fasterxml.jackson.databind.ObjectMapper._convert(ObjectMapper.java:3750) ~[jackson-databind-2.9.8.jar:2.9.8]

7voto

leeCoder Points 86

J'ai eu ce problème et la solution qui a fonctionné pour moi était de créer une Constructeur par défaut sans champs et le problème a disparu.

7voto

mareck_ste Points 137

Si vous avez des champs finaux dans votre classe et que vous préférez avoir un créateur d'objet qui utilise un constructeur spécifique pour désérialiser votre POJO avec @Data je suggère d'annoter le constructeur avec l'annotation @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) et les paramètres concrets du constructeur requis avec @JsonProperty("<field_name>") . L'exemple peut être vu ci-dessous :

package test.model;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;

import java.util.Date;

@Data
public class KafkaEventPojo {
    private final String executionId;
    private final String folder;
    private final Date created_at;

    private Date updated_at;
    // ... other params

    /**
     * Instantiates a new Kafka event pojo.
     *
     * @param executionId the execution or flow id of the event lifecycle
     * @param folder      the folder to be retrieved
     */
    @JsonCreator(mode = JsonCreator.Mode.PROPERTIES)
    public KafkaEventPojo(@JsonProperty("executionId") String executionId, @JsonProperty("folder") String folder) {
        this(executionId, folder, new Date(System.currentTimeMillis()));
    }

    private KafkaEventPojo(String executionId, String folder, Date date) {
        this.executionId = executionId;
        this.folder = folder;

        this.created_at = date;
        this.updated_at = date;
    }

    // ... other logic
}

4voto

Flavio Oliva Points 121

J'ai été confronté à ce problème, mais ma solution n'a pas consisté à ajouter ceci : (mode = JsonCreator.Mode.PROPERTIES)

Ce qui a vraiment résolu mon problème, c'est le @JsonProperty dans le constructeur.

Mon résultat final était le suivant :

@Value
public class ServerTimeResponse {

    long serverTime;

    @JsonCreator
    public ServerTimeResponse(@JsonProperty("serverTime") long serverTime) {
        this.serverTime = serverTime;
    }

}

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X