杰克逊反序列化绕过最终领域
Jackson deserialization circumventing final fields
这是代码
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.Data;
import lombok.ToString;
public class Main {
public static void main(String[] args) throws Exception {
Fields f1 = new Fields(1);
System.out.println(f1);
ObjectMapper mapper = new ObjectMapper();
String str = mapper.writeValueAsString(f1);
System.out.println(str);
Fields f2 = mapper.readValue(str, Fields.class);
System.out.println(f2);
}
@Data
@ToString
public static class Fields {
private final long value1;
private final long value2;
public Fields(@JsonProperty("blah") long value) {
this.value1 = value++;
this.value2 = value++;
System.out.println(this);
}
}
}
输出
Main.Fields(value1=1, value2=2)
Main.Fields(value1=1, value2=2)
{"value1":1,"value2":2}
Main.Fields(value1=0, value2=1)
Main.Fields(value1=1, value2=2)
我的问题是:
- 为什么jackson在构造完成后修改了没有setter的private final fields?如果这是有意的,我该如何关闭它?
- 如果jackson可以直接设置字段,为什么还需要我用@JsonProperty注解构造函数? (从 Fields 中删除 @JsonProperty 会导致错误;我什至不需要使用正确的属性进行注释)
谢谢
Why is it required that I annotate the constructor with @JsonProperty?
不是。需要的是一个可访问的构造函数。你可以有一个无参数的构造函数
public Fields() {
this.value1 = 0;
this.value2 = 0;
System.out.println("cons: " + this);
}
(这必然初始化字段,因为它们是 final
)或者你可以有一个构造函数,Jackson 将尝试根据声明的 @JsonProperty
名称解析。请注意 JsonProperty#required
默认为 false
。
Why did jackson modify private final fields that do not have setters
after finish constructing it? If this is intended, how do I turn it
off?
因为可以。因此,它允许您使用不可变类型进行反序列化。据我所知,没有任何内置方法可以禁用此功能。
Why did jackson modify private final fields that do not have setters after finish constructing it? If this is intended, how do I turn it off?
您可以在配置映射器时将 MapperFeature.ALLOW_FINAL_FIELDS_AS_MUTATORS
属性 设置为 false
(默认为 true
)。
示例:
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
public class Temp {
private static final String RAW = "{\"value1\": \"aabbcc\",\"value2\":\"zzzzz\"}";
public static void main(String[] args) throws Exception {
System.out.println(new ObjectMapper().readValue(RAW, TestClass.class));
ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); // you will receive UnrecognizedPropertyException without this line
mapper.configure(MapperFeature.ALLOW_FINAL_FIELDS_AS_MUTATORS, false);
System.out.println(mapper.readValue(RAW, TestClass.class));
}
public static class TestClass {
private final String value1;
private final String value2;
@JsonCreator
public static TestClass createTestClass(
@JsonProperty("value1") String value1,
@JsonProperty("blah") String value2) {
return new TestClass(value1, value2);
}
private TestClass(String value1, String value2) {
this.value1 = value1;
this.value2 = value2;
}
public String getValue1() {
return value1;
}
public String getValue2() {
return value2;
}
@Override
public String toString() {
return "TestClass{" + "value1=" + value1 + ", value2=" + value2 + '}';
}
}
}
输出:
TestClass{value1=aabbcc, value2=zzzzz}
TestClass{value1=aabbcc, value2=null}
这是代码
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.Data;
import lombok.ToString;
public class Main {
public static void main(String[] args) throws Exception {
Fields f1 = new Fields(1);
System.out.println(f1);
ObjectMapper mapper = new ObjectMapper();
String str = mapper.writeValueAsString(f1);
System.out.println(str);
Fields f2 = mapper.readValue(str, Fields.class);
System.out.println(f2);
}
@Data
@ToString
public static class Fields {
private final long value1;
private final long value2;
public Fields(@JsonProperty("blah") long value) {
this.value1 = value++;
this.value2 = value++;
System.out.println(this);
}
}
}
输出
Main.Fields(value1=1, value2=2)
Main.Fields(value1=1, value2=2)
{"value1":1,"value2":2}
Main.Fields(value1=0, value2=1)
Main.Fields(value1=1, value2=2)
我的问题是:
- 为什么jackson在构造完成后修改了没有setter的private final fields?如果这是有意的,我该如何关闭它?
- 如果jackson可以直接设置字段,为什么还需要我用@JsonProperty注解构造函数? (从 Fields 中删除 @JsonProperty 会导致错误;我什至不需要使用正确的属性进行注释)
谢谢
Why is it required that I annotate the constructor with @JsonProperty?
不是。需要的是一个可访问的构造函数。你可以有一个无参数的构造函数
public Fields() {
this.value1 = 0;
this.value2 = 0;
System.out.println("cons: " + this);
}
(这必然初始化字段,因为它们是 final
)或者你可以有一个构造函数,Jackson 将尝试根据声明的 @JsonProperty
名称解析。请注意 JsonProperty#required
默认为 false
。
Why did jackson modify private final fields that do not have setters after finish constructing it? If this is intended, how do I turn it off?
因为可以。因此,它允许您使用不可变类型进行反序列化。据我所知,没有任何内置方法可以禁用此功能。
Why did jackson modify private final fields that do not have setters after finish constructing it? If this is intended, how do I turn it off?
您可以在配置映射器时将 MapperFeature.ALLOW_FINAL_FIELDS_AS_MUTATORS
属性 设置为 false
(默认为 true
)。
示例:
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
public class Temp {
private static final String RAW = "{\"value1\": \"aabbcc\",\"value2\":\"zzzzz\"}";
public static void main(String[] args) throws Exception {
System.out.println(new ObjectMapper().readValue(RAW, TestClass.class));
ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); // you will receive UnrecognizedPropertyException without this line
mapper.configure(MapperFeature.ALLOW_FINAL_FIELDS_AS_MUTATORS, false);
System.out.println(mapper.readValue(RAW, TestClass.class));
}
public static class TestClass {
private final String value1;
private final String value2;
@JsonCreator
public static TestClass createTestClass(
@JsonProperty("value1") String value1,
@JsonProperty("blah") String value2) {
return new TestClass(value1, value2);
}
private TestClass(String value1, String value2) {
this.value1 = value1;
this.value2 = value2;
}
public String getValue1() {
return value1;
}
public String getValue2() {
return value2;
}
@Override
public String toString() {
return "TestClass{" + "value1=" + value1 + ", value2=" + value2 + '}';
}
}
}
输出:
TestClass{value1=aabbcc, value2=zzzzz}
TestClass{value1=aabbcc, value2=null}