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
相关产品推荐
相关产品推荐

