Jackson,使用私有字段和 arg 构造函数反序列化类,不带注释

2022-09-02 13:49:46

是否可以反序列化为具有私有字段和自定义参数构造函数的类,而无需使用注释,也无需修改类,使用 Jackson?

我知道在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中是可能的,所以我期望在Jackson中必须有某种方法,而无需修改Person类,没有Java 8,也没有显式的自定义反序列化程序。有人知道解决方案吗?

  • 更新,其他信息

Gson 似乎跳过了参数构造函数,因此它必须在幕后使用反射创建一个无参数构造函数。

此外,还有一个Kotlin Jackson模块,即使没有“-parameters”编译器标志,它也能为Kotlin数据类执行此操作。因此,奇怪的是,对于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)

答案 1

具有混合注释的解决方案

您可以使用混合注记。当修改类不是一个选项时,这是一个很好的选择。您可以将其视为一种面向方面的方式,在运行时添加更多注释,以增强静态定义的注释。

假设您的类定义如下: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) {
    }
}

然后配置使用定义的类作为 POJO 的混音:ObjectMapper

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

答案 2

由于没有默认构造函数,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());