iOS开发 - Swift Codable协议实战:快速、简单、高效地完成JSON和Model转换!
前言
Codable
是 Swift 4.0
引入的一种协议,它是一个组合协议,由 Decodable
和 Encodable
两个协议组成。它的作用是将模型对象转换为 JSON 或者是其它的数据格式,也可以反过来将 JSON 数据转换为模型对象。
Encodable
和 Decodable
分别定义了 encode(to:)
和 init(from:)
两个协议函数,分别用来实现数据模型的归档和外部数据的解析和实例化。最常用的场景就是刚提到的 JSON 数据与模型的相互转换,但是 Codable 的能力并不止于此。
简单应用
在实际开发中,Codable
的使用非常方便,只需要让模型遵循 Codable
协议即可:
struct GCPerson: Codable {var name: Stringvar age: Intvar height: Float // cmvar isGoodGrades: Bool
}
接下来编写数据编码和解码的方法:
func encodePerson() {let person = GCPerson(name: "XiaoMing", age: 16, height: 160.5, isGoodGrades: true)let encoder = JSONEncoder()encoder.outputFormatting = .prettyPrinted // 优雅永不过时,json会好看点哟do {let data = try encoder.encode(person)let jsonStr = String(data: data, encoding: .utf8)textView.text = jsonStrprint(jsonStr as Any)} catch let err {print("err", err)}
}func decodePerson() {let jsonStr = "{\"age\":16,\"isGoodGrades\":1,\"name\":\"XiaoMing\",\"height\":160.5}"guard let data = jsonStr.data(using: .utf8) else {print("get data fail")return}let decoder = JSONDecoder()do {let person = try decoder.decode(GCPerson.self, from: data)print(person)} catch let err {print("err", err)}
}
上面例子的输出:
Optional("{\n \"age\" : 16,\n \"isGoodGrades\" : true,\n \"name\" : \"XiaoMing\",\n \"height\" : 160.5\n}")
GCPerson(name: "XiaoMing", age: 16, height: 160.5, isGoodGrades: false)
应该有眼尖的童鞋是发现了,我将 JSONEncoder
的 outputFormatting
设置为了 prettyPrinted
,这会让它输出的时候会美观一下,比如将它们放置在 UITextView
视图中作对比:
这里指的
default
是在没有设置outputFormatting
的默认情况
CodingKeys 字段映射
如果属性名称与 JSON 数据中的键名不一致,需要使用 Swift
语言中的 CodingKeys
枚举来映射属性名称和键名。CodingKeys
是一个遵循了 CodingKey
协议的枚举,它可以用来描述 Swift
对象的属性与 JSON 数据中的键名之间的映射关系。
struct Address: Codable {var zipCode: Intvar fullAddress: Stringenum CodingKeys: String, CodingKey {case zipCode = "zip_code"case fullAddress = "full_address"}
}
数据编码和解码的方法与前面的大同小异:
func encodeAddress() {let address = Address(zipCode: 528000, fullAddress: "don't tell you")let encoder = JSONEncoder()encoder.outputFormatting = .prettyPrinted // 优雅永不过时,json会好看点哟do {let data = try encoder.encode(address)let jsonStr = String(data: data, encoding: .utf8)textView.text.append("\n\n")textView.text = textView.text.appending(jsonStr ?? "")print(jsonStr as Any)} catch let err {print("err", err)}
}func decodeAddress() {let jsonStr = "{\"zip_code\":528000,\"full_address\":\"don't tell you\"}"guard let data = jsonStr.data(using: .utf8) else {print("get data fail")return}let decoder = JSONDecoder()do {let address = try decoder.decode(Address.self, from: data)print(address)} catch let err {print("err", err)}
}
此时的输出为:
Optional("{\n \"zip_code\" : 528000,\n \"full_address\" : \"don\'t tell you\"\n}")
Address(zipCode: 528000, fullAddress: "don\'t tell you")
从控制台日志可以看出,Address
模型中的的 zipCode
和 fullAddress
属性字段已被替换为 zip_code
和 full_address
,值得注意的是,使用 CodingKeys
映射后就只能使用映射后的字段名称。
数据类型匹配
Swift
中的数据类型需要与 JSON 数据中的数据类型匹配,否则将无法正确地进行解码。如果数据类型不匹配,则会进入到 catch
代码块,意味着解码失败。
let jsonStr = "{\"age\":16,\"isGoodGrades\":1,\"name\":\"XiaoMing\",\"height\":160.5}"
在上面的例子中,将 isGoodGrades 的值改为1,此时输出的错误内容为:
err typeMismatch(Swift.Bool, Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "isGoodGrades", intValue: nil)], debugDescription: "Expected to decode Bool but found a number instead.", underlyingError: nil))
由此引出,Bool
型只支持 true
和 false
,其它一概不认。
注意:只要是其中一个数据字段不能解析,则整条解析失败。
Date 和 Optional 可选类型
在使用 Codable
对 Date 和 Optional 属性进行编解码时,有些细节是需要了解的。
Codable
默认启用的时间策略是 deferredToDate
,即从 UTC时间2001年1月1日0时0分0秒
开始的秒数,对应 Date
类型中 timeIntervalSinceReferenceDate
这个属性。比如 702804983.44863105
这个数字解析后的结果是 2023-04-10 07:34:17 +0000
。
在这儿把时间策略设置为 secondsSince1970
,因为这个会比上面的要常用。我们需将 JSONEncoder
的 dateEncodingStrategy
设置为 secondsSince1970
,JSONDecoder
也是相同的设置。
在设置 Optional
可选类型时,在编码时,为空的属性不会包含在 JSON 数据中。在解码时,直接不传或将值设定为 \"null\"
/ \"nil\"
/ null
这三种值也能被解析为 nil
。
struct Activity: Codable {var time: Datevar url: URL?
}
编码解码的工作:
func encodeActivity() {let activity = Activity(time: Date(), url: URL(string: "https://www.baidu.com"))let encoder = JSONEncoder()encoder.outputFormatting = .prettyPrinted // 优雅永不过时,json会好看点哟encoder.dateEncodingStrategy = .secondsSince1970 // 秒do {let data = try encoder.encode(activity)let jsonStr = String(data: data, encoding: .utf8)textView.text.append("\n\n")textView.text = textView.text.appending(jsonStr ?? "")print(jsonStr as Any)} catch let err {print("err", err)}
}func decodeActivity() {
// let jsonStr = "{\"time\":528000,\"url\":111}" // 即便是 Optional 的属性也要对应的数据类型,否则还是会解析失败let jsonStr = "{\"time\":1681055185}" // Optional类型的属性字段,直接不传也是nil// let jsonStr = "{\"time\":528000,\"url\":null}" // 以下三种也能被解析为nil,\"null\" / \"nil\" / nullguard let data = jsonStr.data(using: .utf8) else {print("get data fail")return}let decoder = JSONDecoder()decoder.dateDecodingStrategy = .secondsSince1970 // 秒do {let activity = try decoder.decode(Activity.self, from: data)print(activity)} catch let err {print("err", err)}
}
此时的输出为:
Optional("{\n \"url\" : \"https:\\/\\/www.baidu.com\",\n \"time\" : 1681057020.835813\n}")
Activity(time: 2023-04-09 15:46:25 +0000, url: nil)
自定义编解码
有时候前后端定义的模型不同时,有可能会需要用到自定义编解码,以此来达成“统一”。
比如我们现在有一个 Dog 模型,sex 字段为 Bool 型,在后端的定义为 0 和 1,此时我们需要将它们给转换起来,可以是 false 为 0,true 为 1。
struct Dog: Codable {var name: Stringvar sex: Bool // 0/false女 1/true男init(name: String, sex: Bool) {self.name = nameself.sex = sex}// 必须实现此枚举,在编码解码方法中需要用到enum CodingKeys: CodingKey {case namecase sex}init(from decoder: Decoder) throws {let container = try decoder.container(keyedBy: CodingKeys.self)self.name = try container.decode(String.self, forKey: .name)// 取出来int后再转换为Boollet sexInt = try container.decode(Int.self, forKey: .sex)sex = sexInt == 1}func encode(to encoder: Encoder) throws {var container = encoder.container(keyedBy: CodingKeys.self)try container.encode(self.name, forKey: .name)// 将sex属性以int类型编码try container.encode(sex ? 1 : 0, forKey: .sex)}
}
在编码的时候将 sex 从 Bool 型转换为 Int 型,解码时则反过来。编解码的工作依旧与前面的大致一样:
func encodeDog() {let dog = Dog(name: "Max", sex: true)let encoder = JSONEncoder()encoder.outputFormatting = .prettyPrinted // 优雅永不过时,json会好看点哟do {let data = try encoder.encode(dog)let jsonStr = String(data: data, encoding: .utf8)textView.text.append("\n\n")textView.text = textView.text.appending(jsonStr ?? "")print(jsonStr as Any)} catch let err {print("err", err)}
}func decodeDog() {let jsonStr = "{\"name\":\"Max\",\"sex\":1}"guard let data = jsonStr.data(using: .utf8) else {print("get data fail")return}let decoder = JSONDecoder()do {let dog = try decoder.decode(Dog.self, from: data)print(dog)} catch let err {print("err", err)}
}
此时的日志输出为:
Optional("{\n \"name\" : \"Max\",\n \"sex\" : 1\n}")
Dog(name: "Max", sex: true)
总结
Codable
是 Swift
中非常方便的一个协议,可以帮助我们快速进行数据的编码和解码,提高了开发效率和代码可读性。当然使用不当也会造成严重的灾难,所以我为大家整理了以下几点使用时的注意事项,希望能对大家有所帮助:
- 嵌套的数据结构也需要遵循
Codable
协议。 Bool
型只支持true
或false
。Optional
类型修饰的属性字段,直接不传是nil
,或将值设定为以下三种也能被解析为nil
,\"null\"
/\"nil\"
/null
。- 可以使用自定义的编码器和解码器来进行转换。
相关文章:

iOS开发 - Swift Codable协议实战:快速、简单、高效地完成JSON和Model转换!
前言 Codable 是 Swift 4.0 引入的一种协议,它是一个组合协议,由 Decodable 和 Encodable 两个协议组成。它的作用是将模型对象转换为 JSON 或者是其它的数据格式,也可以反过来将 JSON 数据转换为模型对象。 Encodable 和 Decodable 分别定…...

RabbitMq:Topic exchange(主题交换机)的理解和使用
RabbitMq:Topic exchange(主题交换机)的理解和使用 在RabbitMq中,生产者的消息都是通过交换机来接收,然后再从交换机分发到不同的队列中去,在分发的过程中交换机类型会影响分发的逻辑,下面主要讲解一下主题交换机。 主题交换…...

汽车级36V、4A同步降压转换器MAX20404AFOD/VY、MAX20404AFOC/VY、MAX20404AFOA/VY开关稳压器
MAX20404是小型同步降压转换器,集成了高端和低端开关。这些IC均设计为可在3V到36V的宽输入电压范围内提供高达4A的电流。电压质量可以通过观察PGOOD信号来监测。该器件可以在99%的占空比下运行,非常适合汽车和工业应用。 MAX20404提供可编程输出电压或5…...

C++------利用C++实现二叉搜索树【数据结构】
文章目录 二叉搜索树概念二叉搜索树的操作查找插入删除 二叉搜索树的应用 二叉搜索树 概念 什么是二叉搜索树,二叉搜索树就是指左孩子永远比根小右孩子永远比根大。这个规则适用于所有的子树。 上面的就是一棵二叉搜索树,我们还可以发现这棵树走一个中…...

HotSpot虚拟机之内存模型与线程安全
目录 一、线程内存模型 1. 内存模型 2. 内存模型操作 二、Happens-Before原则 三、Java线程 1. 线程实现方式 2. Java线程状态 四、Java线程安全 1. 线程安全程度 2. 锁优化 五、参考资料 一、线程内存模型 1. 内存模型 内存模型主要目的是定义共享变量的访问规则&…...

TiDB 多集群告警监控-中章-融合多集群 Grafana
作者: longzhuquan 原文来源: https://tidb.net/blog/ac730b0f 背景 随着公司XC改造步伐的前进,越来越多的业务选择 TiDB,由于各个业务之间需要物理隔离,避免不了的 TiDB 集群数量越来越多。虽然每套 TiDB 集群均有…...

【图像分类】基于卷积神经网络和主动学习的高光谱图像分类(Matlab代码实现)
💥💥💞💞欢迎来到本博客❤️❤️💥💥 🏆博主优势:🌞🌞🌞博客内容尽量做到思维缜密,逻辑清晰,为了方便读者。 ⛳️座右铭&a…...
notepad++ verilog关键字自动补全
新建verilog.xml放在安装目录下 D:\Program Files (x86)\Notepad\autoCompletion <?xml version"1.0" encoding"Windows-1252" ?> <NotepadPlus><AutoComplete><KeyWord name"accept_on" /><KeyWord name"a…...

C语言知识
C语言知识 链接 C语言中的数组初始化是有三种形式的,分别是: (1)数据类型 数组名称[长度n] {元素1,元素2…元素n}; (2)数据类型 数组名称[] {元素1,元素2…元素n}; (3)数据类型 数组名称[长度n]; 数组名称[0] 元素1; 数组名称[1] 元素2; 数组…...
数据结构基础
将节点构建成树 数据的结构逻辑结构集合线性结构树形结构图状结构 存储结构合理的创建标题,有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表创建一个表格设定内容居中、居左、居右SmartyPants 创建一个自定义列表如…...
深度学习中数据处理相关的技巧
文章目录 提取隐蔽特征惰性加载数据集类别分布不均衡 提取隐蔽特征 在某些任务中,一些类别的特征可能相对较为罕见或难以捕捉。由于这些特征在数据集中出现的频率较低,模型可能无法充分学习它们,从而导致对这些类别的辨别能力较弱。为了解决…...

wkhtmltopdf 与 .Net Core
wkhtmltopdf 是使用webkit引擎转化为pdf的开源小插件. 其有.NET CORE版本的组件,DinkToPdf,但该控件对跨平台支持有限 。 是由于各系统平台会产生不同的编译结果,故windows上使用.dll,而Linux上的动态链接库是.so 所以你需要在Linux系统上安装相关wkhtmltox软件。 我这里准备了…...

Linux Mint 21.3 计划于 2023 年圣诞节发布
Linux Mint 项目近日公布了基于 Ubuntu 的 Linux Mint 发行版下一个重要版本的一些初步细节,以及备受期待的基于 Debian 的 LMDE 6(Linux Mint Debian Edition)版本。 近日,Linux Mint 项目负责人克莱门特-勒菲弗(Clem…...

腾讯云3年轻量应用服务器2核4G5M和2核2G4M详细介绍
腾讯云轻量应用服务器3年配置,目前可以选择三年的轻量配置为2核2G4M和2核4G5M,2核2G4M和2核4G5M带宽,当然也可以选择选一年,第二年xufei会比较gui,腾讯云百科分享腾讯云轻量应用服务器3年配置表: 目录 腾…...
rabbitmq中的消息确认
如何保证消息被全部消费 应用场景:我们不想丢失任何任务消息。如果一个工作者(worker)挂掉了,我们希望任务会重新发送给其他的工作者(worker)。 为了防止消息丢失,RabbitMQ提供了消息响应&…...

jenkins一键部署github项目
个人目前理解jenkins部署分为两步: 构建项目,如生成jar自动执行sh脚本 如果没有jenkins,我们可能需要将jar移动到服务器,然后执行java -jar跑程序,jenkins可以替代我们执行这些东西,下面从0开始࿰…...

岩土工程安全监测隧道中使用振弦采集仪注意要点?
岩土工程安全监测隧道中使用振弦采集仪注意要点? 岩土工程的安全监测是非常重要的,它可以帮助工程师及时发现可能存在的问题,并及时解决,保障施工进度以及施工质量,保障工程的安全运行。其中,振弦采集仪是…...

第四章nginx组件精讲
nginx配件location匹配的规则和优先级(重点面试题) RUI:统一资源标识符,是一种字符串标识,用于标识抽象的或者物理资源(文件,图片,视频) nginx当中:uri ww…...

LlamaGPT -基于Llama 2的自托管类chatgpt聊天机器人
LlamaGPT一个自托管、离线、类似 ChatGPT 的聊天机器人,由 Llama 2 提供支持。100% 私密,不会有任何数据离开你的设备。 推荐:用 NSDT编辑器 快速搭建可编程3D场景 1、如何安装LlamaGPT LlamaGPT可以安装在任何x86或arm64系统上。 首先确保…...
常见的跨域解决方案
常见的跨域解决方案: 跨域问题可以分为两种情况:前端跨域和后端跨域。以下是针对这两种情况的跨域解决方案: 前端跨域解决方案: JSONP: 适用于前端向不同域名下的服务器请求数据,通过添加回调函数名称来…...
生成xcframework
打包 XCFramework 的方法 XCFramework 是苹果推出的一种多平台二进制分发格式,可以包含多个架构和平台的代码。打包 XCFramework 通常用于分发库或框架。 使用 Xcode 命令行工具打包 通过 xcodebuild 命令可以打包 XCFramework。确保项目已经配置好需要支持的平台…...
Java 语言特性(面试系列2)
一、SQL 基础 1. 复杂查询 (1)连接查询(JOIN) 内连接(INNER JOIN):返回两表匹配的记录。 SELECT e.name, d.dept_name FROM employees e INNER JOIN departments d ON e.dept_id d.dept_id; 左…...

如何在看板中体现优先级变化
在看板中有效体现优先级变化的关键措施包括:采用颜色或标签标识优先级、设置任务排序规则、使用独立的优先级列或泳道、结合自动化规则同步优先级变化、建立定期的优先级审查流程。其中,设置任务排序规则尤其重要,因为它让看板视觉上直观地体…...

MySQL 8.0 OCP 英文题库解析(十三)
Oracle 为庆祝 MySQL 30 周年,截止到 2025.07.31 之前。所有人均可以免费考取原价245美元的MySQL OCP 认证。 从今天开始,将英文题库免费公布出来,并进行解析,帮助大家在一个月之内轻松通过OCP认证。 本期公布试题111~120 试题1…...

Android15默认授权浮窗权限
我们经常有那种需求,客户需要定制的apk集成在ROM中,并且默认授予其【显示在其他应用的上层】权限,也就是我们常说的浮窗权限,那么我们就可以通过以下方法在wms、ams等系统服务的systemReady()方法中调用即可实现预置应用默认授权浮…...
Device Mapper 机制
Device Mapper 机制详解 Device Mapper(简称 DM)是 Linux 内核中的一套通用块设备映射框架,为 LVM、加密磁盘、RAID 等提供底层支持。本文将详细介绍 Device Mapper 的原理、实现、内核配置、常用工具、操作测试流程,并配以详细的…...

微软PowerBI考试 PL300-在 Power BI 中清理、转换和加载数据
微软PowerBI考试 PL300-在 Power BI 中清理、转换和加载数据 Power Query 具有大量专门帮助您清理和准备数据以供分析的功能。 您将了解如何简化复杂模型、更改数据类型、重命名对象和透视数据。 您还将了解如何分析列,以便知晓哪些列包含有价值的数据,…...
Python 包管理器 uv 介绍
Python 包管理器 uv 全面介绍 uv 是由 Astral(热门工具 Ruff 的开发者)推出的下一代高性能 Python 包管理器和构建工具,用 Rust 编写。它旨在解决传统工具(如 pip、virtualenv、pip-tools)的性能瓶颈,同时…...

初探Service服务发现机制
1.Service简介 Service是将运行在一组Pod上的应用程序发布为网络服务的抽象方法。 主要功能:服务发现和负载均衡。 Service类型的包括ClusterIP类型、NodePort类型、LoadBalancer类型、ExternalName类型 2.Endpoints简介 Endpoints是一种Kubernetes资源…...
QT3D学习笔记——圆台、圆锥
类名作用Qt3DWindow3D渲染窗口容器QEntity场景中的实体(对象或容器)QCamera控制观察视角QPointLight点光源QConeMesh圆锥几何网格QTransform控制实体的位置/旋转/缩放QPhongMaterialPhong光照材质(定义颜色、反光等)QFirstPersonC…...