You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何构建解析动态键多类型值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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.11 04:14:55