讲师中心 微信公众号
AI工具推荐 视频效率加速

如何使用 Moshi 处理含固定字段与动态键的 JSON 反序列化

酷明君_7873

酷明君_7873

发布时间:2026-08-15 18:55:06

|

328人浏览过

|

来源于php中文网

原创

如何使用 Moshi 处理含固定字段与动态键的 JSON 反序列化

本文详解如何在 Moshi 中为混合结构(如固定 attributes 字段 + 任意其他键值对)设计自定义适配器,实现将动态属性统一捕获到 Map 中,弥补 Moshi 缺乏 @JsonAnySetter 的限制。

本文详解如何在 moshi 中为混合结构(如固定 `attributes` 字段 + 任意其他键值对)设计自定义适配器,实现将动态属性统一捕获到 `map` 中,弥补 moshi 缺乏 `@jsonanysetter` 的限制。

在实际开发中,尤其是对接 Salesforce、REST APIs 或遗留系统时,常会遇到「半结构化」JSON:其中一部分字段(如 attributes)语义明确、结构固定;其余字段则高度动态、键名不可预知(如 Id、Name、自定义字段等)。Moshi 默认不支持类似 Jackson 的 @JsonAnySetter,但可通过手动编写类型适配器(Type Adapter) 精准控制反序列化逻辑,兼顾健壮性与灵活性。

以下是一个生产就绪的解决方案,核心在于利用 JsonReader.Options 高效识别已知字段,并将剩余字段统一读取为 Map<string any></string>:

Expanso json-flatten
Expanso json-flatten

通过 CLI 或 MCP 服务器,使用 Expanso Edge 管道扁平化嵌套 JSON 对象。

下载

✅ 定义数据模型(保持简洁)

@JsonClass(generateAdapter = true)
data class Body(
  val records: List<Record>
)

@JsonClass(generateAdapter = true)
data class Record(
  val attributes: Attributes,
  val recordBody: Map<String, Any>
)

@JsonClass(generateAdapter = true)
data class Attributes(
  val type: String,
  val url: String
)

⚠️ 注意:recordBody 类型为 Map<string any></string>,意味着它能容纳任意嵌套 JSON(对象、数组、字符串、数字等),这依赖于 Moshi 的 readJsonValue() 方法——它是 Moshi 原生支持的通用 JSON 解析能力。

✅ 实现健壮的自定义适配器

object RecordAdapter {
  private val options = JsonReader.Options.of("attributes")

  @FromJson
  fun fromJson(reader: JsonReader, attributesAdapter: JsonAdapter<Attributes>): Record {
    reader.beginObject()
    var attributes: Attributes? = null
    val recordBody = mutableMapOf<String, Any>()

    while (reader.hasNext()) {
      when (reader.selectName(options)) {
        0 -> { // 匹配到 "attributes"
          if (attributes != null) throw JsonDataException("Duplicate 'attributes' field")
          attributes = attributesAdapter.fromJson(reader) ?: throw JsonDataException("'attributes' cannot be null")
        }
        -1 -> { // 未匹配任何已知字段 → 动态键
          val key = reader.nextName()
          val value = reader.readJsonValue() ?: throw JsonDataException("Unexpected null value for key '$key'")
          recordBody[key] = value
        }
        else -> throw AssertionError("Unexpected selectName result")
      }
    }
    reader.endObject()
    attributes ?: throw JsonDataException("'attributes' field is required but missing")
    return Record(attributes, recordBody)
  }

  @ToJson
  fun toJson(
    writer: JsonWriter,
    value: Record,
    attributesAdapter: JsonAdapter<Attributes>,
    dynamicAdapter: JsonAdapter<Any>
  ) {
    writer.beginObject()
    writer.name("attributes")
    attributesAdapter.toJson(writer, value.attributes)
    value.recordBody.forEach { (key, v) ->
      writer.name(key)
      dynamicAdapter.toJson(writer, v)
    }
    writer.endObject()
  }
}

关键设计说明:

  • JsonReader.Options.of("attributes"):声明仅需显式识别 "attributes" 字段,其余全部视为动态键。
  • selectName() + nextName() 组合:先尝试匹配已知字段;若返回 -1,说明是未知键,再调用 nextName() 获取键名,readJsonValue() 安全解析任意类型值。
  • 空值防护:显式校验 attributes 是否存在、readJsonValue() 返回值是否为 null,避免静默失败。
  • 双向支持:@ToJson 方法确保序列化时也能正确还原原始 JSON 结构(固定字段前置 + 动态字段后置)。

✅ 注册并使用适配器

val moshi = Moshi.Builder()
  .add(RecordAdapter) // 注册自定义适配器
  .build()

val bodyAdapter = moshi.adapter<Body>(Body::class.java)

// 示例 JSON(含动态字段 Id/Name/...)
val json = """{
  "records": [{
    "attributes": {"type": "Account", "url": "/services/data/vXX.X/sobjects/Account/001..."},
    "Id": "001...",
    "Name": "Acme Corp",
    "Industry": "Technology",
    "CustomField__c": true
  }]
}"""

val body = bodyAdapter.fromJson(json)!!
println(body.records.first().recordBody) 
// 输出: {Id=001..., Name=Acme Corp, Industry=Technology, CustomField__c=true}

? 注意事项与最佳实践

  • 性能考量:readJsonValue() 会完整解析子树为 Kotlin 对象(如 Map、List、String 等),适用于中低频场景;若需极致性能且动态字段类型简单(如全为字符串),可改用 nextString() 等专用方法。
  • 类型安全增强:若动态字段有部分已知模式,可在 recordBody 后追加扩展函数或封装类(如 Record.withDynamic<t>(key: String): T?</t>),提升调用侧安全性。
  • 错误定位:在 fromJson 中尽早抛出带上下文信息的异常(如字段名、位置),便于排查上游数据异常。
  • Moshi 版本兼容性:本方案适用于 Moshi 1.14+(readJsonValue() 为稳定 API);旧版本请升级或使用 JsonReader.peek() + 手动分支判断。

通过该方案,你无需妥协数据灵活性,也无需放弃 Moshi 的编译时类型安全与轻量优势——真正实现「结构化字段强校验 + 动态字段自由捕获」的完美平衡。

热门AI工具

更多
二狗PPT
二狗PPT Hot

一款AI演示文稿工具,主要用于专为中式职场打造的AI PPT生成工具,适合需要提升相关任务效率的用户。

SkildArt
SkildArt Hot

SkildArt是一款AI文本写作工具,一站式 AI 视觉创作平台。

WorkBuddy

一款AI办公效率工具,主要用于腾讯云推出的AI原生桌面智能体工作台,适合需要提升相关任务效率的用户。

Atoms
Atoms Hot

Atoms是一款AI智能体工具,第一支自动构建真实业务的 AI 团队。

豆包大模型

豆包大模型是一款由字节跳动推出的企业级大语言模型服务平台。

Laper
Laper Hot

Laper是专为编剧、导演和制片人推出的 AI 原生剧本创作工具。

DeepSeek

DeepSeek是一款面向对话、写作、编程和推理场景的AI大模型工具。

UpDream
UpDream Hot

一款AI视频创作工具,主要用于哔哩哔哩推出的自研AI视频创作工具,适合需要提升相关任务效率的用户。

立刻MV
立刻MV Hot

立刻MV是一款AI文本写作工具,AI 音乐视频(MV)创作工具。

相关专题

更多
js正则表达式
js正则表达式

php中文网为大家提供各种js正则表达式语法大全以及各种js正则表达式使用的方法,还有更多js正则表达式的相关文章、相关下载、相关课程,供大家免费下载体验。

4816

2023.06.20

js获取当前时间
js获取当前时间

JS全称JavaScript,是一种具有函数优先的轻量级,解释型或即时编译型的编程语言;它是一种属于网络的高级脚本语言,主要用于Web,常用来为网页添加各式各样的动态功能。js怎么获取当前时间呢?php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

1415

2023.07.28

js 字符串转数组
js 字符串转数组

js字符串转数组的方法:1、使用“split()”方法;2、使用“Array.from()”方法;3、使用for循环遍历;4、使用“Array.split()”方法。本专题为大家提供js字符串转数组的相关的文章、下载、课程内容,供大家免费下载体验。

1878

2023.08.03

js是什么意思
js是什么意思

JS是JavaScript的缩写,它是一种广泛应用于网页开发的脚本语言。JavaScript是一种解释性的、基于对象和事件驱动的编程语言,通常用于为网页增加交互性和动态性。它可以在网页上实现复杂的功能和效果,如表单验证、页面元素操作、动画效果、数据交互等。

10343

2023.08.17

js删除节点的方法
js删除节点的方法

js删除节点的方法有:1、removeChild()方法,用于从父节点中移除指定的子节点,它需要两个参数,第一个参数是要删除的子节点,第二个参数是父节点;2、parentNode.removeChild()方法,可以直接通过父节点调用来删除子节点;3、remove()方法,可以直接删除节点,而无需指定父节点;4、innerHTML属性,用于删除节点的内容。

1020

2023.09.01

js截取字符串的方法
js截取字符串的方法

js截取字符串的方法有substring()方法、substr()方法、slice()方法、split()方法和slice()方法。本专题为大家提供字符串相关的文章、下载、课程内容,供大家免费下载体验。

3024

2023.09.04

Js中concat和push的区别
Js中concat和push的区别

Js中concat和push的区别:1、concat用于将两个或多个数组合并成一个新数组,并返回这个新数组,而push用于向数组的末尾添加一个或多个元素,并返回修改后的数组的新长度;2、concat不会修改原始数组,是创建新的数组,而push会修改原数组,将新元素添加到原数组的末尾等等。本专题为大家提供concat和push相关的文章、下载、课程内容,供大家免费下载体验。

1649

2023.09.14

js截取字符串的方法介绍
js截取字符串的方法介绍

JavaScript字符串截取方法,包括substring、slice、substr、charAt和split方法。这些方法可以根据具体需求,灵活地截取字符串的不同部分。在实际开发中,根据具体情况选择合适的方法进行字符串截取,能够提高代码的效率和可读性 。

5209

2023.09.21

DBeaver怎么导入数据
DBeaver怎么导入数据

DBeaver数据导入专题,整理Excel、CSV、SQL、TXT等常见文件的数据导入方法,涵盖目标数据库选择、数据表创建、字段映射、数据类型设置及导入参数配置等操作,帮助用户将本地文件中的数据写入数据库,并了解批量导入、重复数据处理和导入失败等常见问题的解决方法。

20

2026.11.05

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
WEB前端教程【HTML5+CSS3+JS】
WEB前端教程【HTML5+CSS3+JS】

共101课时 | 21.3万人学习

JS进阶与BootStrap学习
JS进阶与BootStrap学习

共39课时 | 4.9万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn