Джексон, десериализуй класс с приватными полями и arg-конструктором без аннотаций

Можно десериализовать класс с частными полями и настраиваемым конструктором аргументов без использования аннотаций и без изменения класса с помощью Jackson?

Я знаю, что в Джексоне это возможно при использовании этой комбинации: 1) Java 8, 2) компиляция с опцией -parameters и 3) имена параметров соответствуют JSON. Но это также возможно в GSON по умолчанию без всех этих ограничений.

Например:

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

Что отлично работает для GSON, но Джексон бросает:

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)

Это возможно в GSON, поэтому я ожидал, что в Джексоне должен быть какой-то способ без изменения класса Person, без Java 8 и без явного настраиваемого десериализатора. Кто-нибудь знает решение?

  • Обновление, дополнительная информация

Кажется, что Gson пропускает конструктор аргументов, поэтому он должен создавать конструктор без аргументов за кулисами, используя отражения.

Кроме того, существует модуль Kotlin Jackson, который может делать это для классов данных Kotlin. , даже без флага компилятора -parameters. Так что странно, что такого решения, похоже, не существует для Java Jackson.

Это (красивое и чистое) решение, доступное в Kotlin Jackson (которое IMO также должно стать доступным в Java Jackson через настраиваемый модуль):

val mapper = ObjectMapper()
    .enable(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES)
    .registerModule(KotlinModule())     
        
val person: Person = mapper.readValue(json, Person::class.java)

person Devabc    schedule 30.11.2017    source источник
comment
создать пустой конструктор для person   -  person Kalaiselvan    schedule 30.11.2017
comment
Но для этого нужно изменить класс. У меня вопрос, возможно ли это без его модификации, как с Gson. (Я уточню это в своем вопросе.)   -  person Devabc    schedule 30.11.2017
comment
Таким образом, вы не хотите аннотировать класс, вы не хотите использовать решение имен параметров и вам не нужен настраиваемый десериализатор. Что-нибудь еще, прежде чем я попытаюсь написать ответ?   -  person user1803551    schedule 30.11.2017
comment
Не совсем, я тоже не добавил новых требований к вопросу. Ситуация в основном такова: у вас есть большое количество неизменяемых классов вне вашей кодовой базы, как эффективно десериализовать их без шаблонного кода. Решение, вероятно, заключается в настраиваемом модуле Джексона, которого еще не существует, или в использовании флага -параметр. (Этот флаг громоздкий, потому что 1) он требует перекомпиляции и 2) IntelliJ игнорирует флаги компилятора Maven. Но я принял модуль Kotlin Jackson как решение моего проекта.   -  person Devabc    schedule 02.12.2017
comment
@Kalaiselvan, я поддержал ваш комментарий, так как он сработал для моего коллеги. Но я не предоставил конструктор по умолчанию / пустой, и я все еще мог его решить. Единственная разница заключалась в том, что я использовал Windows 10, а он использовал Mac.   -  person susenj    schedule 26.07.2019


Ответы (4)


Решение со смешанными аннотациями

Вы можете использовать смешанные аннотации. Это отличная альтернатива, когда изменение классов невозможно. Вы можете думать об этом как об аспектно-ориентированном способе добавления большего количества аннотаций во время выполнения, чтобы расширить статически определенные.

Предполагая, что ваш класс Person определен следующим образом:

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
}

Сначала определите абстрактный класс смешанной аннотации:

public abstract class PersonMixIn {

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

Затем настройте ObjectMapper, чтобы использовать определенный класс как добавку для вашего POJO:

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

И десериализуйте JSON:

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

Поскольку конструктора по умолчанию нет, jackson или gson хотят создать экземпляр самостоятельно. вы должны сообщить API, как создать такой экземпляр, предоставив настраиваемую десериализацию.

вот фрагмент кода

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

Затем зарегистрируйте простой модуль для обработки вашего типа

final ObjectMapper mapper = jacksonBuilder().build();
SimpleModule module = new SimpleModule();
module.addDeserializer(Person.class, new PersonDeserializer());
person adia    schedule 30.11.2017
comment
Код выдает ошибку Person person = (Person) mapper.readValue(node.toString(), Person.class);: Cannot construct instance of `jacksonParametersTest.Person` Также это решение для десериализатора, а я ищу его без него. Gson умеет это делать по умолчанию. Также модуль Kotlin Джексона может это делать: github.com/FasterXML/jackson-module-kotlin. Я также обнаружил, что Gson пропускает конструктор аргументов, поэтому он негласно создает ctor по умолчанию без аргументов. - person Devabc; 30.11.2017

Джексон предоставляет модуль jackson-modules-java8 для решить вашу проблему.

Вы должны построить свой ObjectMapper следующим образом:

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

Вы должны добавить -parameters в качестве аргумента компилятора.

Пример для 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>

Для градиента:

compileJava {
  options.compilerArgs << "-parameters"
}
person Beno Arakelyan    schedule 30.11.2017
comment
OP уже упоминал об этих параметрах и не хочет этого, возможно, они не запускают Java 8. - person user1803551; 30.11.2017
comment
@ user1803551 Он упоминает эту опцию для классов Kotlin. - person Beno Arakelyan; 30.11.2017
comment
@ user1803551 Он говорит. Странно, что такого решения не существует для Java Jackson. На мой взгляд, он ищет это решение. Джексон не поддерживает другой способ решения проблемы, если вы не хотите создавать собственный десериализатор. - person Beno Arakelyan; 30.11.2017
comment
Он говорит , поэтому я ожидал, что в Джексоне должен быть какой-то способ без изменения класса Person, без Java 8 и без явного настраиваемого десериализатора. Кто-нибудь знает решение? Для вашего решения требуется Java 8. - person user1803551; 30.11.2017
comment
Проблема с Java 8 заключается не в самой Java 8, а в необходимости использования опции -parameters. Решение требует 1) перекомпиляции классов (которые могут быть классами вне кодовой базы) и 2) IntelliJ игнорирует флаги компилятора maven. youtrack.jetbrains.com/v2/issue/IDEA-143742 - person Devabc; 02.12.2017
comment
@Devabc Я знаю, когда вы используете параметры -параметры, вы должны компилировать классы с помощью mvn compile, и если ваш класс находится за пределами, вы должны создать свой собственный deserializer или mixin - person Beno Arakelyan; 03.12.2017

Решение ниже работает, только если вы можете изменить реализацию класса

Простое решение - просто создать конструктор по умолчанию

Person() в Person классе

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

    public Person() {
    }

    public Person(String firstName, String lastName, int age) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.age = age;
    }
    ...
}
person Asad Shakeel    schedule 05.09.2019
comment
Что ж ... Это очевидное решение. Но упомянутый OP: без изменения класса - person cassiomolin; 05.09.2019