- 标准库
- 网络
- 密码学
- 并发编程
【免费下载链接】cangjie_stdx
仓颉编程语言提供了 stdx 模块,该模块提供了网络、安全等领域的通用能力。
本文围绕仓颉标准库扩展(cangjie_stdx)中stdx.encoding.json包提供的ToJson接口展开,系统讲解它如何打通 JSON 数据层(JsonValue)与序列化中间数据层(DataModel)之间的双向转换,并结合 to_json.cj 源码剖析其底层类型映射与递归处理逻辑。读完本文,你将掌握fromJson()/toJson()的调用方式、类型转换规则、异常语义,并能在自定义业务类上落地"JSON 字符串 ↔ 自定义类型"的完整序列化链路。
一、ToJson 接口的定位:连接 JSON 与序列化两套数据模型
在stdx.encoding.json包中,JSON 数据的处理分为两层:
- JsonValue 层:对 JSON 数据格式(object、array、string、number、true、false、null)的封装,具体由
JsonArray、JsonBool、JsonFloat、JsonInt、JsonNull、JsonObject、JsonString等类实现,通过JsonKind枚举(JsNull | JsBool | JsInt | JsFloat | JsString | JsArray | JsObject)标识具体类型,其抽象基类定义见 json_value.cj。 - DataModel 层:
stdx.serialization.serialization包中的中间数据层,DataModelBool、DataModelInt、DataModelFloat、DataModelString、DataModelNull、DataModelSeq、DataModelStruct等子类共同表达任意可序列化对象的结构,定义见 data_model.cj。
ToJson接口正是这两层之间的桥梁,其完整定义位于 to_json.cj:
public interface ToJson { static func fromJson(jv: JsonValue): DataModel func toJson(): JsonValue }整个 JSON 处理链路因此闭合为:String ↔ JsonValue ↔ DataModel ↔ 自定义类型。其中String与JsonValue的互转由JsonValue.fromStr()等方法承担,DataModel与自定义类型的互转由序列化包的Serializable接口承担,而JsonValue与DataModel的互转正是ToJson接口的职责。
二、接口方法详解
static func fromJson(JsonValue):将 JsonValue 还原为 DataModel
static func fromJson(jv: JsonValue): DataModel- 参数:
jv: JsonValue,待转换的 JsonValue。 - 返回值:转换后的
DataModel对象。 - 异常:当
jv无法被转换为 DataModel 时抛出 JsonException。
该方法是静态方法,因此既可以按接口签名直接以类型名调用,也可以在DataModel上以DataModel.fromJson(...)的形式调用(见下文 extend 实现)。
func toJson():将自身转换为 JsonValue
func toJson(): JsonValue- 返回值:转换后的
JsonValue对象。 - 异常:转换失败时抛出 JsonException。
三、extend DataModel <: ToJson:仓库内置的默认实现
接口本身只定义契约,真正开箱即用的能力来自仓库通过扩展(extend)机制为DataModel类型实现的默认转换逻辑:
extend DataModel <: ToJson这意味着任何DataModel实例(或其子类实例)都天然具备toJson()能力,任何调用方都能直接使用DataModel.fromJson(jv)完成还原,无需自行编写样板代码。以下逐一剖析其实现。
3.1 fromJson 的类型映射:一次 match 完成标量转换
从 to_json.cj 的源码可以看到,fromJson对六类JsonValue做了精确的模式匹配:
public static func fromJson(jv: JsonValue): DataModel { match (jv) { case jb: JsonBool => DataModelBool(jb.getValue()) case ji: JsonInt => DataModelInt(ji.getValue()) case jf: JsonFloat => DataModelFloat(jf.getValue()) case js: JsonString => DataModelString(js.getValue()) case ja: JsonArray => buildDMSeq(ja) case jo: JsonObject => buildDMStruct(jo) case _ => DataModelNull() } }映射关系可归纳为下表:
| JsonValue 类型 | 转换目标 | 说明 |
|---|---|---|
JsonBool | DataModelBool | 通过getValue()取出 Bool 值 |
JsonInt | DataModelInt | 通过getValue()取出 Int64 值 |
JsonFloat | DataModelFloat | 通过getValue()取出 Float64 值 |
JsonString | DataModelString | 通过getValue()取出字符串 |
JsonArray | DataModelSeq | 委托buildDMSeq递归转换每个元素 |
JsonObject | DataModelStruct | 委托buildDMStruct递归转换每个字段 |
其余(如JsonNull) | DataModelNull | 兜底转换为空值数据模型 |
3.2 复合类型递归:buildDMSeq 与 buildDMStruct
数组和对象是复合结构,源码通过两个私有辅助函数实现逐元素、逐字段的递归转换:
- buildDMSeq(to_json.cj):遍历
JsonArray的 items,先reserve预分配容量,再对每个元素递归调用fromJson,最终组装为DataModelSeq。 - buildDMStruct(to_json.cj):遍历
JsonObject的字段映射(HashMap<String, JsonValue>),将每个键值对转为Field(name, dataModel),其中嵌套的数组/对象同样递归处理;若出现无法识别的 JsonValue 类型,则抛出JsonException("Internal error: unmatched JsonValue type.")。
3.3 toJson 的类型映射:反向 match 与递归
toJson的反向转换同样基于 match 分发(to_json.cj):
public func toJson(): JsonValue { match (this) { case _: DataModelNull => return JsonNull() case dmb: DataModelBool => return JsonBool(dmb.getValue()) case dmi: DataModelInt => return JsonInt(dmi.getValue()) case dmf: DataModelFloat => return JsonFloat(dmf.getValue()) case dmstr: DataModelString => return JsonString(dmstr.getValue()) case dmseq: DataModelSeq => return toJson(dmseq) case dms: DataModelStruct => return toJson(dms) case _ => throw JsonException("Internal error: unmatched DataModel type.") } }对应关系为:DataModelNull → JsonNull、DataModelBool → JsonBool、DataModelInt → JsonInt、DataModelFloat → JsonFloat、DataModelString → JsonString、DataModelSeq → JsonArray、DataModelStruct → JsonObject。
复合类型同样递归展开:
toJson(dmseq: DataModelSeq)(to_json.cj):遍历DataModelSeq的 items,对每个元素调用其toJson(),填入新建的JsonArray。toJson(dms: DataModelStruct)(to_json.cj):遍历DataModelStruct的字段,取字段名与字段数据的toJson()结果,通过JsonObject.put(name, value)组装。
值得注意的是,两处兜底分支都抛出JsonException并携带"Internal error: unmatched ..."提示,这从源码层面印证了接口文档中"转换失败抛出 JsonException"的约定。
四、接口自带示例:两种方向的最小可运行代码
4.1 从 JsonValue 还原为 DataModel
下面示例演示了"先手工构造 JsonObject,再通过DataModel.fromJson还原为 DataModel,最后用序列化包反序列化回HashMap"的完整流程:
import stdx.serialization.serialization.* import stdx.encoding.json.* import std.collection.* main() { var fields = HashMap<String, JsonValue>() fields.add("name", JsonString("张三")) fields.add("tel", JsonString("10000000000")) // 使用HashMap创建新的JsonValue对象 var jsonObject: JsonObject = JsonObject(fields) // JsonValue还原回DataModel let dataModel = DataModel.fromJson(jsonObject) let data = HashMap<String, String>.deserialize(dataModel) println("数据: ${data}") return 0 }运行结果:
数据: [(name, 张三), (tel, 10000000000)]这段代码的价值在于揭示了三种能力如何衔接:JsonObject负责表达 JSON 对象结构,DataModel.fromJson将其转换为中间数据层,HashMap<String, String>.deserialize(来自序列化包)将中间数据层还原为业务数据结构。
4.2 从 DataModel 转换为 JsonValue
下面示例演示反向流程:先用DataModelSeq表达一个序列(列表),再调用toJson()得到JsonValue,最后通过kind()断言类型并用toJsonString()输出格式化 JSON:
import stdx.serialization.serialization.* import std.collection.* import stdx.encoding.json.* main() { // 创建一个ArrayList并添加一些DataModel let list = ArrayList<DataModel>() list.add(DataModelString("元素1")) list.add(DataModelString("元素2")) // 使用ArrayList创建DataModelSeq实例 let dataModel = DataModelSeq(list) // 将DataModelSeq转换为JsonValue let jsonValue = dataModel.toJson() let kind = jsonValue.kind() match (kind) { case JsonKind.JsArray => println("JsonValue的类型是JsArray") case _ => println("JsonValue的类型不是JsArray") } println("JsonValue: ${jsonValue.toJsonString()}") return 0 }运行结果:
JsonValue的类型是JsArray JsonValue: [ "元素1", "元素2" ]该示例同时展示了JsonKind枚举的典型用法——配合match表达式对JsonValue的实际类型做运行时分支处理,这也是 JsonKind 枚举文档 与接口文档相辅相成之处。
五、实战:JSON 字符串与自定义类型的完整互转
接口文档之外,仓库还提供了完整实战用例 to_json_sample.md:为Person类型实现Serializable接口,打通"JSON 字符串 → 自定义类型 → JSON 字符串"的闭环。其核心流程分四步:
- 定义可序列化类型:
Person、Location实现Serializable<Person>/Serializable<Location>,通过serialize()产出DataModelStruct,通过deserialize(dm)从DataModelStruct取字段还原对象。 - JSON 字符串解析为 JsonValue:调用
JsonValue.fromStr(jsonString)。 - JsonValue 还原为 DataModel:调用
DataModel.fromJson(jsonValue),即本文主题接口。 - DataModel 反序列化为对象:调用
Person.deserialize(dataModel)。
反向(序列化)路径则为:person.serialize()得到 DataModel →serializedDataModel.toJson()得到 JsonValue →.asObject()转成JsonObject→toJsonString()输出格式化 JSON。运行结果如下:
反序列化结果: 姓名: A 年龄: 30 国家: China 省份: Beijing 序列化结果: { "name": "A", "age": 30, "loc": { "country": "China", "province": "Beijing" } }建议将本示例与本文 4.1、4.2 两个最小示例对照阅读:最小示例帮助你理解ToJson两个方法的直接用法,实战示例则展示了它在真实业务对象(含嵌套结构)上的端到端价值。
六、异常与边界情况
转换过程中的异常统一由 JsonException 表达,它继承自Exception,支持无参构造与带消息构造。结合源码可以归纳出三类典型抛错场景:
- 类型不匹配:调用
asObject()、asArray()等强转方法时,若JsonValue实际类型不符,会抛出JsonException("Failed to convert JsonValue to ...")(见 json_value.cj)。 - 内部类型无法匹配:
fromJson/toJson的 match 兜底分支抛出JsonException("Internal error: unmatched ..."),从源码看这属于防御性分支,正常数据不会触发。 - 序列化异常:例如 JSON 浮点数为无穷值时,
jsonFloatToString会抛出JsonException("Failed to serialize JSON float.")(见 json_object.cj)。
实际编码中建议对跨层转换调用(尤其是解析外部输入的 JSON 字符串)使用try-catch包裹,避免未捕获异常导致程序中断。
七、小结:ToJson 在 JSON 处理体系中的位置
回顾 json 包概览 的能力边界——"json 包用于对 JSON 数据的处理,实现 String、JsonValue、DataModel 之间的相互转换"——可以看到:
String ↔ JsonValue由JsonValue.fromStr()与各类toJsonString()承担;DataModel ↔ 自定义类型由序列化包的Serializable接口承担;JsonValue ↔ DataModel由本文的ToJson接口承担,其默认实现extend DataModel <: ToJson已在 to_json.cj 中完整提供。
掌握ToJson接口,就等于掌握了仓颉 JSON 处理链路的"中间枢纽",无论是解析外部 JSON 构造业务对象,还是把业务对象序列化输出为 JSON,都可以借助DataModel.fromJson(jv)与dm.toJson()一行代码完成跨层转换,剩余的类型安全与递归细节交给仓库实现即可。
- 标准库
- 网络
- 密码学
- 并发编程
【免费下载链接】cangjie_stdx
仓颉编程语言提供了 stdx 模块,该模块提供了网络、安全等领域的通用能力。
相关推荐
仓颉 stdx.encoding.json 包详解:JsonValue 与 DataModel 的 JSON 数据转换实战指南
仓颉 stdx.encoding.json 包详解:JsonValue 与 DataModel 的 JSON 数据转换实战指南 导读 stdx.encoding
标准库网络密码学并发编程仓颉 stdx 实战:JsonValue 与 DataModel 双向转换,实现自定义类型的 JSON 序列化与反序列化
仓颉 stdx 实战:JsonValue 与 DataModel 双向转换,实现自定义类型的 JSON 序列化与反序列化 本文基于 cangjie_stdx 仓
标准库网络密码学并发编程仓颉 stdx.encoding.json 核心类详解:JsonValue 体系、数组/对象操作与格式化输出实战
仓颉 stdx.encoding.json 核心类详解:JsonValue 体系、数组/对象操作与格式化输出实战 本文以 cangjie_stdx 仓库中 st
标准库网络密码学并发编程
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考