问题描述:

一个JSON字符串在转对象的时候报JSON解析异常的错误,我仔细看了一下错误堆栈,是枚举导致的数组越界问题。

[{"fee":0,"amount":15,"orderNo":"9136104331757999","storeId":0,"bankCode":"","bankName":"","userName":"测试","accountId":0,"payMethod":10,"storeName":"测试小队","bankBranch":"","cardNumber":"60380500","StoreName":""}
]

报错的方法:

String  s="[{\"fee\": 0, \"amount\": 15, \"orderNo\": \"9136104331757999\", \"storeId\": 941, \"bankCode\": \"\", \"bankName\": \"\", \"userName\": \"测试\", \"accountId\": 0, \"payMethod\": 10, \"storeName\": \"测试小队\", \"bankBranch\": \"\", \"cardNumber\": \"60380500\",\"storeId\":0,\"StoreName\":\"\"}]";
PayAccount[] payAccount = JsonUtil.toBean(s, PayAccount[].class);

JSON转换工具类

public class JsonUtil {private static final ParserConfig parseConfig = new ParserConfig();/*** 转成具体的泛型bean对象** @param text json字符串* @param clazz bean类型* @param <T>* @return*/public static <T> T toBean(String text, Class<T> clazz) {return JSON.parseObject(text, clazz, parseConfig);}}

枚举对象

我看了一下fastjson在反序列化枚举类的源码,发现其在反序列化枚举对象的时候是把枚举的值当做数组下标来取值的,如果枚举对象的第一个值不是从0开始一次递增,就会有问题,开头说的问题就是原因导致的,详细源码看下面截图:

需要取的枚举是value=10的对象,然而对于有枚举对象组成的数组来说,value=10的对象是ordinalEnums[9],但是按照fastjson的处理取的确实ordinalEnums[10],数组越界。

好的。找到了问题之后我们就要考虑如何解决问题。

最简单的方法就是哪里有问题就从哪里解决,我们可以覆盖fastjson的源码,使其按照枚举value值对应的下标去数组中取值。代码我已经写出来了,如下:

Fastjson jar包提供的EnumDeserializer类的deserializer()方法:

重写后的序列化方法:(增加了按照枚举值取下标,从而取到对应的数组值)

简单可行,并且很完美的解决了我们的问题。

但是看题目事情应该不会这么简单,并且在实际的项目开发中不仅有枚举的序列化和反序列化问题,还有json对象的序列化和反序列化问题,今天我们就借着这个问题来总结一下SpringBoot中自定义JSON对象以及JSON对象中的枚举属性的序列化和反序列化问题

定义一个枚举类:

public enum JobStatus {DIMISSION("离职",0),INCUMBENCY("在职",1),INDETERMINATE("待入职",2);private String label;private Integer value;JobStatus(String label, Integer value) {this.label = label;this.value = value;}@Overridepublic String getLabel() {return label;}@Overridepublic Integer getValue() {return value;}
}

1、枚举类型的处理

我们通过继承MyBatis提供的BaseTypeHandler来实现自定义的枚举类型处理器:

import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.MappedTypes;import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;/*** <p>* description: 枚举对象的typehandler* </p>*/
@MappedTypes(value = {JobStatus.class})
public class EnumTypeHandler extends BaseTypeHandler<BaseEnum> {private Class<BaseEnum> type;public EnumTypeHandler() {}public EnumTypeHandler(Class<BaseEnum> type) {if (type == null) throw new IllegalArgumentException("Type argument cannot be null");this.type = type;}@Overridepublic void setNonNullParameter(PreparedStatement ps, int i, BaseEnum parameter, JdbcType jdbcType) throws SQLException {ps.setInt(i, parameter.getValue());}@Overridepublic BaseEnum getNullableResult(ResultSet rs, String columnName) throws SQLException {return convert(rs.getInt(columnName));}@Overridepublic BaseEnum getNullableResult(ResultSet rs, int columnIndex) throws SQLException {return convert(rs.getInt(columnIndex));}@Overridepublic BaseEnum getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {return convert(cs.getInt(columnIndex));}private BaseEnum convert(int status) {BaseEnum[] objs = type.getEnumConstants();for (BaseEnum em : objs) {if (em.getValue() == status) {return em;}}return null;}
}

在MappedTypes后配置需要进行处理的枚举类。

枚举类型的反序列化方法,与上面我们定义的EnumDeserializer功能相同:

import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.parser.DefaultJSONParser;
import com.alibaba.fastjson.parser.JSONLexer;
import com.alibaba.fastjson.parser.deserializer.ObjectDeserializer;
import java.lang.reflect.Type;
import java.util.regex.Pattern;/*** 自定义枚举类型的反序列化* 接口获取枚举的value值时,将其反序列化成枚举对象**/
public class EnumTypeDeserializer implements ObjectDeserializer {@Overridepublic <T> T deserialze(DefaultJSONParser parser, Type type, Object fieldName) {JSONLexer lexer = parser.lexer;Integer value = null;Object parse = parser.parse();if (parse == null)return null;String s = parse.toString();if (Pattern.matches("\\d+", s)){value = (int)parse;} else {if (s.startsWith("{")){JSONObject jo = (JSONObject) parse;value = jo.getIntValue("value");}}if (value == null)return null;if (value < -999999)return null;Class<T> clazz = (Class<T>)type;T[] enumConstants = clazz.getEnumConstants();return (T)BaseEnum.valueOfEnum1(enumConstants, value);}@Overridepublic int getFastMatchToken() {return 0;}
}

枚举基类

import org.springframework.util.ReflectionUtils;import java.lang.reflect.Field;public interface BaseEnum {String DEFAULT_VALUE_NAME = "value";String DEFAULT_LABEL_NAME = "label";default Integer getValue() {Field field = ReflectionUtils.findField(this.getClass(), DEFAULT_VALUE_NAME);if (field == null)return null;try {field.setAccessible(true);return Integer.parseInt(field.get(this).toString());} catch (IllegalAccessException e) {throw new RuntimeException(e);}}default String getLabel() {Field field = ReflectionUtils.findField(this.getClass(), DEFAULT_LABEL_NAME);if (field == null)return null;try {field.setAccessible(true);return field.get(this).toString();} catch (IllegalAccessException e) {throw new RuntimeException(e);}}static <T extends Enum<T>> T valueOfEnum(Class<T> enumClass, Integer value) {if (value == null)throw  new IllegalArgumentException("DisplayedEnum value should not be null");if (enumClass.isAssignableFrom(com.zfkr.qianyue.common.enums.BaseEnum.class))throw new IllegalArgumentException("illegal DisplayedEnum type");T[] enums = enumClass.getEnumConstants();for (T t: enums) {com.zfkr.qianyue.common.enums.BaseEnum displayedEnum = (com.zfkr.qianyue.common.enums.BaseEnum)t;if (displayedEnum.getValue().equals(value))return (T) displayedEnum;}throw new IllegalArgumentException("cannot parse integer: " + value + " to " + enumClass.getName());}static <T> T valueOfEnum1(T[] enums, Integer value) {for (T t: enums) {com.zfkr.qianyue.common.enums.BaseEnum displayedEnum = (com.zfkr.qianyue.common.enums.BaseEnum)t;if (displayedEnum.getValue().equals(value))return (T) displayedEnum;}throw new IllegalArgumentException("cannot parse integer: " + value + " to " );}
}

2、JSON对象的处理

通过继承MyBatis的BaseTypeHandler来实现我们自定义的JSON类型处理器:

/*** json对象的typehandler* */
@MappedTypes(value = {JSON1.class, jSON2.class})
public class JsonTypeHandler<T> extends BaseTypeHandler<T> {private static final SerializeConfig config;private static final ParserConfig parseConfig;private static final SerializerFeature[] features = {SerializerFeature.WriteMapNullValue, // 输出空置字段SerializerFeature.WriteNullListAsEmpty, // list字段如果为null,输出为[],而不是nullSerializerFeature.WriteNullNumberAsZero, // 数值字段如果为null,输出为0,而不是nullSerializerFeature.WriteNullBooleanAsFalse, // Boolean字段如果为null,输出为false,而不是nullSerializerFeature.WriteNullStringAsEmpty // 字符类型字段如果为null,输出为"",而不是null};static {config = new SerializeConfig();parseConfig = new ParserConfig();}private Class<T> clazz;public JsonTypeHandler(Class<T> clazz) {if (clazz == null) throw new IllegalArgumentException("Type argument cannot be null");this.clazz = clazz;}@Overridepublic void setNonNullParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException {ps.setString(i, toJson(parameter));}@Overridepublic T getNullableResult(ResultSet rs, String columnName) throws SQLException {return this.toObject(rs.getString(columnName), clazz);}@Overridepublic T getNullableResult(ResultSet rs, int columnIndex) throws SQLException {return this.toObject(rs.getString(columnIndex), clazz);}@Overridepublic T getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {return this.toObject(cs.getString(columnIndex), clazz);}private String toJson(T object) {try {return JSON.toJSONString(object, config, features);} catch (Exception e) {throw new RuntimeException(e);}}@SuppressWarnings("unchecked")private T toObject(String content, Class<?> clazz) {if (content != null && !content.isEmpty()) {if (content.startsWith("[")) {//数组类型List<?> objects = JSON.parseArray(content, clazz.getComponentType());return (T) objects.toArray();} else {return (T) JSON.parseObject(content, clazz, parseConfig);}} else {return null;}}}

3、将我们自定义的枚举类型处理添加到解析配置器和序列化配置器中

/*** json对象的typehandler*/
@MappedTypes(value = {JSON1.class, jSON2.class})
public class JsonTypeHandler<T> extends BaseTypeHandler<T> {private static final SerializeConfig config;private static final ParserConfig parseConfig;private static final SerializerFeature[] features = {SerializerFeature.WriteMapNullValue, // 输出空置字段SerializerFeature.WriteNullListAsEmpty, // list字段如果为null,输出为[],而不是nullSerializerFeature.WriteNullNumberAsZero, // 数值字段如果为null,输出为0,而不是nullSerializerFeature.WriteNullBooleanAsFalse, // Boolean字段如果为null,输出为false,而不是nullSerializerFeature.WriteNullStringAsEmpty // 字符类型字段如果为null,输出为"",而不是null};static {config = new SerializeConfig();parseConfig = new ParserConfig();//获取枚举类型处理器中定义的需要处理的枚举类Annotation[] annotations = EnumTypeHandler.class.getAnnotations();MappedTypes annotation = (MappedTypes) annotations[0];//设置枚举类型的反序列化方法为自定义的EnumTypeDeserializerEnumTypeDeserializer enumTypeObjectDeserializer = new EnumTypeDeserializer();for (Class<?> aClass : annotation.value()) {parseConfig.getDeserializers().put(aClass, enumTypeObjectDeserializer);}}private Class<T> clazz;public JsonTypeHandler(Class<T> clazz) {if (clazz == null) throw new IllegalArgumentException("Type argument cannot be null");this.clazz = clazz;}@Overridepublic void setNonNullParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException {ps.setString(i, toJson(parameter));}@Overridepublic T getNullableResult(ResultSet rs, String columnName) throws SQLException {return this.toObject(rs.getString(columnName), clazz);}@Overridepublic T getNullableResult(ResultSet rs, int columnIndex) throws SQLException {return this.toObject(rs.getString(columnIndex), clazz);}@Overridepublic T getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {return this.toObject(cs.getString(columnIndex), clazz);}private String toJson(T object) {try {return JSON.toJSONString(object, config, features);} catch (Exception e) {throw new RuntimeException(e);}}@SuppressWarnings("unchecked")private T toObject(String content, Class<?> clazz) {if (content != null && !content.isEmpty()) {if (content.startsWith("[")) {//数组类型List<?> objects = JSON.parseArray(content, clazz.getComponentType());return (T) objects.toArray();} else {return (T) JSON.parseObject(content, clazz, parseConfig);}} else {return null;}}}

在SpringBoot的yml文件中配置自定义类型处理器所在的包,项目启动之后自定义类型处理器即可被扫描到。问题解决。

自定义类型处理器的应用相关推荐

  1. MyBatis自定义类型处理器 TypeHandler

    在项目开发中经常会遇到一个问题: 当我们在javabean中自定义了枚举类型或者其它某个类型,但是在数据库中存储时往往需要转换成数据库对应的类型,并且在从数据库中取出来时也需要将数据库类型转换为jav ...

  2. MyBatis核心源码剖析(SqlSession XML解析 Mapper executor SQL执行过程 自定义类型处理器 缓存 日志)

    MyBatis核心源码剖析 MyBatis核心源码剖析 1 MyBatis源码概述 1.1 为什么要看MyBatis框架的源码 1.2 如何深入学习MyBatis源码 1.3 源码分析的5大原则 2 ...

  3. MyBatis自定义类型处理器(typeHandler)

    MyBatis自定义类型处理器(typeHandler) 我们执行sql语句通过PreparedStatement语句实现,PreparedStatement会设置?值,类型处理器帮PreparedS ...

  4. 在mybatis里如何自定义类型处理器

    类型处理器(typeHandlers) MyBatis 在设置预处理语句(PreparedStatement)中的参数或从结果集中取出一个值时, 都会用类型处理器将获取到的值以合适的方式转换成 Jav ...

  5. 【Mybatis】类型处理器TypeHandler的作用与自定义

    一.什么是类型处理器 1.类型处理器(TypeHandler) MyBatis 在设置预处理语句(PreparedStatement)中的参数或从结果集中取出一个值时,都会用类型处理器将获取到的值以合 ...

  6. Mybatis-自定义类型处理器

    类型转换器:mybatis中有一些常用的类型转换器,比如把Java中的short类型转换为mysql中的short类型:但是如果现在是Java中的Date类型,但是我想要存储到数据库中转换为Long类 ...

  7. desc 枚举类型id_想让代码更优雅?Mybatis类型处理器了解一下!

    明确需求 在设计之初,sys_role表的enabled字段有2个可选值,其中0 代表禁用,1代表启用,而且实体类中我们使用的是Interger类型: 源码展示 /** * 有效标志 */ priva ...

  8. 09_Mybatis-plus类型处理器示例,例如 json 字段对象转换

    DROP TABLE IF EXISTS user;CREATE TABLE user (id BIGINT(20) NOT NULL COMMENT '主键ID',name VARCHAR(30) ...

  9. MyBatis(九):MyBatis类型处理器(TypeHandler)详解

    TypeHandler简介 TypeHandler,顾名思义类型转换器,就是将数据库中的类型与Java中的类型进行相互转换的处理器. MyBatis 在设置预处理语句(PreparedStatemen ...

最新文章

  1. .NET设计模式(4):建造者模式(Builder Pattern)(转)
  2. 爬虫学习笔记(二十四)—— pyspider框架
  3. AngularJS深入(1)——加载启动
  4. [TODO]Python拾遗(二)
  5. Linux下百度云盘报 获取bdstoken失败
  6. [Java基础]自动装箱和拆箱
  7. android node
  8. 【Spring】Spring boot 如何进行私有方法测试
  9. Android开发笔记(一百)折叠式列表
  10. C#写一个URL编码转换GB23121的方法,然后可以取到天气预报
  11. 第3节:vue-router如何参数传递
  12. 容斥原理-51nod1284
  13. c++ Primer plus 之c++学习
  14. Adobe软件字体导入无法显示的问题
  15. 黑帽seo技术大揭秘
  16. Http方式下载文件
  17. Centos7基于postfix实现extmail邮件服务器
  18. 名帖90 蔡襄 楷书《临锺繇帖卷》
  19. 旺店通与金蝶集成解决方案
  20. cdf日上免税店_把代购删了吧!在家也可以逛免税店

热门文章

  1. 网络驱动器设备:iscsi服务器
  2. Linq to Sql 动态条件另类实现方法
  3. [Unity3d]u3d中定时器的使用
  4. Erlang的散列数据结构
  5. 档案和社会保险究竟有什么关系?【转】
  6. 哈·曼丁的故事(三)
  7. 【知识小课堂】 mongodb 之字段中的【 数组】、【内嵌文档】
  8. org.apache.catalina.loader.WebappClassLoader clearReferencesJdbc
  9. android 使用Photoshop获取图片某一点的颜色
  10. android 整数与ip地址的转换