坑——fastjson将字符串转到带枚举的java对象
fastjson将不同的字符串转换成带枚举的java对象时,不同的字符串值转换成java对象的结果不同;
测试用fastjson版本:
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.83</version>
</dependency>
测试用的java对象:
package test;
public enum E {
AAA,
BBB;
}
package test;
public class A {
public String abc;
public E e;
@Override
public String toString() {
return "A [abc=" + abc + ", e=" + e + "]";
}
}
测试类:
package test;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
public class Test {
public static void main(String[] args) {
System.out.println("start");
String s = "{\"abc\":\"TEST\",\"e\":\"AAA\"}";
JSONObject jobj = JSON.parseObject(s);
System.out.println(jobj.get("e"));
A a = JSON.toJavaObject(jobj, A.class);
System.out.println(a);
}
}
正常使用枚举的值字符串状态可以正常转换:
使用枚举中的数值也可以正常转换:
例如使用数值1,String s = "{\"abc\":\"TEST\",\"e\":1}";
但是使用枚举中不存在的字符串,就会返回null,没有该枚举的值也是null:
字符串改为,String s = "{\"abc\":\"TEST\",\"e\":\"XXXXXX\"}";
改为,String s = "{\"abc\":\"TEST\"}";
如果使用枚举中不存在的数值,则会直接抛出异常:
字符串改为,String s = "{\"abc\":\"TEST\",\"e\":100}";
改成对象也是一样的错误:
String s = "{\"abc\":\"TEST\",\"e\":{}}";
String s = "{\"abc\":\"TEST\",\"e\":{\"e\":1}}";
可以看到,不管何种情况,JSONObject都会获得对应的值,但是转换成java对应的对象时就会有不同结果,主要注意这个地方,尤其字符串来自未知的地方;
简单解决,给java对象里的枚举设定默认值,但也只能处理字符串里没有该枚举字段的情况,其他情况还是不行;
彻底解决方法,继承com.alibaba.fastjson.parser.ParserConfig类,重写protected ObjectDeserializer getEnumDeserializer(Class<?> clazz)方法,该方法返回一个com.alibaba.fastjson.parser.deserializer.EnumDeserializer实例,所以还得写个类继承EnumDeserializer类,重写public <T> T deserialze(DefaultJSONParser parser, Type type, Object fieldName)方法,该方法就是处理各种枚举值转化的地方,可以按照自己的逻辑重写;
源码里getEnumDeserializer()方法:
源码里deserialze()方法:
测试代码:
继承ParserConfig类重写getEnumDeserializer()方法(当然也可以根据clazz处理特定枚举):
package test;
import com.alibaba.fastjson.parser.ParserConfig;
import com.alibaba.fastjson.parser.deserializer.ObjectDeserializer;
public class MyParseConf extends ParserConfig {
@Override
protected ObjectDeserializer getEnumDeserializer(Class<?> clazz) {
System.out.println("MyParseConf clazz:" + clazz);
return new MyEnumDeserializer(clazz);
}
}
继承EnumDeserializer类重写deserialze()方法,(比如我要处理int值非法,所有非法值默认成0,当然也可以根据type、fieldName参数设置别的值,其他不需要处理的直接跟源码一样就好):
package test;
import static com.alibaba.fastjson.util.TypeUtils.fnv1a_64_magic_hashcode;
import static com.alibaba.fastjson.util.TypeUtils.fnv1a_64_magic_prime;
import java.lang.reflect.Type;
import com.alibaba.fastjson.JSONException;
import com.alibaba.fastjson.parser.DefaultJSONParser;
import com.alibaba.fastjson.parser.Feature;
import com.alibaba.fastjson.parser.JSONLexer;
import com.alibaba.fastjson.parser.JSONToken;
import com.alibaba.fastjson.parser.deserializer.EnumDeserializer;
public class MyEnumDeserializer extends EnumDeserializer {
public MyEnumDeserializer(Class<?> enumClass) {
super(enumClass);
}
@Override
public <T> T deserialze(DefaultJSONParser parser, Type type, Object fieldName) {
try {
Object value;
final JSONLexer lexer = parser.lexer;
final int token = lexer.token();
if (token == JSONToken.LITERAL_INT) {
int intValue = lexer.intValue();
lexer.nextToken(JSONToken.COMMA);
if (intValue < 0 || intValue >= ordinalEnums.length) {
// throw new JSONException("parse enum " + enumClass.getName() + " error, value : " + intValue);
System.out.println("deserialze type:" + type);
System.out.println("deserialze fName:" + fieldName);
intValue = 0;
}
return (T) ordinalEnums[intValue];
} else if (token == JSONToken.LITERAL_STRING) {
String name = lexer.stringVal();
lexer.nextToken(JSONToken.COMMA);
if (name.length() == 0) {
return (T) null;
}
long hash = fnv1a_64_magic_hashcode;
long hash_lower = fnv1a_64_magic_hashcode;
for (int j = 0; j < name.length(); ++j) {
char ch = name.charAt(j);
hash ^= ch;
hash_lower ^= ((ch >= 'A' && ch <= 'Z') ? (ch + 32) : ch);
hash *= fnv1a_64_magic_prime;
hash_lower *= fnv1a_64_magic_prime;
}
Enum e = getEnumByHashCode(hash);
if (e == null && hash_lower != hash) {
e = getEnumByHashCode(hash_lower);
}
if (e == null && lexer.isEnabled(Feature.ErrorOnEnumNotMatch)) {
throw new JSONException("not match enum value, " + enumClass.getName() + " : " + name);
}
return (T) e;
} else if (token == JSONToken.NULL) {
value = null;
lexer.nextToken(JSONToken.COMMA);
return null;
} else {
value = parser.parse();
}
throw new JSONException("parse enum " + enumClass.getName() + " error, value : " + value);
} catch (JSONException e) {
throw e;
} catch (Exception e) {
throw new JSONException(e.getMessage(), e);
}
}
}
测试主类,使用自己的ParseConfig:
测试结果: