将Map转成对应实体,但存在字段类型对不上,比如map里面是字符串(存在非数字字符),但实体是double,map不需要和实体一一对应,map中有的字段,实体没有,则不会转换,也不会报错
<dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.83</version> </dependency>User user = JSON.parseObject(jsonString, User.class);处理Map(或JSON字符串)中字段类型与Java实体类不匹配的问题,比如字符串包含非数字字符却需要转为double类型,这里有几种实用的解决方案。我先通过一个表格来汇总它们:
解决方案 | 适用场景 | 实现方式 |
自定义反序列化器 | 需要精细控制转换逻辑(如清洗数据、处理特殊格式) | 实现 |
全局类型转换配置 | 统一处理特定类型(如项目中所有 | 使用 |
注解与宽松模式 | 字段格式基本规范,只需忽略个别无关字段或简单适配 | 使用 |
1、自定义反序列化器
当字符串包含非数字字符(如货币符号"¥100.5"、单位"123.45cm"或千位分隔符"1,234.5")时,推荐使用自定义反序列化器。这让你能完全掌控转换逻辑。
实现自定义反序列化器:创建一个类实现ObjectDeserializer接口。在deserialze方法中编写清洗和转换逻辑。
注意:引入的是import com.alibaba.fastjson.parser.DefaultJSONParser;
import com.alibaba.fastjson.parser.DefaultJSONParser; import com.alibaba.fastjson.parser.deserializer.ObjectDeserializer; import com.alibaba.fastjson.JSONToken; import java.lang.reflect.Type; public class CustomDoubleDeserializer implements ObjectDeserializer { @Override public <T> T deserialze(DefaultJSONParser parser, Type type, Object fieldName) { // 1. 首先将值作为字符串读取 String originalValue = parser.parseObject(String.class); if (originalValue == null || originalValue.trim().isEmpty()) { return null; } try { // 2. 清洗字符串:移除非数字字符(保留小数点、负号) String cleanNumberString = originalValue.replaceAll("[^\\\\d.-]", ""); // 3. 转换为 Double // 此步骤会自动处理整数(如 "123")和小数(如 "123.45") return (T) Double.valueOf(cleanNumberString); } catch (NumberFormatException e) { System.err.println("字符串转换Double失败: " + originalValue); return null; } } @Override public int getFastMatchToken() { // 修改此处:返回 LITERAL_FLOAT 以同时匹配整数和小数 return JSONToken.LITERAL_FLOAT; } }1.1、匹配策略与令牌选择
在 FastJSON 中,词法分析器会将 JSON 中的数字转换为特定的令牌。您可以通过getFastMatchToken()方法告知解析器,当前反序列化器希望处理哪种类型的令牌。
为了实现匹配整数和小数的目标,通常有以下两种策略,其核心区别如下表所示:
策略 |
| 优点 | 缺点 |
策略一:精确匹配 |
| 目标准确,性能最佳。对于纯整数输入的场景最高效。 | 如果 JSON 中明确是浮点数(如 |
策略二:稳健匹配 |
| 兼容性最好,能确保覆盖所有数值(整数和小数)。 | 性能可能有极细微损耗,因为浮点令牌的匹配范围更广。 |
2、注解使用
在实体类字段上应用:使用@JSONField注解指定使用这个自定义反序列化器。
public class User { private String name; @JSONField(deserializeUsing = CustomDoubleDeserializer.class) private Double salary; // 使用Double包装类型,以更好地处理null // 省略 getter 和 setter }3、考虑全局类型转换配置 -(没生效)
如果项目中有大量同类转换需求,全局配置更高效。
通过SerializeConfig在全局层面为特定类型(如String到Double)注册一个通用的转换器。这样,Fastjson 在遇到类型不匹配时,会尝试使用这个全局转换器。
import com.alibaba.fastjson.parser.ParserConfig; import org.springframework.boot.ApplicationArguments; import org.springframework.boot.ApplicationRunner; import org.springframework.context.annotation.Configuration; import javax.annotation.PostConstruct; @Configuration public class FastJsonConfig { /** * 使用 @PostConstruct 在Bean初始化后执行 */ @PostConstruct public void initFastJsonGlobalConfig() { // 关闭ASM以解决复杂对象反序列化问题 // 当反序列化的目标对象属性超过一定数量(如32个)时,FastJSON默认使用的ASM技术可能无法正确处理自定义反序列化器,这时会报错 ParserConfig.getGlobalInstance().setAsmEnable(false); ParserConfig.getGlobalInstance().putDeserializer(Double.class, new CustomDoubleDeserializer()); System.out.println("全局Double类型反序列化器已注册。"); } /** * 或者,实现 ApplicationRunner 接口,在应用完全启动后执行 */ // @Override // public void run(ApplicationArguments args) throws Exception { // ParserConfig.getGlobalInstance().putDeserializer(Double.class, new CustomDoubleDeserializer()); // } }3.1、全局配置失效
您遇到的全局自定义反序列化器配置未生效的问题,通常与配置的加载时机、作用范围或环境冲突有关。下面是一个系统的排查指南。
1、排查步骤与解决方案
排查方向 | 关键检查点 | 建议操作 |
1. 配置加载时机 | 确保 | 在 |
2. 配置类加载 | 确保 | 确认类位于Spring Boot的主应用类( |
3. 版本与兼容性 | 不同版本的Fastjson在处理全局反序列化器时可能存在差异或Bug。 | 尝试升级Fastjson到最新稳定版本(如 |
4. JSON数据结构 | 确认您的JSON字符串中对应字段的类型。 | 如果JSON中 |
5. 配置覆盖 | 项目中其他地方是否重置了 | 检查代码中是否有其他地方调用了 |
6. 依赖冲突 | 项目中是否存在多个版本的Fastjson。 | 使用 |
2、进阶排查:类加载器隔离问题
在Spring Boot打包成可执行JAR(即fat jar)运行时,可能会遇到一个更深层次的问题:类加载器隔离。
- 问题根源:Spring Boot使用自定义的
LaunchedURLClassLoader来加载BOOT-INF/classes和BOOT-INF/lib下的类。而您通过@Configuration定义的配置和反序列化器都在这个类加载器中。但某些情况下(例如,在Agent中或通过特定方式引用的Fastjson核心类),可能由系统类加载器(AppClassLoader)加载。这会导致ParserConfig.getGlobalInstance()在不同的类加载器视角下可能不是同一个实例,造成配置"看似生效,实则无效"。 - 解决方案:如果上述常规排查均无效,可以尝试一种更直接的方式:在每次反序列化时显式指定配置。
// 创建一个新的、独立的配置实例 ParserConfig config = new ParserConfig(); config.putDeserializer(Double.class, new CustomDoubleDeserializer()); // 在解析时使用这个配置 User user = JSON.parseObject(jsonString, User.class, config);这种方式虽然不如全局配置方便,但它确保了配置的绝对有效性,避免了复杂的类加载环境干扰。
3、总结
全局配置未生效,通常按以下顺序排查:
- 确认配置类被正确加载(加日志最直接)。
- 检查JSON数据格式是否为字符串。
- 检查依赖是否存在版本冲突。
- 若仍无法解决,考虑是否是类加载器隔离问题,并尝试使用显式传入配置的方式。
希望这些步骤能帮助您定位并解决问题。如果方便,可以分享您的Spring Boot主类位置和部分日志,以便更精确地分析。
4、尝试注解与宽松模式
对于更简单的情况,可以尝试以下方法:
- 使用
@JSONField格式化:如果字符串是标准数字格式(如日期、数字),可用@JSONField注解的format属性简单指定格式。 - 启用宽松模式:通过配置
ParserConfig的setAutoTypeSupport(true)等方法,让 Fastjson 以更宽松的方式解析,尝试自动类型转换。 - 在 FastJSON 中,使用
@JSONField注解和通过ParserConfig进行全局注册是两种不同的自定义反序列化器配置方式。它们各有特点,可以单独使用,也可以组合使用,但需要理解其优先级和生效范围。
下表清晰地展示了两者的核心区别:
特性 |
|
|
作用范围 | 字段级别,仅对特定注解字段生效 | 类型级别,对该类型(如 |
配置位置 | 直接定义在实体类的字段上 | 在配置类中集中管理(如 |
侵入性 | 较强(需修改实体类代码) | 较弱(不修改实体类,配置与代码分离) |
维护性 | 分散在各个实体类中,不利于统一修改 | 配置集中,方便统一管理和修改 |
优先级 | 更高(字段级别配置优先) | 相对较低 |
1、如何选择和使用
- 可以单独使用注解如果你的自定义逻辑只针对某个特定类中的个别字段,并且你希望这种配置明确地与这个字段绑定,那么单独使用
@JSONField注解是简洁直观的选择。
- 优点:配置精准,一目了然。
- 缺点:如果多个类的多个字段都需要同样的处理逻辑,就需要在每个字段上重复注解,不利于维护。
- 可以单独使用全局配置当你需要为某种数据类型(如所有
Date类型字段、所有Double类型字段)统一应用自定义的反序列化逻辑时,全局配置是更优的选择。
- 优点:一劳永逸,一次注册,全局生效。非常适合处理通用的数据清洗、格式转换等场景。
- 注意点:确保配置代码(如
FastJsonConfig类的init方法)在任何 JSON 反序列化操作之前执行。
- 可以结合使用(注意优先级)两种方式可以共存。当同时存在时,FastJSON 会按照优先级来决定使用哪个反序列化器:字段级别的
@JSONField注解 > 全局ParserConfig注册的类型反序列化器。
- 应用场景:你可以为
Double类型设置一个全局的、通用的反序列化器。如果某个特定字段需要特殊处理,可以再在这个字段上使用@JSONField指定一个不同的反序列化器,它将覆盖全局配置。
- 应用场景:你可以为
2、注解可能“失效”的排查点
你之前遇到的@JSONField注解“似乎没有生效”的情况,除了反序列化器本身的实现细节外,还可能源于一些特定场景。例如,有报告指出,当对象被放入JSONArray后,再通过JSONArray.getObject(index, Class)方法取出时,注解信息可能会在某些 FastJSON 版本的处理流程中被忽略,导致自定义反序列化器未被调用。在这种情况下,使用全局配置往往是更可靠的选择。