web-dev-qa-db-de.com

Jackson, deserialize Klasse mit privaten Feldern und arg-Konstruktor ohne Anmerkungen

Es ist möglich, zu einer Klasse mit privaten Feldern und einem benutzerdefinierten Argumentkonstruktor zu deserialisieren, ohne Anmerkungen zu verwenden und ohne die Klasse zu ändern, indem Sie Jackson verwenden.

Ich weiß, dass es in Jackson bei Verwendung dieser Kombination möglich ist: 1) Java 8, 2) Kompilieren mit der Option "-parameters" und 3) die Parameternamen stimmen mit JSON überein. Es ist aber auch in GSON standardmäßig ohne alle diese Einschränkungen möglich.

Zum Beispiel:

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

Was für GSON gut funktioniert, aber Jackson wirft:

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)

Es ist in GSON möglich, also würde ich erwarten, dass es in Jackson einen Weg geben muss, ohne die Person-Klasse, ohne Java 8 und ohne einen expliziten benutzerdefinierten Deserializer zu ändern. Kennt jemand eine Lösung?

- Update, zusätzliche Informationen

Gson scheint den Argumentkonstruktor zu überspringen, so dass hinter den Kulissen ein Reflektor ohne Argumente erstellt werden muss.

Es gibt auch ein Kotlin Jackson-Modul , das dies für Kotlin-Datenklassen auch ohne das Compiler-Flag "-parameters" .. tun kann für Java Jackson.

Dies ist die (schöne und saubere) Lösung, die in Kotlin Jackson verfügbar ist (die IMO auch in Java Jackson über ein benutzerdefiniertes Modul verfügbar werden sollte):

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

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

Lösung mit eingemischten Anmerkungen

Sie könnten mix-in-Anmerkungen verwenden. Es ist eine großartige Alternative, wenn das Ändern der Klassen keine Option ist. Sie können es sich als eine Art Aspekt-orientiertes Verfahren vorstellen, um während der Laufzeit weitere Anmerkungen hinzuzufügen, um statisch definierte zu erweitern.

Angenommen, Ihre Person-Klasse ist wie folgt definiert:

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
}

Definieren Sie zunächst eine abstrakte Klasse für die Mix-In-Annotation:

public abstract class PersonMixIn {

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

Konfigurieren Sie dann ObjectMapper so, dass die definierte Klasse als Mix-In für Ihren POJO verwendet wird:

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

Und deserialisiert die JSON:

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

Jackson bietet das Modul jackson-modules-Java8 an, um Ihr Problem zu lösen.

Sie müssen Ihre ObjectMapper wie folgt erstellen:

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

Sie müssen -parameters als Compiler-Argument hinzufügen. 

Beispiel für 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>

Für gradle:

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

Da es keinen Standardkonstruktor gibt, möchten jackson oder gson von dort eine Instanz erstellen. Sie sollten der API mitteilen, wie Sie eine solche Instanz erstellen, indem Sie custom deserialize angeben.

hier ein Snippet-Code

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

Registrieren Sie dann ein einfaches Modul, um Ihren Typ zu bearbeiten

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