又被 fastjson 坑了?它调用了我自定义的 get 方法!

一、背景

最近看到又有同学被 fastjson 坑了。
该同学在类中自定义了 get 方法,在该 get 方法中引用了一个对象,由于某段代码中 “没有用到”该方法就没注入,最后出现了空指针。
由于自己确定没有主动调用这个方法,排查了半天,借助 arthas 看 trace 才发现这个坑。

二、问题复现

@Data
public class Student {

    private String name;

    public String getValue() {
        return "test";
    }
}

测试:

public class StudentDemo {
    public static void main(String[] args) {
        Student student = new Student();
        student.setName("Student");

        System.out.println(JSON.toJSONString(student));
    }
}

结果是:{“name”:“Student”,“value”:“test”}

可见 fastjson 的 toJSONString 方法转 JSON 时,底层是通过解析 get 方法来识别属性的,它认为有一个 value 属性,转为 JSON 字符串时会自动调用对应的 get 方法获取 value 属性的值。

如果自定义的 get 方法中使用到了尚没有设置的对象,由于并没有显示调用 getAddress 方法,很多人并不会意识到需要注入 repository 对象,如果调用了 toJSONString 方法就极容易出现空指针异常。

@Data
public class Student {

    private String name;

    private String addressId;

    private AddressRepository repository;


    // 省略其他
    public Address getAddress() {
        return repository.get(addressId);
    }
}
public class StudentDemo {
    public static void main(String[] args) {
        Student student = new Student();
        student.setName("Student");
        student.setAddressId("10086");

        // 很多人并不会意识到这里会自动调用 getAddress 方法,因此没有设置  repository,空指针了!
        log.info("过程中某个日志, 参数:{}", JSON.toJSONString(student));
    }
}

三、如何解决

方法一:自定义的方法避免定义为 get 开头。

import com.alibaba.fastjson.annotation.JSONField;
import lombok.Data;

@Data
public class Student {

    private String name;


    public String fetchValue() {
        return "test";
    }
}

方法二:使用 @JSONField(serialize = false) 在 getValue 方法上,让 fastjson 忽略该方法。

import com.alibaba.fastjson.annotation.JSONField;
import lombok.Data;

@Data
public class Student {

    private String name;

    @JSONField(serialize = false)
    public String getValue() {
        return "test";
    }
}

四、启发

大家在进行项目开发时,当你发现对象转 JSON 字符串时“莫名其妙地”多出了某些属性,其实就是这个原因。
大家在使用 fastjson 将对象转为 JSON 字符串时一定要小心这个坑。

对于枚举的自定义序列化和反序列化, Fastjson 提供了几种方式: 1. 实现 `com.alibaba.fastjson.parser.deserializer.ExtraProcessor` 接口来自定义反序列化逻辑,其中 `processExtra` 方法可以用于处理枚举字段的反序列化。 2. 在枚举类中定义一个静态的 `valueOf` 方法,用于根据枚举值的字符串表示返回相应的枚举对象。Fastjson 反序列化时会自动调用方法。 3. 使用注解 `@JSONType` 来指定一个 `deserializer` 类,该类需要实现 `com.alibaba.fastjson.parser.deserializer.ObjectDeserializer` 接口,并在 `deserializer` 类中实现自定义的反序列化逻辑。 以下是一个示例代码,展示了如何使用上述方式来自定义枚举的序列化和反序列化方法: ```java import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.parser.DefaultJSONParser; import com.alibaba.fastjson.parser.deserializer.ExtraProcessor; import com.alibaba.fastjson.parser.deserializer.ObjectDeserializer; import com.alibaba.fastjson.parser.deserializer.ParseProcess; import com.alibaba.fastjson.parser.deserializer.ParseContext; import com.alibaba.fastjson.serializer.JSONSerializable; import com.alibaba.fastjson.serializer.JSONSerializer; import com.alibaba.fastjson.serializer.ObjectSerializer; import com.alibaba.fastjson.serializer.SerializeConfig; import com.alibaba.fastjson.serializer.SerializerFeature; import java.io.IOException; import java.lang.reflect.Type; import java.util.HashMap; import java.util.Map; public class EnumSerializationExample { public static void main(String[] args) { SerializeConfig serializeConfig = new SerializeConfig(); serializeConfig.put(TestEnum.class, new TestEnumSerializer()); // 序列化 String jsonString = JSON.toJSONString(TestEnum.VALUE1, serializeConfig); System.out.println("Serialized Enum: " + jsonString); // 反序列化 TestEnum deserializedEnum = JSON.parseObject(jsonString, TestEnum.class, new TestEnumDeserializer()); System.out.println("Deserialized Enum: " + deserializedEnum); } public static class TestEnumSerializer implements ObjectSerializer { @Override public void write(JSONSerializer serializer, Object object, Object fieldName, Type fieldType, int features) throws IOException { if (object instanceof TestEnum) { serializer.write(((TestEnum) object).getValue()); } } } public static class TestEnumDeserializer implements ObjectDeserializer, ExtraProcessor { private Map<String, TestEnum> valueMap = new HashMap<>(); public TestEnumDeserializer() { for (TestEnum value : TestEnum.values()) { valueMap.put(value.getValue(), value); } } @Override public <T> T deserialze(DefaultJSONParser parser, Type type, Object fieldName) { String value = parser.parseObject(String.class); return (T) valueMap.get(value); } @Override public int getFastMatchToken() { return 0; } @Override public void processExtra(Object object, String key, Object value) { if (object instanceof TestEnum) { ((TestEnum) object).setValue((String) value); } } } public enum TestEnum { VALUE1("Value 1"), VALUE2("Value 2"); private String value; TestEnum(String value) { this.value = value; } public String getValue() { return value; } public void setValue(String value) { this.value = value; } } } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

明明如月学长

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值