在 JSON 这条物流线上,普通对象就像标准快递盒,按照规则打包和拆箱。但有些场景,我们需要 更自由、更聪明地打包和拆开。前两天写过一篇文章《Java 中 JSON 字段不固定怎么搞序列化?用好这两个注解就够了!》,这个操作是jackson中的注解,有些开发的项目并不使用Jackson,使用的是fastjson,今天我们聊聊 fastjson2 特供版的打包方法。
快递小哥的 JSON 魔法技能(Fastjson 特供版序列化)
1、@JSONField(unwrapped = true):把内部小件直接摊开打包
正常打包时,一个对象里面还有一个子对象,子对象会单独套一层盒子,比如:
{
"user": {
"name": "Tom",
"age": 18
}
}
但如果你加上了 @JSONField(unwrapped = true),就好比告诉快递员:
“别单独套盒了!直接把里面的小件摊在大箱子里!”
最终打包效果变成:
{
"name": "Tom",
"age": 18
}
简单说就是展开内嵌对象的字段,减少一层结构,常用于简化 API 返回格式。
现在我们还是使用这个 Person 类,想要在其中存储一些动态的字段。
这里我们使用Map存放未知字段,示例代码如下所示
public class PersonMap {
private String name;
private int age;
// 存储动态属性
// 通过@JSONField(unwrapped=true)实现动态属性扁平化序列化
@JSONField(unwrapped = true)
private Map<String, Object> additionalProperties = new HashMap<>();
public void addAdditionalProperty(String key, Object value) {
this.additionalProperties.put(key, value);
}
// 省略标准getter/setter(Fastjson2会默认序列化)
public static void main(String[] args) {
PersonMap person = new PersonMap();
person.setName("小豆1号");
person.setAge(18);
person.addAdditionalProperty("address", "123 Street");
person.addAdditionalProperty("nickname", "dadou");
// 使用Fastjson序列化
String json = JSON.toJSONString(person3);
// 输出:{"address":"123 Street","nickname":"dadou","age":18,"name":"xiaodou"}
System.out.println(json);
}
}
在这个例子中:
additionalProperties是一个Map,用来存储动态字段。- 我们通过
addAdditionalProperty()方法向additionalProperties中添加动态字段。 - 使用
JSON.toJSONString()方法序列化时,additionalProperties中的内容会作为额外的字段被序列化到 JSON 中。
就像最开始说的@JSONField(unwrapped = true)是展开属性,这里还可以处理对象,比如下面这种代码
public class PersonObject {
private String name;
private int age;
@JSONField(unwrapped = true)
private XiaoDou xiaoDou;
}
这里会有一个问题,假设XiaoDou和PersonObject拥有相同的属性,那这个属性会如何处理呢?会覆盖么?
完整的测试代码:点我打开
字段反序列化
@JSONType(deserializer = Class.class):指定专属开箱员
有些货物比较特别,普通快递小哥不会开(比如生鲜、艺术品)。这时候你需要给它指派专业开箱师傅!
在代码里,就是给类打上:
@JSONType(deserializer = PersonDeserializer.class)
告诉 Fastjson:
“这个对象(Person4)不能普通拆,要找指定的
Person4Deserializer来精细处理!”
适合复杂 JSON → Java 对象映射,比如字段名不一致、嵌套特别奇怪、需要自定义处理逻辑的场景。
在这里,我们可以通过自定义 PersonDeserializer 解析器来将不同的字段分别放入不同的 Map 中。具体做法是,检查每个动态字段的名称,根据名称或者其他规则来决定将它放到哪个 Map 中。示例代码:点我打开
@JSONType(deserializer = PersonDeserializer.class)
public class PersonDeserializerTest {
private String name;
private int age;
private Map<String, Object> additionalProperties = new HashMap<>();
// 这里省略标准getter/setter(Fastjson2会默认序列化)
public static void main(String[] args) {
String json = "{\"name\":\"xiaodou\",\"age\":18,\"address\":\"123 Street\",\"nickname\":\"xiaodou\"}";
// 使用Fastjson反序列化
PersonDeserializerTest person = JSON.parseObject(json, PersonDeserializerTest.class);
System.out.println("Name: " + person.name); // 输出:Name: John
System.out.println("Age: " + person.age); // 输出:Age: 30
System.out.println("Additional Properties: " + person.getAdditionalProperties());
}
}
// 自定义反序列化器
class PersonDeserializer implements ObjectReader<PersonDeserializerTest> {
@Override
public PersonDeserializerTest readObject(JSONReader jsonReader, Type fieldType, Object fieldName, long features) {
PersonDeserializerTest person = new PersonDeserializerTest();
if (jsonReader.nextIfObjectStart()) {
while (!jsonReader.nextIfObjectEnd()) {
String key = jsonReader.readFieldName();
switch (key) {
case "name":
person.setName(jsonReader.readString());
break;
case "age":
person.setAge(jsonReader.readInt32());
break;
default:
person.addAdditionalProperty(key, jsonReader.readAny());
}
}
}
return person;
}
}
在这个例子中:
- Fastjson 会自动将
address和nickname字段映射到additionalProperties中,而不需要显式声明这些字段。 JSON.parseObject()方法会自动识别 JSON 中的额外字段并将它们放入Map中。
输出:
Name: xiaodou
Age: 18
Additional Properties: {address=123 Street, nickname=xiaodou}
这里也可以换一种调用方式,点我打开源码
public static void main(String[] args) {
// 注册自定义Reader
ObjectReaderProvider provider = JSONFactory.getDefaultObjectReaderProvider();
provider.register(PersonDeserializerTest.class, new PersonDeserializer());
String json = "{\"name\":\"xiaodou\",\"age\":18,\"address\":\"123 Street\",\"nickname\":\"xiaodou\"}";
PersonDeserializerTest person = JSON.parseObject(json, PersonDeserializerTest.class);
System.out.println("Name: " + person.getName());
System.out.println("Age: " + person.getAge());
System.out.println("Additional Properties: " + person.getAdditionalProperties());
}
又想到一个问题,Dog、Cat都是Animal的子类,当我们用 fastjson 把一堆 Animal 类型的数据序列化成 JSON 时,JSON里本身是没有类型信息的,所以你反序列化回来只知道是 Animal,不知道是 Dog 还是 Cat,这时候该怎么办呢?