Gson枚举序列化实现

发布于:2025-02-10 ⋅ 阅读:(33) ⋅ 点赞:(0)

众所周知,Json是无法传递枚举类型的,在对Json处理的过程中,可能大家或多或少都会遇到对于枚举处理的问题,当然解决方式也有很多种,本篇仅针对使用Gson处理Json时对于枚举问题给出一种解决方式,可能并不能适用于小伙伴们实际应用的所有场景,但作为借鉴也算是一个不错的选择。

Gson简介

Gson是Google提供的一个Java库,用于将Java对象转换成Json字符串,以及将Json字符串转换成Java对象。它在Android开发中被广泛使用。

主要特点

  • 简单易用: Gson提供了简单的方法来实现Java对象与Json字符串之间的转换,无需编写复杂的代码。
  • 支持复杂类型: 能够处理Java中的各种复杂类型,包括集合、泛型等。
  • 自定义序列化和反序列化: 通过TypeAdapter和TypeAdapterFactory等机制,可以自定义序列化和反序列化的过程,满足特定的需求。
  • 注解支持: 提供了如@SerializedName、@Expose等注解,方便控制字段的序列化和反序列化行为。

Gson的具体用法本篇就不再过多赘述,有需要的可以参考GsonUtils

枚举序列化实现原理

实现将会使用上面简介中提到的TypeAdapter和TypeAdapterFactory两个用于自定义序列化和反序列化过程的重要组件。

TypeAdapter: 是Gson自2.0版本开始提供的一个抽象类,用于接管某种类型的序列化和反序列化过程。它包含两个重要方法write(JsonWriter out, T value)和read(JsonReader in),其他方法都是final方法并最终调用这两个抽象方法。

TypeAdapterFactory: 用于创建TypeAdapter的工厂类,通过对比Type,确定有没有对应的TypeAdapter,没有就返回null。它与GsonBuilder.registerTypeAdapterFactory配合使用。

我们将通过TypeAdapter和TypeAdapterFactory仅对枚举类型进行序列化和反序列化的操作,因为枚举本身不支持序列化,因此我们需要将其转换成可以进行序列化的类型,而本篇会给出将枚举转为Int和String的实现,至于其他的可序列化类型,相信通过本篇的了解,自行实现也是非常简单的。

编码实现

枚举序列化前的数据准备

根据我们上面实现描述,我们需要将枚举转化为可以进行序列化的类型,那么我们需要有一个类来描述可以序列化为什么类型,让我们新建一个枚举类EnumMappingType,并添加如下代码:

enum class EnumMappingType {
    INT,
    STRING,
    LONG,
    DOUBLE,
    BOOLEAN;
}

接着就是我们本次实现的核心代码,通过继承TypeAdapter实现枚举序列化和反序列化的核心逻辑,让我们继续新增一个类EnumTypeAdapter:

class EnumTypeAdapter<T: Any>(type: TypeToken<T>): TypeAdapter<T>(){

    override fun write(out: JsonWriter?, value: T) {
        TODO("Not yet implemented")
    }

    override fun read(`in`: JsonReader?): T {
        TODO("Not yet implemented")
    }
}

该类我们继承自TypeAdapter,并且仅存在一个参数为TypeToken<T>的构造函数,这里的TypeToken可以理解为我们需要处理的枚举类型在Gson中的封装对象,处理时会通过Gson传入并调用write和read进行序列化和反序列化操作。接着让我们来继续完善它。

    //枚举元素与序列化数据之间的映射表
    private var maps = mutableMapOf<T, EnumValue>()

    //枚举值与类型的封装
    private data class EnumValue(var value: Any, var type: EnumMappingType)

    init {
        //遍历枚举类中所有的枚举元素
        type.rawType.enumConstants.filterNotNull().forEach {
            val tt: T = it as T

            //通过反射获取每个元素的ordinal,以枚举元素为键,将ordinal返回值和EnumMappingType.INT类型进行封装为EnumValue作为值存储至maps
            val ordinal = tt.javaClass.superclass.getDeclaredMethod("ordinal")
            var accessible = ordinal.isAccessible
            ordinal.isAccessible = true
            maps[tt] = EnumValue(ordinal.invoke(it) as Int, EnumMappingType.INT)
            ordinal.isAccessible = accessible
        }
    }

添加了一个MutableMap类型的全局变量maps,这个maps用来保存枚举与对应序列化数据的映射关系。

EnumValue类用于保存枚举转为序列化数据的值和类型。

而init代码块则是会在序列化和反序列化之前准备的枚举的映射数据,每个需要进行序列化的枚举类会先通过init代码块遍历所有枚举类的元素,并将他们根据一定的规则进行序列化数据映射,我们这里是将枚举映射为了int值,而这个int值则是枚举的序号ordinal。

初始化数据完成之后,接着让我们开始着手序列化和反序列化的操作,首先先进行序列化操作。

枚举序列化

    override fun write(writer: JsonWriter, value: T) {
        if (value == null) {
            writer.nullValue()
        } else {
            val valueType = maps[value]!!
            when (valueType.type) {
                EnumMappingType.INT -> writer.value(valueType.value as Int)
                EnumMappingType.STRING -> writer.value(valueType.value as String)
                EnumMappingType.LONG -> writer.value(valueType.value as Long)
                EnumMappingType.DOUBLE -> writer.value(valueType.value as Double)
                EnumMappingType.BOOLEAN -> writer.value(valueType.value as Boolean)
            }
        }
    }

这里序列化的时候就单纯的通过我们上面init准备好的数据通过去查找需要序列化的枚举对应的EnumValue这里名字叫valueType,然后根据EnumValue中我们上面初始化数据时保存的类型EnumMappingType进行匹配,将EnumValue中保存的数据强转为对应的类型并写入序列化。

接着我们来看反序列化的实现。

枚举反序列化
    override fun read(reader: JsonReader): T? {
        if (reader.peek() == JsonToken.NULL) {
            reader.nextNull()
            return null
        } else {
            val source = reader.nextString()
            var tt: T? = null
            for((value, type) in maps){
                if (type.value.toString() == source) {
                    tt = value
                    break
                }
            }
            return tt
        }
    }

反序列化代码其实也不难,上面序列化是通过键找值,因为我们本身保存数据就是键值对,所以很容易就可以找到,但是反序列化刚好相反,需要通过值来找键,所以就需要用到一个for循环遍历maps中的所有元素进行比对查找,如果比对成功返回对应的枚举元素即可。其实这里也说明一个问题就是枚举json序列化的时候一定要保证同一个枚举类中的枚举元素序列化后的值唯一,否则就会有序列化和反序列化数据错乱的问题。

至此我们枚举类型的json序列化和反序列化的核心代码就已经编写完成,下面给出完整代码。

EnumTypeAdapter:

class EnumTypeAdapter<T: Any>(type: TypeToken<T>): TypeAdapter<T>(){
    //枚举元素与序列化数据之间的映射表
    private var maps = mutableMapOf<T, EnumValue>()

    init {
        //遍历枚举类中所有的枚举元素
        type.rawType.enumConstants.filterNotNull().forEach {
            val tt: T = it as T

            //通过反射获取每个元素的ordinal,以枚举元素为键,将ordinal返回值和EnumMappingType.INT类型进行封装为EnumValue作为值存储至maps
            val ordinal = tt.javaClass.superclass.getDeclaredMethod("ordinal")
            var accessible = ordinal.isAccessible
            ordinal.isAccessible = true
            maps[tt] = EnumValue(ordinal.invoke(it) as Int, EnumMappingType.INT)
            ordinal.isAccessible = accessible
        }
    }

    override fun write(writer: JsonWriter, value: T) {
        if (value == null) {
            writer.nullValue()
        } else {
            val valueType = maps[value]!!
            when (valueType.type) {
                EnumMappingType.INT -> writer.value(valueType.value as Int)
                EnumMappingType.STRING -> writer.value(valueType.value as String)
                EnumMappingType.LONG -> writer.value(valueType.value as Long)
                EnumMappingType.DOUBLE -> writer.value(valueType.value as Double)
                EnumMappingType.BOOLEAN -> writer.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
            for((value, type) in maps){
                if (type.value.toString() == source) {
                    tt = value
                    break
                }
            }
            return tt
        }
    }

    //枚举值与类型的封装
    private data class EnumValue(var value: Any, var type: EnumMappingType)
}
TypeAdapterFactory接口实现

接下来让我们来实现我们下一个关键类EnumTypeAdapterFactory,该类实现了Gson的TypeAdapterFactory接口:

class EnumTypeAdapterFactory: TypeAdapterFactory {

    override fun <T : Any> create(gson: Gson?, type: TypeToken<T>): TypeAdapter<T>? {
        if (!type.rawType.isEnum) {
            return null
        }
        return EnumTypeAdapter<T>(type)
    }
}

这个类相对来说就非常简单了,这里实现了TypeAdapterFactory接口的create函数,因为我们仅处理枚举类型,其他类型我们就直接返回null,而如果是需要处理的枚举类型就返回我们实现的EnumTypeAdapter对象。

枚举序列化为String类型

到目前为止我们好像只实现了枚举序列化为int值,可能有小伙伴会问那么如何序列化为String呢,如果要序列化为String的话,就需要新增一个注解,我们命名为SerializedEnumValue2String:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.METHOD})
public @interface SerializedEnumValue2String {
    String value();
    String[] alternate() default {};
}

该注解需要传入一个String 参数,聪明的同学可能已经想到,注解中传入的这个参数就是我们枚举元素序列化之后的值,接着让我们在EnumTypeAdapter中新增一个函数annotationProcessing:

    //自定义注解转换处理
    private fun annotationProcessing(it:Any,tt: T):Boolean{
        //新增@SerializedEnumValue2String注解用于枚举转String
        val annotation = tt.javaClass.getField((it as Enum<*>).name).getAnnotation(SerializedEnumValue2String::class.java)
        if (annotation != null) {
            maps[tt] = EnumValue(annotation.value, EnumMappingType.STRING)
            return true
        }
        //其他转换注解可以参考SerializedEnumValue2String进行添加
        return false
    }

在annotationProcessing中我们首先通过反射来查找枚举元素是否包含SerializedEnumValue2String,如果存在就就将注解中传入的参数值取出并封装为EnumValue,设置其值类型为String,然后添加枚举与序列化值的映射关系到maps。annotationProcessing的返回值表示有没有处理注解的序列化,有就返回true,没有就返回false。

接着让我们在init代码块中使用这个函数。

init {
        //遍历枚举类中所有的枚举元素
        type.rawType.enumConstants.filterNotNull().forEach {
            val tt: T = it as T

            if(annotationProcessing(it,tt)) return@forEach

            //...枚举序列化为int值...//
        }
    }

这里逻辑也非常简单,就不针对代码进行解释,但这块说明下存在注解的枚举处理方式优先级高于序号的处理方式,这个优先级大家需要注意下。

如果实际有需要添加枚举转其他数据类型的话,应该还是要用到注解,可以在annotationProcessing中参考SerializedEnumValue2String注解继续添加。

使用及验证

使用和验证代码也比较简单,所以我就直接全部贴出来了

fun initGson():Gson{
    var gsonBuilder = Gson().newBuilder()
    gsonBuilder.registerTypeAdapterFactory(EnumTypeAdapterFactory())
    return gsonBuilder.create()
}



 enum class Weekday {
    @SerializedEnumValue2String("MONDAY")
    MONDAY,
    TUESDAY,
    WEDNESDAY,
    THURSDAY,
    FRIDAY,
    SATURDAY,
    SUNDAY
}

class Course(private var name:String = "语文", private var time:Weekday = Weekday.MONDAY){
    override fun toString(): String {
        return "Course(name='$name', time=$time)"
    }
}


fun main() {
    var gson = initGson()

    //对象转json
    var jsonStr = gson.toJson(Course("物理",Weekday.MONDAY))
    println("Course转json:$jsonStr")

    //json转对象
    jsonStr = "{\"name\":\"数学\",\"time\":5}"
    var course = gson.fromJson(jsonStr,Course::class.java)
    println("json转Course:$course")
}

在initGson函数中主要是用于创建Gson对象,这里特殊的一点是用到了GsonBuilder,主要是需要通过GsonBuilder将我们的EnumTypeAdapterFactory类注册到Gson中。

然后我写了一个枚举类Weekday和Course类用于验证模拟数据。

接着在main函数中先通过gson对Course类进行序列化,然后在通过Json字符串对Course进行反序列化,执行结果如下:

在这里插入图片描述

执行结果,符合预期,说明我们本次实现可以通过Gson对枚举类型数据进行序列化和反序列化,至此Gson序列化枚举类型数据实现圆满结束,感谢大家观看。


网站公告

今日签到

点亮在社区的每一天
去签到