1. 从一次“诡异”的JSON解析说起
最近在重构一个老项目的接口时,遇到了一个让我调试了半天的“灵异事件”。接口返回的JSON数据里有一个字段,它的值是一个数组,但数组里的元素类型却不固定,可能是字符串,也可能是数字,甚至嵌套着对象。用fastjson2的JSON.parseObject一把梭解析成List<Object>后,后续的业务逻辑处理起来简直是灾难——到处都是instanceof判断和强制类型转换,代码又臭又长,还容易出错。更头疼的是,当我想把这个List再序列化成JSON传给下游时,发现某些数字被错误地写成了带引号的字符串,下游系统直接报类型错误。这让我意识到,对于这种包含异构类型的List,通用的反序列化策略已经不够用了,必须祭出fastjson2的终极武器之一:自定义ObjectReader。
fastjson2作为阿里开源的高性能JSON库,其2.x版本在性能和API设计上都有了长足进步。它提供了ObjectReader和ObjectWriter这两个核心接口,允许我们对特定类型的序列化与反序列化过程进行深度定制。今天,我们就来深入探讨如何利用ObjectReader,优雅地解决“包含不同类型元素的List”的反序列化难题。无论你是正在处理复杂的历史数据格式,还是在设计需要高度灵活性的新接口,这套方法都能让你对JSON数据的掌控力提升一个档次。
2. 为什么通用解析器在异构List面前“失灵”了?
在动手写代码之前,我们得先搞清楚问题出在哪。为什么标准的JSON.parseObject(jsonString, new TypeReference<List<Object>>() {})会让我们陷入困境?
2.1 标准反序列化的“黑盒”逻辑
当我们调用JSON.parseObject去解析一个数组时,fastjson2内置的默认ObjectReader会按照一套既定的规则工作。对于形如[“hello”, 123, {“key”: “value”}]的JSON数组,解析器会遍历每个元素:
- 遇到
“hello”(JSON字符串),它将其解析为Java的String类型。 - 遇到
123(JSON数字),它将其解析为Integer类型(如果数字在int范围内)。 - 遇到
{“key”: “value”}(JSON对象),它将其解析为一个JSONObject(默认的Map实现)或根据上下文尝试反序列化为某个具体的Java Bean。
最终,我们得到一个List<Object>,里面的元素可能是String、Integer、JSONObject等各种类型的混合体。从功能上讲,解析器完成了它的工作——它没有丢失任何数据。但问题在于,它把类型判定的责任完全抛给了后续的业务代码。
2.2 业务代码中的类型处理噩梦
拿到一个List<Object>后,如果你想安全地使用其中的数据,代码会变得非常冗余:
List<Object> mixedList = JSON.parseObject(jsonStr, new TypeReference<List<Object>>() {}); for (Object item : mixedList) { if (item instanceof String) { String str = (String) item; // 处理字符串逻辑 } else if (item instanceof Integer) { Integer num = (Integer) item; // 处理整数逻辑 } else if (item instanceof JSONObject) { JSONObject obj = (JSONObject) item; // 尝试从obj中获取字段,可能还需要进一步判断 Object maybeNested = obj.get(“key”); // 继续instanceof... } // ... 更多类型判断 }这种代码不仅难以阅读和维护,而且性能上也有损耗(频繁的instanceof和类型转换)。更重要的是,它完全丧失了编译期的类型安全检查。如果你错误地认为某个位置一定是数字而做了强制转换,运行时就会抛出ClassCastException。
2.3 序列化时的“二次伤害”
当你处理完数据,需要将这个List再序列化成JSON返回时,另一个问题出现了。fastjson2的默认序列化器(ObjectWriter)会根据对象的运行时类型来决定如何输出。但是,由于历史原因或某些框架的默认行为,一个原本是Integer的值,可能在某个环节被存储为String类型(比如从Map<String, Object>中取出后又放入)。当序列化时,fastjson2看到它是一个String对象,就会给它加上双引号。于是,下游系统收到了[“hello”, “123”, {…}],那个123变成了字符串,导致下游的类型校验失败。
问题的根源在于,标准的解析过程只完成了“语法解析”,没有完成“语义绑定”。我们需要一个机制,能在解析JSON的那一刻,就根据数据的内容、结构或位置,将其绑定到我们期望的、语义更明确的Java类型上。这就是自定义ObjectReader的用武之地。
3. 深入ObjectReader:定制反序列化的核心引擎
ObjectReader是fastjson2中用于反序列化的核心接口。你可以把它想象成一个高度可定制的“翻译官”,它的职责就是将JSON输入流(JSONReader)中的令牌(Tokens)转换成一个具体的Java对象。
3.1 ObjectReader的核心方法与生命周期
要自定义一个ObjectReader,通常我们不会直接实现这个接口的所有方法,而是继承ObjectReaderAdapter或使用相关的建造者。但理解其核心方法有助于我们把握定制点:
T readObject(JSONReader jsonReader, Type fieldType, Object fieldName, long features):这是最主要的反序列化方法。JSONReader是fastjson2的JSON解析器,它以一个流的方式读取JSON内容。fieldType是我们期望的目标类型(例如List.class)。fieldName是当前字段名(在对象中),对于根元素或数组元素可能为null。features是一组特性标志,可以影响解析行为。createInstance(JSONReader jsonReader, Type fieldType, Object fieldName, long features):用于创建目标对象的实例。对于List,通常就是创建一个ArrayList或你指定的其他实现。- 其他方法如
getObjectClass、getFeatures等用于获取元信息。
fastjson2在反序列化时的大致流程是:
- 根据目标类型(如
List<MyItem>)查找或创建对应的ObjectReader。 - 调用该
ObjectReader的readObject方法。 readObject方法内部通过JSONReader读取令牌。对于数组,会读到[,然后循环读取数组元素,直到遇到]。- 对于数组中的每一个元素,需要再次调用某个
ObjectReader来解析该元素。关键就在这里:我们可以控制用哪个ObjectReader来解析数组内的元素。
3.2 为List元素指定自定义反序列化策略
我们的目标是为一个List定制反序列化,但真正的定制点在于List中的元素。fastjson2提供了几种方式来实现这一点:
- 通过
@JSONType注解指定deserializer:如果你能控制元素类型的类,可以在类上使用@JSONType(deserializer = MyCustomDeserializer.class)。但这要求所有元素都是同一类型,且该类型已知,不适合我们这种元素类型不确定的场景。 - 使用
JSON.register()全局注册:可以为特定的Type全局注册一个ObjectReader。但这是全局生效的,可能会影响其他地方对该类型的解析,需要谨慎使用。 - 在调用
parseObject时,通过Context或Reader特性临时指定:这是最灵活、也是我们接下来要重点介绍的方式。我们可以创建一个自定义的ObjectReader,在它解析List内部元素时,根据运行时读到的JSON内容动态决定如何解析。
注意:在
fastjson2中,deserializer(反序列化器)的概念通常就是通过实现ObjectReader或相关接口来完成的。@JSONType注解中的deserializer属性需要的是一个ObjectReader的实现类。
4. 实战:构建一个智能的异构List解析器
理论说再多不如一行代码。让我们来设计一个具体的场景并实现它。假设我们有一个数据采集系统,它上报的事件列表(events)格式如下:每个事件必须有一个type字段标识类型,根据type的不同,其他字段的结构也不同。
目标JSON示例:
{ “events”: [ {“type”: “click”, “timestamp”: 1678886400000, “x”: 150, “y”: 300}, {“type”: “pageview”, “timestamp”: 1678886401000, “url”: “/home”, “duration”: 45}, {“type”: “error”, “timestamp”: 1678886402000, “message”: “NullPointerException”, “level”: “high”} ] }我们希望反序列化到一个List<BaseEvent>里,其中BaseEvent是基类,ClickEvent、PageViewEvent、ErrorEvent是子类。
4.1 第一步:定义数据模型(Java Beans)
首先,定义我们的类结构。这里使用一个简单的继承体系,并用type字段作为类型鉴别器。
// 基类,包含公共字段 abstract class BaseEvent { private String type; private long timestamp; // getters and setters } // 点击事件 class ClickEvent extends BaseEvent { private int x; private int y; // getters and setters // 可以添加一个无参构造器,fastjson2反射时需要 public ClickEvent() { this.setType(“click”); // 可以在这里设置,或者由反序列化器设置 } } // 页面浏览事件 class PageViewEvent extends BaseEvent { private String url; private int duration; // 秒 // getters and setters public PageViewEvent() { this.setType(“pageview”); } } // 错误事件 class ErrorEvent extends BaseEvent { private String message; private String level; // getters and setters public ErrorEvent() { this.setType(“error”); } }4.2 第二步:创建自定义的ObjectReader
这是最核心的一步。我们将创建一个ObjectReader,专门用于反序列化List<BaseEvent>。它的智能之处在于,在解析数组的每个元素(JSON对象)时,会先窥探(peek)这个对象的type字段,然后决定用哪个具体的子类ObjectReader来解析整个对象。
import com.alibaba.fastjson2.JSONReader; import com.alibaba.fastjson2.reader.ObjectReader; import com.alibaba.fastjson2.reader.ObjectReaders; import com.alibaba.fastjson2.util.TypeUtils; import java.lang.reflect.Type; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; public class PolymorphicEventListReader implements ObjectReader<List<BaseEvent>> { // 缓存不同类型对应的ObjectReader,避免重复创建 private static final Map<String, ObjectReader<? extends BaseEvent>> READER_CACHE = new ConcurrentHashMap<>(); static { // 预先注册已知类型的Reader。这里使用fastjson2提供的ObjectReaders.of创建。 // 注意:ObjectReaders.of(Class) 创建的是基于反射/setter的默认Reader,对于简单Bean够用。 READER_CACHE.put(“click”, ObjectReaders.of(ClickEvent.class)); READER_CACHE.put(“pageview”, ObjectReaders.of(PageViewEvent.class)); READER_CACHE.put(“error”, ObjectReaders.of(ErrorEvent.class)); } @Override public List<BaseEvent> readObject(JSONReader jsonReader, Type fieldType, Object fieldName, long features) { // 1. 创建一个空的List作为结果 List<BaseEvent> list = new ArrayList<>(); // 2. 检查JSONReader当前读取的是否是数组起始符‘[’ if (jsonReader.nextIfArrayStart()) { // fastjson2 的写法,判断并消费‘[’ // 3. 循环读取,直到遇到数组结束符‘]’ while (!jsonReader.nextIfArrayEnd()) { // 4. 关键:读取单个事件对象 BaseEvent event = readSingleEvent(jsonReader); if (event != null) { list.add(event); } // 5. 检查下一个token是否是‘,’(数组元素分隔符),如果是则跳过 jsonReader.nextIfComma(); } } else { // 如果不是数组,根据实际情况处理,比如抛出异常或返回null jsonReader.skipValue(); // 跳过当前值 return null; } return list; } private BaseEvent readSingleEvent(JSONReader jsonReader) { // 方法一:使用“窥探”模式(Peek)。先读取整个对象到一个临时的Map中。 // 注意:这种方法会完整解析一次JSON到Map,然后再转换到目标对象,有额外开销。 // 但对于结构不固定、需要根据字段决定类型的场景,逻辑清晰。 // 1. 将当前JSON对象读取到Map<String, Object>中。 // jsonReader.startObject() 和 jsonReader.read(Map.class) 可以配合使用。 // 更直接的方式:使用JSONReader的read方法,指定类型为Map。 // 但需要注意,read方法会移动读取位置。我们这里用另一种方式:使用parseObject的变体。 // 为了教学清晰,我们采用一个更直观的方法:使用JSONReader的“特性”来原地解析。 // 假设jsonReader现在指向一个对象的开始‘{‘。 if (!jsonReader.nextIfObjectStart()) { // 判断并消费‘{‘ jsonReader.skipValue(); return null; } String eventType = null; // 我们需要先读取前几个键值对,直到找到“type”。一种方法是使用一个临时的JSONObject。 // 在fastjson2中,我们可以用JSONReader直接解析成Map。 // 这里演示一个简化逻辑:我们手动读取键值对。 // 注意:这不是最高效的方式,但易于理解。生产环境可以考虑更优化的流式解析。 // 创建一个临时Map来存储这个对象的所有键值对 Map<String, Object> tempMap = new HashMap<>(); while (!jsonReader.nextIfObjectEnd()) { // 循环直到遇到‘}’ String key = jsonReader.readString(); // 读取key jsonReader.nextIfColon(); // 跳过‘:’ Object value; // 根据值的类型读取,这里做简单处理,全部用readAny读取为通用对象 value = jsonReader.readAny(); tempMap.put(key, value); jsonReader.nextIfComma(); // 跳过‘,’ } // 从临时Map中获取type eventType = (String) tempMap.get(“type”); if (eventType == null) { // 没有type字段,无法处理,可以记录日志或抛出异常 return null; } // 根据type获取对应的ObjectReader ObjectReader<? extends BaseEvent> eventReader = READER_CACHE.get(eventType); if (eventReader == null) { // 未知类型,可以按默认方式处理(如解析为BaseEvent)或忽略 // 这里我们尝试用BaseEvent的Reader来解析(但需要先把Map转成JSON字符串再解析,比较绕) // 更简单的做法:创建一个只包含通用字段的BaseEvent对象 BaseEvent base = new BaseEvent() {}; // 匿名子类 base.setType(eventType); base.setTimestamp(((Number)tempMap.getOrDefault(“timestamp”, 0L)).longValue()); // 其他字段无法设置 return base; } // 将临时Map转换回JSON字符串,然后用对应的Reader解析。 // 注意:这种方式有性能损耗(多了一次序列化和解析)。 // 优化方案:可以定制一个更复杂的Reader,在第一次遍历Map时就直接构造目标对象。 String singleEventJson = JSON.toJSONString(tempMap); JSONReader singleReader = JSONReader.of(singleEventJson); return eventReader.readObject(singleReader, null, null, 0); } }上面的实现为了逻辑清晰,采用了“先读Map,再根据type二次解析”的方式,这在性能上不是最优的。但它清晰地展示了核心思路:在解析过程中动态决策。
4.3 第三步:性能优化与流式解析
生产环境中,我们需要避免将整个对象读成Map再转字符串的额外开销。我们可以实现一个更高效的版本,在JSONReader流式解析的过程中,一旦识别出type字段,就“切换”到对应的子类ObjectReader继续解析剩余字段。
这需要更深入地使用JSONReader的API,实现一个能“预读”并“传递”控制权的逻辑。这里给出一个优化思路的伪代码:
private BaseEvent readSingleEventEfficiently(JSONReader jsonReader) throws Exception { if (!jsonReader.nextIfObjectStart()) { return null; } // 偷看第一个字段名,假设第一个字段就是“type” // JSONReader提供了peek()等方法,但直接操作token流更复杂。 // 另一种思路:使用fastjson2的Feature.SupportAutoType结合@JSONType注解。 // 但这里我们演示手动流式解析。 // 1. 创建一个字符缓冲区或临时结构,记录我们读取的内容。 // 2. 读取第一个key。 String firstKey = jsonReader.readFieldName(); if (!“type”.equals(firstKey)) { // 如果不是type开头,回退并采用默认策略,这里简化处理为跳过 jsonReader.skipValue(); return readRemainingAsMap(jsonReader); // 辅助方法,读取剩余部分为Map } jsonReader.nextIfColon(); String eventType = jsonReader.readString(); // 3. 获取对应的子类Reader ObjectReader<? extends BaseEvent> eventReader = READER_CACHE.get(eventType); if (eventReader == null) { // 读取剩余部分到Map,构建一个通用的BaseEvent return buildGenericEvent(eventType, jsonReader); } // 4. 关键:我们需要“欺骗”子类的Reader,让它从当前位置开始解析一个完整的对象。 // 但是,我们已经消费了“type”字段。子类的Reader在解析时可能也需要这个字段。 // 方案A:将“type”字段塞回jsonReader的缓冲区(很难实现)。 // 方案B:为每个子类创建自定义的Reader,这个Reader知道“type”字段已经被消费,跳过它。 // 方案C(推荐):使用fastjson2的“ExtraProcessor”或“FieldReader”机制,在创建ObjectReader时指定忽略或处理特定字段。 // 这里展示方案C的思路:我们提前为每个子类创建好定制的ObjectReader。 // 在创建时,通过ObjectReaders.of(Class)创建后,可以获取其内部的FieldReaders。 // 但直接操作较为复杂。更实用的生产级方案是使用@JSONCreator构造函数或工厂方法。 // 鉴于复杂度,对于性能要求极高的场景,可以退而求其次,使用第一次实现的“Map中转”方案,并配合缓存优化。 // 或者,如果数据结构固定,可以约定“type”必须是第一个字段,然后实现一个混合解析器。 // 作为折中,我们实现一个简化版:读取剩余的所有键值对到一个Map,然后利用fastjson2的TypeUtils.cast将Map转换为目标对象。 // TypeUtils.cast内部会使用目标类型的默认反序列化逻辑,能处理setter方法。 Map<String, Object> remainingMap = new HashMap<>(); while (!jsonReader.nextIfObjectEnd()) { String key = jsonReader.readFieldName(); jsonReader.nextIfColon(); Object value = jsonReader.readAny(); remainingMap.put(key, value); jsonReader.nextIfComma(); } // 将之前读到的type也放回去 remainingMap.put(“type”, eventType); // 使用TypeUtils进行转换,这比转成JSON字符串再解析效率高一些 return (BaseEvent) TypeUtils.cast(remainingMap, eventReader.getObjectClass(), null); }这个优化版本避免了完整的JSON字符串转换,直接使用Map和TypeUtils.cast,性能更好。但它依然依赖于反射。对于极致性能,可以考虑使用fastjson2的ObjectReader创建器(ObjectReaders.of)结合JSONReader的原始API,手动为每个字段绑定读取逻辑。
4.4 第四步:注册并使用自定义Reader
创建好PolymorphicEventListReader后,我们需要在反序列化时使用它。有几种方式:
方式一:在parseObject时通过TypeReference指定(推荐,作用域清晰)
import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.TypeReference; // 定义一个包装类,用于接收JSON class EventReport { private List<BaseEvent> events; // getter and setter } public class Main { public static void main(String[] args) { String jsonStr = “…” // 你的JSON字符串 // 创建我们自定义的Reader ObjectReader<List<BaseEvent>> customReader = new PolymorphicEventListReader(); // 使用JSON.parseObject的重载方法,传入自定义Reader // 注意:这里需要指定List<BaseEvent>的类型 TypeReference<List<BaseEvent>> typeRef = new TypeReference<List<BaseEvent>>() {}; // 但是,parseObject方法没有直接接受ObjectReader参数来解析根对象的版本。 // 我们需要为整个EventReport类定制反序列化,或者只解析events字段。 // 方案A:先解析出events字段的字符串,再用自定义Reader解析。 // 方案B:为EventReport类创建一个自定义的ObjectReader,在其内部对events字段使用我们的PolymorphicEventListReader。 // 更简单的方案:直接解析整个JSON对象,然后手动处理events字段。 JSONReader reader = JSONReader.of(jsonStr); reader.startObject(); while (!reader.nextIfObjectEnd()) { String fieldName = reader.readFieldName(); if (“events”.equals(fieldName)) { reader.nextIfColon(); // 使用自定义Reader解析这个字段的值 List<BaseEvent> events = customReader.readObject(reader, new TypeReference<List<BaseEvent>>(){}.getType(), null, 0); // … 处理events System.out.println(events); } else { reader.skipValue(); } } } }方式二:使用JSON.register()全局注册(谨慎使用)
// 注册List<BaseEvent>类型使用我们的自定义Reader JSON.register(new TypeReference<List<BaseEvent>>(){}.getType(), new PolymorphicEventListReader()); // 之后,所有对List<BaseEvent>的解析都会自动使用这个Reader EventReport report = JSON.parseObject(jsonStr, EventReport.class); // 此时report.getEvents()已经是通过我们的自定义Reader解析出来的多态列表了。全局注册会影响整个应用内所有对该类型的反序列化,务必确保这是你想要的行为,并且注意线程安全。
方式三:结合@JSONField注解(需fastjson2版本支持)
某些版本允许通过@JSONField(deserializeUsing = …)在字段级别指定反序列化器。你需要查看当前使用的fastjson2版本是否支持此功能,以及是否支持用于集合类型。
5. 避坑指南与进阶技巧
在实际使用自定义ObjectReader处理异构List时,我踩过不少坑,这里总结几个关键点:
5.1 类型鉴别器字段的位置与名称
在上面的例子中,我们假设了type字段作为鉴别器。但在实际JSON中,这个字段可能不叫type,也可能是eventType、_t等。我们的ObjectReader需要能够灵活地定位这个字段。一种更健壮的做法是将鉴别器的字段名作为构造参数传入:
public class PolymorphicEventListReader implements ObjectReader<List<BaseEvent>> { private final String typeFieldName; public PolymorphicEventListReader(String typeFieldName) { this.typeFieldName = typeFieldName; } // 在readSingleEvent方法中,使用this.typeFieldName去Map中查找 }另外,鉴别器字段不一定在对象的第一个位置。我们的流式解析优化版本假设它是第一个字段,这很脆弱。更通用的做法是完整遍历一次对象的所有键值对,找到鉴别器字段后再做决定。虽然这可能意味着需要缓存已读的键值对(就像我们第一个实现那样),但对于大多数应用,这部分开销是可以接受的。
5.2 处理未知类型与向后兼容
系统迭代中,可能会新增事件类型。我们的READER_CACHE如果写死在静态代码块里,就需要重新发布代码。更好的做法是设计成可扩展的,例如结合Spring的ApplicationContext,从容器中自动发现所有BaseEvent的子类并注册其ObjectReader。或者,至少提供一个公共的注册方法:
public class PolymorphicEventListReader implements ObjectReader<List<BaseEvent>> { private static final Map<String, ObjectReader<? extends BaseEvent>> READER_CACHE = new ConcurrentHashMap<>(); public static void registerEventType(String typeKey, Class<? extends BaseEvent> eventClass) { READER_CACHE.put(typeKey, ObjectReaders.of(eventClass)); } // … 在readSingleEvent中,对于未知类型,可以记录警告日志,并返回一个包含原始数据的通用对象,或者直接忽略。 private BaseEvent handleUnknownType(String eventType, Map<String, Object> data) { log.warn(“Unknown event type: {}”, eventType); // 返回一个包含所有原始数据的通用事件对象,便于调试或数据归档 GenericEvent generic = new GenericEvent(); generic.setType(eventType); generic.setRawData(data); return generic; } }5.3 性能考量与缓存策略
自定义ObjectReader的解析速度通常比默认的反射方式慢,因为多了类型判断和可能的数据拷贝。在高吞吐量场景下,需要仔细评估:
- Reader实例缓存:确保
ObjectReader实例本身是单例或可缓存的,避免每次解析都创建新的PolymorphicEventListReader。 - 避免多层嵌套的异构List:如果数据结构非常复杂(例如List里面套List,里面元素类型还不确定),自定义解析的逻辑会指数级复杂,性能也会下降。尽量在设计数据格式时避免这种结构。
- 考虑使用
fastjson2的JSONPath:如果只是偶尔需要从复杂JSON中提取特定类型的元素,使用JSONPath进行过滤和提取可能比完全自定义反序列化更简单高效。 - 基准测试(Benchmark):使用JMH等工具对自定义解析器和备用方案(如先解析为
JSONArray再手动转换)进行性能对比,用数据驱动决策。
5.4 与序列化(ObjectWriter)的配对使用
解决了反序列化问题,别忘了序列化。如果你将处理后的List<BaseEvent>序列化回JSON,默认的序列化器会正常工作,因为它依赖于对象的运行时类型。但是,如果你希望对序列化过程也进行定制(例如,统一输出格式、过滤某些字段),可以同样实现一个自定义的ObjectWriter,并通过JSON.register注册配对使用,或者在@JSONType注解上同时指定serializer和deserializer。
6. 举一反三:其他复杂场景的应用
掌握了为异构List定制ObjectReader的核心思想后,你可以将这种模式应用到其他类似场景:
Map<String, Object>中特定值的转换:解析一个Map时,如果某个key对应的value可能是多种类型,可以定制该Map的ObjectReader,在读到那个key时进行特殊处理。- 处理不规范的日期格式:接口可能传来多种格式的日期字符串(如
“2023-01-01”,“01/01/2023”, 时间戳)。可以定制一个LocalDateTime的ObjectReader,尝试多种格式进行解析。 - 自定义数字精度处理:将JSON中的浮点数解析为
BigDecimal时指定精度和舍入模式。 - 实现“忽略未知字段”的白名单机制:默认情况下,
fastjson2会忽略JSON中多余字段。但你可以通过自定义ObjectReader实现更严格的反序列化,遇到未知字段时抛出异常,这对于API的健壮性很有帮助。
自定义ObjectReader是fastjson2提供给高级用户的一把利器,它让你能够完全掌控JSON到Java对象的映射过程。虽然初期实现起来比直接调用parseObject复杂,但它带来的类型安全、代码清晰度和长期可维护性的收益是巨大的。下次当你面对“奇葩”的JSON格式时,不妨考虑一下,是不是该请出你的自定义ObjectReader了。