web-dev-qa-db-fra.com

Jackson, désérialise la classe avec des champs privés et arg-constructeur sans annotations

Il est possible de désérialiser une classe avec des champs privés et un constructeur d'argument personnalisé sans utiliser d'annotations ni modifier la classe, à l'aide de Jackson?

Je sais que c'est possible à Jackson avec cette combinaison: 1) Java 8, 2) compiler avec l'option "-parameters", et 3) les noms des paramètres correspondent à JSON. Mais c'est également possible dans GSON par défaut sans toutes ces restrictions.

Par exemple:

public class Person {
    private final String firstName;
    private final String lastName;
    private final int age;

    public Person(String firstName, String lastName, int age) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.age = age;
    }

    public static void main(String[] args) throws IOException {
        String json = "{firstName: \"Foo\", lastName: \"Bar\", age: 30}";

        System.out.println("GSON: " + deserializeGson(json)); // works fine
        System.out.println("Jackson: " + deserializeJackson(json)); // error
    }

    public static Person deserializeJackson(String json) throws IOException {
        ObjectMapper mapper = new ObjectMapper();
        mapper.enable(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES);
        mapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY);
        return mapper.readValue(json, Person.class);
    }

    public static Person deserializeGson(String json) {
        Gson gson = new GsonBuilder().create();
        return gson.fromJson(json, Person.class);
    }
}

Ce qui fonctionne bien pour GSON, mais Jackson lance:

Exception in thread "main" com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `jacksonParametersTest.Person` (no Creators, like default construct, exist): cannot deserialize from Object value (no delegate- or property-based Creator)
 at [Source: (String)"{firstName: "Foo", lastName: "Bar", age: 30}"; line: 1, column: 2]
    at com.fasterxml.jackson.databind.exc.InvalidDefinitionException.from(InvalidDefinitionException.Java:67)

C'est possible dans GSON, donc je m'attendrais à ce qu'il y ait un moyen d'utiliser Jackson sans modifier la classe Person, sans Java 8 et sans un désérialiseur personnalisé explicite. Est-ce que quelqu'un connaît une solution?

- Mise à jour, informations supplémentaires

Gson semble ignorer le constructeur d'arguments, il doit donc créer un constructeur sans argument en arrière-plan à l'aide de réflexions.

En outre, il existe un module Kotlin Jackson qui est capable de le faire pour les classes de données Kotlin, même sans l'indicateur de compilation "-parameters" . Il est donc curieux qu'une telle solution n'existe pas. pour Java Jackson.

C’est la solution (Nice and clean) disponible dans Kotlin Jackson (l’OMI devant également être disponible dans Java Jackson via un module personnalisé):

val mapper = ObjectMapper()
    .enable(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES)
    .registerModule(KotlinModule())     

val person: Person = mapper.readValue(json, Person::class.Java)
8
Devabc

Solution avec annotations mixtes

Vous pouvez utiliser des annotations mixtes . C'est une excellente alternative lorsque la modification des classes n'est pas une option. Vous pouvez penser à cela comme à une sorte de moyen orienté aspect pour ajouter plus d'annotations pendant l'exécution, afin d'augmenter celles qui sont définies statiquement.

En supposant que votre classe Person est définie comme suit:

public class Person {

    private final String firstName;
    private final String lastName;
    private final int age;

    public Person(String firstName, String lastName, int age) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.age = age;
    }

    // Getters omitted
}

Commencez par définir une classe abstraite d'annotations mixtes:

public abstract class PersonMixIn {

    PersonMixIn(@JsonProperty("firstName") String firstName,
                @JsonProperty("lastName") String lastName,
                @JsonProperty("age") int age) {
    }
}

Configurez ensuite ObjectMapper pour utiliser la classe définie en tant que mix-in pour votre POJO:

ObjectMapper mapper = new ObjectMapper();
mapper.enable(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES);
mapper.addMixIn(Person.class, PersonMixIn.class);

Et désérialiser le JSON:

String json = "{firstName: \"Foo\", lastName: \"Bar\", age: 30}";
Person person = mapper.readValue(json, Person.class);
7
cassiomolin

Jackson fournit le module jackson-modules-Java8 pour résoudre votre problème.

Vous devez construire votre ObjectMapper comme suit:

ObjectMapper mapper = new ObjectMapper()
        .enable(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES)
        .registerModule(new ParameterNamesModule(JsonCreator.Mode.PROPERTIES));

Vous devez ajouter -parameters en tant qu'argument du compilateur. 

Exemple pour maven:

 <plugin>
       <groupId>org.Apache.maven.plugins</groupId>
       <artifactId>maven-compiler-plugin</artifactId>
       <version>3.5.1</version>
       <configuration>
           <!--somecode-->

           <compilerArgument>-parameters</compilerArgument>

       </configuration>
 </plugin>

Pour le grade:

compileJava {
  options.compilerArgs << "-parameters"
}
1
Beno Arakelyan

Comme il n’existe pas de constructeur par défaut, jackson ou gson veulent créer leur propre instance. vous devez indiquer à l'API comment créer une telle instance en fournissant custom deserialize.

ici un code d'extrait

public class PersonDeserializer extends StdDeserializer<Person> { 
    public PersonDeserializer() {
        super(Person.class);
    } 

    @Override
    public Person deserialize(JsonParser jp, DeserializationContext ctxt) 
            throws IOException, JsonProcessingException {
        try {
            final JsonNode node = jp.getCodec().readTree(jp);
            final ObjectMapper mapper = new ObjectMapper();
            final Person person = (Person) mapper.readValue(node.toString(),
                    Person.class);
            return person;
        } catch (final Exception e) {
            throw new IOException(e);
        }
    }
}

Puis enregistrez un module simple pour gérer votre type

final ObjectMapper mapper = jacksonBuilder().build();
SimpleModule module = new SimpleModule();
module.addDeserializer(Person.class, new PersonDeserializer());
1
adia