自定义类型处理器的应用
問題描述:
一個JSON字符串在轉對象的時候報JSON解析異常的錯誤,我仔細看了一下錯誤堆棧,是枚舉導致的數組越界問題。
報錯的方法:
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文件中配置自定義類型處理器所在的包,項目啟動之后自定義類型處理器即可被掃描到。問題解決。
總結
以上是生活随笔為你收集整理的自定义类型处理器的应用的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 由防重复点击引发的幂等性问题思考
- 下一篇: 如何在项目启动时就执行某些操作