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

Kotlin JSON解析能否用JsonPath实现字段绑定与结构扁平化?

Kotlin JSON序列化:扁平化映射与特殊字段处理方案

需求梳理

你需要将嵌套结构的JSON序列化为Kotlin数据类,核心要求包括:

  • 扁平化字段:部分字段来自JSON顶层(如key),部分来自fields子对象(如created、issuetype.name)
  • 特殊数组处理:将customfield_10471数组中的每个字符串按空格分割后取第一个元素,生成serviceRequests列表
  • 替代实现:不想用when表达式,希望用类似JsonPath的字段绑定方式,或尝试两次绑定合并实例

可行解决方案

方案1:Kotlinx Serialization 自定义序列化器(原生推荐)

利用Kotlinx Serialization的自定义反序列化逻辑,直接按JsonPath风格映射字段路径,无需大量when分支:

import kotlinx.serialization.*
import kotlinx.serialization.descriptors.*
import kotlinx.serialization.encoding.*
import kotlinx.serialization.json.*
import java.time.Instant
import java.time.Duration

@Serializable
data class MajorIncident(
    @SerialName("key")
    val id: String,
    val created: Instant,
    val pillar: String,
    val impactBreadth: String?,
    val duration: Duration,
    val detectionSource: String,
    val updated: Instant,
    val assignee: String?,
    val serviceRequests: List<String>?,
    @SerialName("summary")
    val title: String,
    val type: String
)

object MajorIncidentDeserializer : DeserializationStrategy<MajorIncident> {
    override val descriptor: SerialDescriptor = buildClassSerialDescriptor("MajorIncident") {
        element<String>("id")
        element<Instant>("created")
        // 按需添加其他字段描述
    }

    override fun deserialize(decoder: Decoder): MajorIncident {
        val rootJson = decoder.decodeJsonElement().jsonObject
        val fieldsJson = rootJson["fields"]!!.jsonObject

        return MajorIncident(
            id = rootJson["key"]!!.jsonPrimitive.content,
            created = Instant.parse(fieldsJson["created"]!!.jsonPrimitive.content),
            pillar = fieldsJson["customfield_10443"]!!.jsonObject["value"]!!.jsonPrimitive.content,
            impactBreadth = null, // 按需补充默认值或解析逻辑
            duration = Duration.ofMinutes(fieldsJson["customfield_13600"]!!.jsonPrimitive.int.toLong()),
            detectionSource = fieldsJson["customfield_10900"]!!.jsonObject["value"]!!.jsonPrimitive.content,
            updated = Instant.parse(fieldsJson["updated"]!!.jsonPrimitive.content),
            assignee = fieldsJson["assignee"]?.jsonPrimitive?.content,
            serviceRequests = fieldsJson["customfield_10471"]?.jsonArray?.map {
                it.jsonPrimitive.content.split(" ")[0]
            },
            title = fieldsJson["summary"]!!.jsonPrimitive.content,
            type = fieldsJson["issuetype"]!!.jsonObject["name"]!!.jsonPrimitive.content
        )
    }
}

// 使用示例
val jsonStr = """你的JSON字符串"""
val incident = Json.decodeFromString(MajorIncidentDeserializer, jsonStr)

方案2:Jackson + JsonPath注解(简洁直观)

如果项目使用Jackson,可以直接用@JsonPath注解实现字段路径映射,完美匹配你的需求:

import com.fasterxml.jackson.annotation.JsonProperty
import com.fasterxml.jackson.databind.annotation.JsonDeserialize
import com.fasterxml.jackson.datatype.jsr310.deser.InstantDeserializer
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import java.time.Instant
import java.time.Duration

data class MajorIncident(
    @JsonProperty("key")
    val id: String,
    @com.fasterxml.jackson.annotation.JsonPath("$.fields.created")
    @JsonDeserialize(using = InstantDeserializer::class)
    val created: Instant,
    @com.fasterxml.jackson.annotation.JsonPath("$.fields.customfield_10443.value")
    val pillar: String,
    val impactBreadth: String?,
    @com.fasterxml.jackson.annotation.JsonPath("$.fields.customfield_13600")
    val rawDuration: Int,
    @com.fasterxml.jackson.annotation.JsonPath("$.fields.customfield_10900.value")
    val detectionSource: String,
    @com.fasterxml.jackson.annotation.JsonPath("$.fields.updated")
    @JsonDeserialize(using = InstantDeserializer::class)
    val updated: Instant,
    @com.fasterxml.jackson.annotation.JsonPath("$.fields.assignee")
    val assignee: String?,
    @com.fasterxml.jackson.annotation.JsonPath("$.fields.customfield_10471")
    val rawServiceRequests: List<String>?,
    @com.fasterxml.jackson.annotation.JsonPath("$.fields.summary")
    val title: String,
    @com.fasterxml.jackson.annotation.JsonPath("$.fields.issuetype.name")
    val type: String
)

// 扩展函数处理特殊字段转换
fun MajorIncident.process(): MajorIncident {
    return copy(
        duration = Duration.ofMinutes(rawDuration.toLong()),
        serviceRequests = rawServiceRequests?.map { it.split(" ")[0] }
    )
}

// 使用示例
val mapper = jacksonObjectMapper()
val incident = mapper.readValue<MajorIncident>(jsonStr).process()

方案3:两次绑定合并实例(拆分逻辑)

先分别解析顶层和fields子对象,再合并为目标数据类,适合字段较多、拆分逻辑清晰的场景:

import kotlinx.serialization.*
import kotlinx.serialization.json.*
import java.time.Instant
import java.time.Duration

@Serializable
data class TopLevelData(val key: String)

@Serializable
data class FieldsData(
    val created: String,
    val customfield_10443: CustomField,
    val customfield_13600: Int,
    val customfield_10900: CustomField,
    val updated: String,
    val assignee: String?,
    val customfield_10471: List<String>?,
    val summary: String,
    val issuetype: IssueType
)

@Serializable data class CustomField(val value: String)
@Serializable data class IssueType(val name: String)

// 合并逻辑函数
fun merge(top: TopLevelData, fields: FieldsData): MajorIncident {
    return MajorIncident(
        id = top.key,
        created = Instant.parse(fields.created),
        pillar = fields.customfield_10443.value,
        impactBreadth = null,
        duration = Duration.ofMinutes(fields.customfield_13600.toLong()),
        detectionSource = fields.customfield_10900.value,
        updated = Instant.parse(fields.updated),
        assignee = fields.assignee,
        serviceRequests = fields.customfield_10471?.map { it.split(" ")[0] },
        title = fields.summary,
        type = fields.issuetype.name
    )
}

// 使用示例
val rootJson = Json.parseToJsonElement(jsonStr).jsonObject
val topData = Json.decodeFromJsonElement<TopLevelData>(rootJson)
val fieldsData = Json.decodeFromJsonElement<FieldsData>(rootJson["fields"]!!)
val incident = merge(topData, fieldsData)

方案对比

  • 自定义序列化器:Kotlinx Serialization原生支持,无额外依赖,逻辑集中,适合纯Kotlinx项目
  • Jackson JsonPath:注解方式简洁,字段映射直观,适合已有Jackson依赖的项目
  • 两次绑定合并:拆分解析逻辑,降低单个序列化器复杂度,适合字段极多或需分步处理的场景

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.12 14:41:31