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

使用Gson解析JSON时初始化Kotlin数据类非构造属性

Gson解析Kotlin数据类:非构造属性为null的解决方法

问题描述

我正在开发一个项目,需要使用Gson库实现Kotlin数据类与JSON之间的序列化/反序列化。我的Request类代码如下:

data class Request(val parent_identifier: String?=null, val identifier: String, val display_name: String,
                       val bssid: String, val company_address: LocationAddress,
                       val minimum_match_criteria: Boolean=false, val public_ip:String, val private_ip:String, val network_switches: List<ZoomNetworkSwitch>): HasUid {
        override val uid: String
            get() = display_name

        val country = company_address.country
        val address_line1 = company_address.address_line1
        val address_line2 = company_address.address_line2
        val city = company_address.city
        val state_code = company_address.state_code
        val zip = company_address.zip
}

注意其中基于嵌套company_address的访问器属性。当调用gson().fromJson()方法解析以下JSON字符串时:

{
        "display_name": "7th St Café",
        "private_ip": "111.0.0.0/16",
        "public_ip": "123.0.0.0/16",
        "identifier": "CAFE",
        "company_address": {
            "address_line1": "420 S 7th St",
            "address_line2": "7th St Café",
            "city": "Lewisburg",
            "country": "US",
            "state_code": " PA",
            "zip": " 17837"
        },
        "network_switches": [
            {
                "mac_address": "00:bd:9e:ff:27:00"
            }
        ]
}

得到的对象中构造属性已正常初始化,但所有非构造属性(country、address_line1等)均为null。需要如何操作才能让Gson正确初始化该类?

解决方案

Gson解析Kotlin数据类时,仅会初始化主构造函数中声明的属性。你定义的country、address_line1这类属于类体中的派生属性,Gson不会自动为其赋值。可通过以下几种方式解决:

1. 将派生属性改为委托属性(推荐)

将这些属性改为从company_address委托读取,Gson初始化company_address后,这些属性会自动获取对应值:

data class Request(val parent_identifier: String?=null, val identifier: String, val display_name: String,
                   val bssid: String, val company_address: LocationAddress,
                   val minimum_match_criteria: Boolean=false, val public_ip:String, val private_ip:String, val network_switches: List<ZoomNetworkSwitch>): HasUid {
    override val uid: String
        get() = display_name

    val country: String by company_address::country
    val address_line1: String by company_address::address_line1
    val address_line2: String? by company_address::address_line2
    val city: String by company_address::city
    val state_code: String by company_address::state_code
    val zip: String by company_address::zip
}

这种方式保持代码简洁,且属性值会随company_address的变化自动更新,同时保留了数据类的不可变性。

2. 自定义Gson TypeAdapter

若需要复杂逻辑,可为Request编写自定义TypeAdapter,在解析完成后手动为派生属性赋值:

class RequestTypeAdapter : TypeAdapter<Request>() {
    private val gson = Gson()
    private val delegateAdapter = gson.getDelegateAdapter(this, TypeToken.get(Request::class.java))

    override fun write(out: JsonWriter, value: Request) {
        delegateAdapter.write(out, value)
    }

    override fun read(`in`: JsonReader): Request {
        val request = delegateAdapter.read(`in`)
        // 注意:需先将派生属性改为var才能赋值
        request.apply {
            // 手动赋值派生属性
            // country = company_address.country
            // ... 其他属性同理
        }
        return request
    }
}

注册TypeAdapter到Gson:

val gson = GsonBuilder()
    .registerTypeAdapter(Request::class.java, RequestTypeAdapter())
    .create()

此方法需要修改派生属性为var,破坏了数据类的不可变性,因此仅在特殊场景下使用。

3. 直接使用嵌套属性

若派生属性仅为方便访问,可直接在代码中通过request.company_address.country替代request.country,省去定义派生属性的步骤,从根源避免问题。


内容的提问来源于stack exchange,提问作者pbuchheit

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.26 12:15:23