java record class feature
使用 jackson 反序列化 record 类型遇到的问题
定义一个 record 类
包含两个组件(字段), 一个是基本类型 age, 一个是集合类型 nickNames。
public record Person(int age, List<String> nickNames) {
}
上述代码没有声明构造器,java 编译器会自动生成一个标准构造器,参数是 age 和 nickNames。
public record Person(int age, List<String> nickNames) {
// java 编译器会自动生成一个标准构造器
public Person(int age, List<String> nickNames) {
this.age = age;
this.nickNames = nickNames;
}
}
通过 jackson 反序列化 json 字符串到 Person 类型对象。
{
"age": 18,
"nick_names": [
"Tom",
"Jerry"
]
}
问题 1
通过 jackson 反序列化 json 字符串到 Person 类型对象时,遇到如下异常。
public record Person(int age, List<String> nickNames) {
public static void main(String[] args) throws Exception {
ObjectMapper mapper = new ObjectMapper();
Person person = mapper.readValue("""
{
"age": 18,
"nick_names": ["Tom", "Jerry"]
}
""", Person.class);
System.out.println(mapper.writeValueAsString(person));
}
}
Exception in thread "main" com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "nick_names"
这是因为 jackson 默认使用驼峰命名法,而 json 字符串中的字段是下划线命名法,所以需要在 record 类中使用 @JsonProperty 注解。
public record Person(int age, @JsonProperty("nick_names") List<String> nickNames) {
public static void main(String[] args) throws Exception {
ObjectMapper mapper = new ObjectMapper();
Person person = mapper.readValue("""
{
"age": 18,
"nick_names": ["Tom", "Jerry"]
}
""", Person.class);
System.out.println(mapper.writeValueAsString(person));
}
}
同样会生成一个标准构造器。
public record Person(int age, @JsonProperty("nick_names") List<String> nickNames) {
// java 编译器会自动生成一个标准构造器
public Person(int age, @JsonProperty("nick_names") List<String> nickNames) {
this.age = age;
this.nickNames = nickNames;
}
}
对 nickNames 字段进行校验
使用简化构造器(Compact Constructor)对 nickNames 字段进行校验,如果为 null,则初始化为一个空的集合。 这种构造器可以省略构造器参数列表,直接在构造器主体中编写逻辑。编译器会自动推断出所有定义的字段,并进行赋值。
public record Person(int age, @JsonProperty("nick_names") List<String> nickNames) {
// 使用简化构造器对 nickNames 字段进行校验
public Person {
if (Objects.isNull(nickNames)) {
nickNames = List.of();
}
}
public static void main(String[] args) throws Exception {
ObjectMapper mapper = new ObjectMapper();
Person person = mapper.readValue("""
{
"age": 18,
"nick_names": ["Tom", "Jerry"]
}
""", Person.class);
System.out.println(mapper.writeValueAsString(person));
}
}
上述简化构造编译后等价于下面代码实现
public record Person(int age, @JsonProperty("nick_names") List<String> nickNames) {
// 上述简化构造编译后等价于下面的标准构造器
public Person(int age, @JsonProperty("nick_names") List<String> nickNames) {
if (Objects.isNull(nickNames)) {
nickNames = List.of();
}
this.age = age;
this.nickNames = nickNames;
}
}
同样我们也开始手动实现一个标准构造器,标准构造器。
public record Person(int age, @JsonProperty("nick_names") List<String> nickNames) {
// 手动实现标准构造器,对 nickNames 字段进行校验
public Person(int age, @JsonProperty("nick_names") List<String> nickNames) {
this.age = age;
this.nickNames = Objects.isNull(nickNames) ? List.of() : nickNames;
}
public static void main(String[] args) throws Exception {
ObjectMapper mapper = new ObjectMapper();
Person person = mapper.readValue("""
{
"age": 18,
"nick_names": ["Tom", "Jerry"]
}
""", Person.class);
System.out.println(mapper.writeValueAsString(person));
}
}
需要注意的是,手动实现的标准构造器的 nickNames 字段必须用 @JsonProperty("nick_names") 修饰。不然会出现一下错误。
public record Person(int age, @JsonProperty("nick_names") List<String> nickNames) {
// nickNames 字段没有使用 @JsonProperty("nick_names") 修饰,导致反序列化失败
public Person(int age, List<String> nickNames) {
this.age = age;
this.nickNames = Objects.isNull(nickNames) ? List.of() : nickNames;
}
public static void main(String[] args) throws Exception {
ObjectMapper mapper = new ObjectMapper();
Person person = mapper.readValue("""
{
"age": 18,
"nick_names": ["Tom", "Jerry"]
}
""", Person.class);
System.out.println(mapper.writeValueAsString(person));
}
}
Exception in thread "main" com.fasterxml.jackson.databind.JsonMappingException: Can not set final java.util.List field com.wosai.crm.qa.Person.nickNames to java.util.ArrayList
这是因为 jackson 无法通过构造器完成 Person 对象的反序列化,构造器参数 nickNames 无法和 nick_names 对应,然后使用反射机制直接修改了 nickNames 字段,而 nickNames 字段是 final 的,所以会抛出异常。
问题 2
如果使用普通类代替 record 类,需要使用 @JsonCreator 修改构造器和使用 @JsonProperty 修饰构造器参数。
public class Person {
@JsonProperty("age")
private final Long age;
@JsonProperty("nick_names")
private final List<String> nickNames;
// 使用 @JsonCreator 修改构造器和使用 @JsonProperty 修饰构造器参数。
@JsonCreator
public Person(@JsonProperty("age") Long age, @JsonProperty("nick_names") List<String> nickNames) {
this.age = age;
this.nickNames = nickNames;
}
public static void main(String[] args) throws JsonProcessingException {
ObjectMapper mapper = new ObjectMapper();
Person metaData = mapper.readValue("""
{
"age": 18,
"nick_names": ["Tom", "Jerry"]
}
""", Person.class);
System.out.println(mapper.writeValueAsString(metaData));
}
}