1. 遇到的问题
用Gson
来序列化、反序列化某个带有Enum
成员变量时,会存在一点瑕疵。比如一个枚举类:
1
2
3
4
enum class Type(var type: Int) {
FIRST(1),
SECOND(2)
}
在序列化时,想要的结果是:
1
2
3
{
"key": 1
}
可实际上是:
1
2
3
{
"key": "FIRST"
}
同样,在反序列化时,json
字符串传进来的是1
, 无法映射到 枚举Type.FIRST
。
2. 解决方案
目前,网上有两种解决方案:
方案一:
通过Gson
提供的 TypeAdapter
、JsonSerializer
、JsonDeserializer
类,在实例化Gson
时注册进去。但是有个致命的缺点,它只能针对单个具体的Enum
。如果你的项目中有20个Enum
, 那就必须实现20个类,再分别注册进去。这种方式通用性不强,排除掉。
方案二:
通过Gson
提供的SerializedName
注解。用法如下:
1
2
3
4
5
6
enum class Type(var type: Int) {
@SerializedName("1")
FIRST(1),
@SerializedName("2")
SECOND(2)
}
加上注解之后,序列化的结果是:
1
2
3
{
"key": "1"
}
但是这种方式也有问题。上面的例子中,type 是 Int
类型,最后生成的json
却是"key": "1"
, 而不是"key": 1
。
那么,要怎么处理才能解决问题并且优雅呢?
3. 最优雅的方案
本质上,是通过TypeAdapterFactory
动态的生成TypeAdapter
来实现的。
具体思路是:
- 判断序列化、反序列化的变量是否为枚举类型。如果不是就交给
Gson
处理。 - 获取到枚举类的所有类型。
- 如果枚举类型有
SerializedName
注解,则反射拿到SerializedName.value
并保存到Map。如果没有,反射拿到成员变量并保存到Map。 - 在
TypeAdapter
的write
方法,也就是在序列化的过程中,根据入参value
去Map中取出相应值并传递给JsonWriter
。 - 在
TypeAdapter
的read
方法,也就是在反序列化的过程中,根据reader.nextString()
值跟Map中的值比较,返回对应值关联的枚举类型。
源码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
import com.google.gson.Gson
import com.google.gson.TypeAdapter
import com.google.gson.TypeAdapterFactory
import com.google.gson.annotations.SerializedName
import com.google.gson.reflect.TypeToken
import com.google.gson.stream.JsonReader
import com.google.gson.stream.JsonToken
import com.google.gson.stream.JsonWriter
class EnumTypeAdapterFactory: TypeAdapterFactory {
override fun <T : Any> create(gson: Gson, type: TypeToken<T>): TypeAdapter<T>? {
if (!type.rawType.isEnum) {
return null
}
val maps = mutableMapOf<T, ValueType>()
type.rawType.enumConstants.filter { it != null }.forEach {
val tt: T = it!! as T
val serializedName = tt.javaClass.getField(it.toString()).getAnnotation(SerializedName::class.java)
if (serializedName != null) {
maps[tt] = ValueType(serializedName.value, BasicType.STRING)
return@forEach
}
val field = tt.javaClass.declaredFields.firstOrNull { it2 ->
BasicType.isBasicType(it2.type.name)
}
if (field != null) {
field.isAccessible = true
val basicType = BasicType.get(field.type.name)
val value: Any = when (basicType) {
BasicType.INT -> field.getInt(tt)
BasicType.STRING -> field.get(tt) as String
BasicType.LONG -> field.getLong(tt)
BasicType.DOUBLE -> field.getDouble(tt)
BasicType.BOOLEAN -> field.getBoolean(tt)
}
maps[tt] = ValueType(value, basicType)
} else {
maps[tt] = ValueType(tt.toString(), BasicType.STRING)
}
}
return object: TypeAdapter<T>() {
override fun write(out: JsonWriter, value: T?) {
if (value == null) {
out.nullValue()
} else {
val valueType = maps[value]!!
when (valueType.type) {
BasicType.INT -> out.value(valueType.value as Int)
BasicType.STRING -> out.value(valueType.value as String)
BasicType.LONG -> out.value(valueType.value as Long)
BasicType.DOUBLE -> out.value(valueType.value as Double)
BasicType.BOOLEAN -> out.value(valueType.value as Boolean)
}
}
}
override fun read(reader: JsonReader): T? {
if (reader.peek() == JsonToken.NULL) {
reader.nextNull()
return null
} else {
val source = reader.nextString()
var tt: T? = null
maps.forEach { value, type ->
if (type.value.toString() == source) {
tt = value
return@forEach
}
}
return tt
}
}
}
}
data class ValueType(var value: Any, var type: BasicType)
enum class BasicType(var value: String) {
INT("int"),
STRING("java.lang.String"),
LONG("long"),
DOUBLE("double"),
BOOLEAN("boolean");
companion object {
fun isBasicType(name: String): Boolean {
return values().any { it.value == name }
}
fun get(name: String) = values().first { it.value == name }
}
}
}
4. 使用
创建一个测试的枚举类:
1
2
3
4
5
6
7
8
import com.google.gson.annotations.SerializedName
enum class TestEnum(var type: Int) {
@SerializedName("ffff")
FIRST(5),
SECOND(6)
}
创建一个测试的数据结构:
1
2
3
data class TestBean(var id: Int,
var type: TestEnum,
var type2: TestEnum)
测试代码:
1
2
3
4
5
6
7
val gson = GsonBuilder().registerTypeAdapterFactory(EnumTypeAdapterFactory()).create()
val bean = TestBean(3, TestEnum.FIRST, TestEnum.SECOND)
println(gson.toJson(bean))
val str = "{\"id\":3,\"type\":\"ffff\", \"type2\":6}"
print(gson.fromJson(str, TestBean::class.java))
打印结果:
1
2
{"id":3,"type":"ffff","type2":6}
TestBean(id=3, type=FIRST, type2=SECOND)
既不用针对具体的Enum
写JsonSerializer
,也不用入侵Enum
类添加注解,只要注册一个EnumTypeAdapterFactory
就能够对所有枚举类(针对其他特殊的可修改源码,思路类似)做处理,真是简洁又优雅。