如何构建解析动态键多类型值JSON的GSON数据类及单元测试?
解决GSON解析动态键+单对象/数组混合JSON的问题
问题背景
给定如下结构的JSON响应,其中data字段包含动态键(如Owner、Moderator),每个键对应的值可能是单个JSON对象,也可能是JSON对象数组:
{ "status": true, "data": { "Owner": { "owner_name": "name", "title": "Standup meeting", "description": "..." // 其他字段 }, "Moderator": [ { "owner_name": "name", "title": "Standup meeting", "description": "..." // 其他字段 } // 可能包含更多对象 ] // 可能包含更多动态键 } }
需要构建正确的GSON数据类,并编写单元测试验证解析逻辑。
正确的GSON数据类实现
1. 定义基础实体类
首先定义单个会议项的实体类,对应JSON中的对象结构:
data class MeetingItem( @SerializedName("owner_name") val ownerName: String, @SerializedName("title") val title: String, @SerializedName("description") val description: String // 按实际需求添加其他字段 )
2. 定义顶层响应类
顶层响应类的data字段使用Map<String, List<MeetingItem>>类型,统一处理单对象和数组的情况:
data class MineMeetResponse( @SerializedName("status") val status: Boolean, @SerializedName("data") val data: Map<String, List<MeetingItem>> )
3. 自定义TypeAdapter处理单对象转列表
由于GSON默认无法将单个对象解析为列表,需要编写自定义TypeAdapterFactory,自动将单个对象转换为单元素列表:
class SingleOrListTypeAdapterFactory : TypeAdapterFactory { override fun <T> create(gson: Gson, type: TypeToken<T>): TypeAdapter<T>? { val rawType = type.rawType // 仅处理List类型 if (rawType != List::class.java) return null val elementType = (type.type as ParameterizedType).actualTypeArguments[0] val elementAdapter = gson.getAdapter(TypeToken.get(elementType)) @Suppress("UNCHECKED_CAST") return object : TypeAdapter<T>() { override fun write(out: JsonWriter, value: T?) { // 序列化时按数组格式写入,保持兼容性 val list = value as List<*> out.beginArray() list.forEach { elementAdapter.write(out, it) } out.endArray() } override fun read(`in`: JsonReader): T { return if (`in`.peek() == JsonToken.BEGIN_OBJECT) { // 单个对象转为单元素列表 listOf(elementAdapter.read(`in`)) as T } else { // 数组直接解析为列表 val list = mutableListOf<Any>() `in`.beginArray() while (`in`.hasNext()) { list.add(elementAdapter.read(`in`)) } `in`.endArray() list as T } } } } }
4. 初始化Gson实例
使用时需要注册自定义的TypeAdapterFactory:
val gson = GsonBuilder() .registerTypeAdapterFactory(SingleOrListTypeAdapterFactory()) .create()
单元测试用例编写
依赖准备
如果是Android项目,在build.gradle的testImplementation中添加依赖:
testImplementation 'junit:junit:4.13.2' testImplementation 'com.google.code.gson:gson:2.10.1'
测试类实现
编写JUnit测试类,覆盖单对象、数组、多动态键等场景:
import com.google.gson.Gson import com.google.gson.GsonBuilder import org.junit.Assert.* import org.junit.Test class MineMeetResponseTest { // 初始化带自定义适配器的Gson实例 private val gson = GsonBuilder() .registerTypeAdapterFactory(SingleOrListTypeAdapterFactory()) .create() @Test fun `解析包含单对象和数组的JSON`() { val testJson = """ { "status": true, "data": { "Owner": { "owner_name": "Alice", "title": "Standup meeting", "description": "Daily sync" }, "Moderator": [ { "owner_name": "Bob", "title": "Standup meeting", "description": "Daily sync" }, { "owner_name": "Charlie", "title": "Standup meeting", "description": "Daily sync" } ] } } """.trimIndent() // 解析JSON val response = gson.fromJson(testJson, MineMeetResponse::class.java) // 验证状态 assertTrue(response.status) // 验证Owner对应的列表 val ownerMeetings = response.data["Owner"] assertNotNull(ownerMeetings) assertEquals(1, ownerMeetings?.size) assertEquals("Alice", ownerMeetings?.get(0)?.ownerName) // 验证Moderator对应的列表 val moderatorMeetings = response.data["Moderator"] assertNotNull(moderatorMeetings) assertEquals(2, moderatorMeetings?.size) assertEquals("Bob", moderatorMeetings?.get(0)?.ownerName) assertEquals("Charlie", moderatorMeetings?.get(1)?.ownerName) } @Test fun `解析包含多个动态键的JSON`() { val testJson = """ { "status": false, "data": { "Attendee": { "owner_name": "Dave", "title": "Retro", "description": "Retrospective" }, "Guest": [ { "owner_name": "Eve", "title": "Retro", "description": "Retrospective" } ], "Organizer": { "owner_name": "Frank", "title": "Retro", "description": "Retrospective" } } } """.trimIndent() val response = gson.fromJson(testJson, MineMeetResponse::class.java) assertFalse(response.status) assertEquals(3, response.data.size) val attendee = response.data["Attendee"]?.get(0) assertEquals("Dave", attendee?.ownerName) val guest = response.data["Guest"]?.get(0) assertEquals("Eve", guest?.ownerName) val organizer = response.data["Organizer"]?.get(0) assertEquals("Frank", organizer?.ownerName) } }
内容的提问来源于stack exchange,提问作者user22475952
相关产品推荐
相关产品推荐

