可以使用 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 来说效果很好,但是 Jackson 抛出了:
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)
您可以使用混合注释。当无法修改类时,这是一个很好的选择。您可以将其视为一种在运行时添加更多注释的面向方面的方式,以增强静态定义的注释。
假设你的
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
}
首先定义一个mix-in注解抽象类:
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);
Jackson 提供了模块 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>
对于 gradle:
compileJava {
options.compilerArgs << "-parameters"
}
由于没有默认构造函数,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());
这样,我们将在创建时初始化对象的所有字段。字段声明中的最终修饰符不会让我们将来更改它们的值。为了使这个对象可反序列化,我们只需要向这个构造函数添加几个注释:
@JsonCreator(mode = JsonCreator.Mode.PROPERTIES)
public Employee(@JsonProperty("id") long id, @JsonProperty("name") String name) {
this.id = id;
this.name = name;
}
让我们仔细看看我们刚刚添加的注释。
首先,@JsonCreator告诉Jackson反序列化器使用指定的构造函数进行反序列化。
有两种模式可以用作此注释的参数 - PROPERTIES 和 DELEGATING。 当我们声明全参数构造函数时,PROPERTIES 是最合适的,而 DELEGATING 对于单参数构造函数可能很有用。
之后,我们需要使用 @JsonProperty 注释每个构造函数参数,将各个属性的名称指定为注释值。我们在这一步应该非常小心,因为所有属性名称都必须与我们在序列化期间使用的属性名称匹配。
仅当您可以更改类实现时,以下解决方案才有效
一个简单的解决方案就是创建一个默认构造函数
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;
}
...
}