对于SwiftUI解码JSON数据,可以参考《Swift UI解构JSON文件》一文,本文主要内容为:深入理解如何设置Swift结构以及整个解码流程。
JSON数组结构
Swift可以识别JSON,因为它遵循了一种标准格式——JSON数组(Array of Objects),且语法完全符合 JSON 规范。
什么是JSON数组?
假设我们需要从货币网址获取到各种外币的最新币值,获取到的JSON格式为:
[
{
"id": "bitcoin",
"symbol": "btc",
"name": "Bitcoin",
"image": "https://assets.coingecko.com/coins/images/1/large/bitcoin.png",
"current_price": 95936.60,
"market_cap": 1904967939513,
"market_cap_rank": 1,
"total_volume": 14351955699,
"price_change_percentage_24h": 0.7,
"last_updated": "2025-05-03T19:00:00.000Z"
},
{
"id": "ethereum",
"symbol": "eth",
"name": "Ethereum",
"image": "https://assets.coingecko.com/coins/images/279/large/ethereum.png",
"current_price": 1834.30,
"market_cap": 221406884198,
"market_cap_rank": 2,
"total_volume": 6985564402,
"price_change_percentage_24h": 0.0,
"last_updated": "2025-05-03T19:00:00.000Z"
}
// ... 其他币种
]
实际上这个JSON的结构为:
[
{ ... }, // 第一个币种
{ ... } // 第二个币种
]
每个对象内部是一个标准的Key-Value字典结构(JSON Object),字段类型可以是字符串、数字、布尔、嵌套对象、数组等。

最外层的 [ ] 方括号就是数组,这就表示这是一个数组对象。
Swift结构
当我们从Bundle或者从网络上使用dataTask等方法获取API的JSON响应后,Swift通过Codable和JSONDecoder会自动识别并解析这种结构。
例如,在这个JSON中有多个对象。

Swift在解码JSON时,必须创建对应的模型(Model Struct),相关知识可以参考《Swift UI解构JSON文件》。
为什么需要创建对应的模型?
Swift 是一个强类型语言,它要求每个变量和属性都有明确的数据类型。
当使用 Codable 解码 JSON 时,Swift 需要知道:
1)每个字段的名称是什么(key)
2)每个字段的值类型是什么(如 String、Double、Date 等)
因此,创建结构体就是为了让编译器知道“应该解成什么样的对象”。
声明结构体
需要根据对象的字段,创建遵循Codable协议的结构体。

我们可以根据返回的JSON字符串字段的内容,映射成对应的类型。

其中,id、symbol和name都是””双引号包裹的字段,所以它们是String类型。
Image字段返回的是一个照片的链接,所以是URL类型。
current_price字段是一串数字,因为存在小数位并且没有双引号包裹,所以是Double类型。
market_cap、market_cap_rank和total_volume也是数字,但是没有小数位且没有双引号包裹,所以是Int类型。
最后的last_updated虽然是双引号包裹,但实际上是ISO 8601标准格式日期,所以是Date类型。
在映射字段时,可以根据是否有双引号包裹来判断是String类型还是数字类型。如果是数字类型,有小数位的是Double,没有小数位的是Int。双引号包裹内容,大部分是String类型,但URL和Date等类型也是通过双引号包裹,所以在细分双引号包裹内容时,需要检查内容是否为其他类型。
最后,根据JSON字符串对应的字段,可以创建出一个结构体,以符合编译器自动合成解码的逻辑。
struct Cryptocurrency: Codable {
let id: String
let symbol: String
let name: String
let image: URL
let current_price: Double
let market_cap: Int
let market_cap_rank: Int
let total_volume: Int
let price_change_percentage_24h: Double
let last_updated: Date
}
必须使用结构体么?
答案是否定的,可以选择Any 或 NSDictionary,但这样会失去类型信息,变得非常不安全:
let json = try JSONSerialization.jsonObject(with: data, options: []) as? [[String: Any]]
这种方式:
1)没有类型提示
2)访问字段时需要手动判断类型和强制转换
3)容易出错且不利于维护
目前还没有写一个详尽说明这种方法的文章,但可以参考类似的文章《Swift JSON和对象的转换类JSONSerialization》
优化结构体
回到前面定义的结构体代码,在Swift中,属性名必须是合法的标识符,虽然语法允许使用下划线(_),但是Swift的命名风格是camelCase(推荐写法)。
1)可读性更强
2)与系统 API 保持一致
3)与 SwiftUI、Foundation 等库风格统一
比如 Swift 标准库也是:
struct DateFormatter {
var dateFormat: String
}
而不是 date_format。
因此,我们应该考虑将属性名的命名风格改为camelCase。
但是,JSON数据是来自API,字段有可能是snake_case 格式。
{
"current_price": 1834.3,
"market_cap": 1234567890
}
这里的字段格式和Swift 的 camelCase 不一致。
这里就需要使用CodingKeys映射字段:
enum CodingKeys: String, CodingKey {
case currentPrice = "current_price"
case marketCap = "market_cap"
}
通过使用CodingKeys,Swift就知道JSON的”current_price” 应该对应 Swift 的 currentPrice,在编码(encode)和解码(decode)时自动完成字段映射。相关知识可以查看《Swift为什么定义CodingKeys》或者《Swift Codable 深入解析:理解 CodingKeys 的关键角色》

最后,Codable模型代码为:
struct CryptoDTO: Codable {
let id: String
let symbol: String
let name: String
let image: URL
let currentPrice: Double
let marketCap: Int
let marketCapRank: Int
let totalVolume: Int
let priceChangePercentage24h: Double
let lastUpdated: Date
enum CodingKeys: String, CodingKey {
case id, symbol, name, image
case currentPrice = "current_price"
case marketCap = "market_cap"
case marketCapRank = "market_cap_rank"
case totalVolume = "total_volume"
case priceChangePercentage24h = "price_change_percentage_24h"
case lastUpdated = "last_updated"
}
}
有人可能疑问为什么不能使用snake_case格式,而是需要借助CodingKeys遵守Swift的属性命名风格?
这是因为snake_case不符合Swift命名规范,Swift中会使用 _ 作为忽略参数或变量,例如 for _ in 0..<3,容易产生混淆。其次是,使用的过程中容易出错和不清晰,比如:
crypto.current_price // 看起来像 C 语言,而非现代 Swift
还有一种方法可以自动映射索引的snake_case:
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
Swift会自动把current_price映射为currentPrice。这样就可以省去CodingKeys。
需要注意的是,这种自动映射索引的方法要求属性名和转换后的字段严格匹配!
解码JSON数据
最后是通过JSONDecoder对JSON数据进行解码。JSONDecoder利用Codable协议的自动合成功能,将JSON数据映射成Swift中的结构体(如CryptoDTO)。
JSONDecoder是Swift专门用来将JSON数据解码为符合Decodable协议的Swift类型的工具,如需进一步了解JSONDecoder可以查看《Swift JSONDecoder类》。
首先,需要获取到JSON文件,通常有两种获取途径,一种是从Bundle中获取,另一种是使用URLSession.shared.dataTask从网络中获取,这两种获取途径的解析方法请见《Swift UI解构JSON文件》。
我这里是在Xcode的Playground中获取Bundle文件的URL:
let url = URL(fileURLWithPath: #file)
.deletingLastPathComponent()
.appendingPathComponent("coins.json")
接着,使用Data(contentsOf:)方法,通过URL读取Data数据。
guard let data = try? Data(contentsOf: url) else {
print("data读取失败")
return []
}
为什么使用Data(contentsOf:)方法呢?而不是其他的方法。这是因为后面用到的JSONDecoder.decode(_:from)方法要求二进制数据(Data),而不是纯文本(String)。如果使用String(contentsOf:)读取JSON数据,那么读取的就是文本内容而不是Data数据。
接着,声明一个JSONDecoder:
let decoder = JSONDecoder()
因为JSON数据存在ISO8601格式的日期,还需要给JSONDecoder配置dateDecodingStrategy日期格式,配置原因请见《Swift使用JSONDecoder解码日期之ISO 8601 标准格式》文章中涉及毫秒的部分。
let decoder = JSONDecoder()
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"
formatter.locale = Locale(identifier: "en_US_POSIX")
decoder.dateDecodingStrategy = .formatted(formatter)
最后是使用JSONDecoder解码JSON数据,前面已经通过Data(contentsOf:)方法读取到了Data数据:
do {
let cryptos = try decoder.decode([CryptoDTO].self, from: data)
for crypto in cryptos {
print("Name: \(crypto.name), Price: \(crypto.currentPrice)")
}
} catch {
print("JSONDecoder解码失败")
}
这里的核心用法就是JSONDecoder().decode()方法。这个方法可以将JSON数据(Data类型)转换成符合Decodable协议的Swift类型(如struct或class)。
func decode<T>(_ type: T.Type, from data: Data) throws -> T where T : Decodable
decode一共需要两个参数:
第一个是类型本身本身T.self,例如 [CryptoDTO].Type 表示 [CryptoDTO]类型的元类型(metatype)。这是为了告诉decode()方法,要求将JSON解码为[CryptoDTO]类型的实例。
第二个参数是JSON的Data数据,也就是前面从Bundle中读取的JSON数据。
最后解码成功,并输出每一个对象的name和currentPrice属性:
Name: Sei, Price: 0.196895
Name: Story, Price: 3.71
...

解码成功后,我们就可以使用这个对象展示或存储到相应的位置。
如果解码存在报错,我们可以使用DecodingError获取到报错的字段,DecodingError的相关知识请见《Swift UI 深入理解 DecodingError》。
总结
以上就是使用Codable协议解码JSON文件的全部知识点。主要的难点在于将如何根据JSON文件声明结构体。当我们拥有结构体后,就可以借助JSONDecoder.decode()方法将数据解码出来。
涉及的文章知识点比较多,如果有不了解的地方,建议进一步阅读扩展文章。
扩展知识
嵌套JSON结构
请移步《Swift构建嵌套复杂的JSON的Codable模型结构》一文。
常见的Swift类型和JSON值
在声明结构体时,我们根据双引号判断是否为字符串、URL和Int等类型。
下面是常见的Swift类型和JSON值:
1、String:”abc”,文本内容。
2、Int:123,整数值(注意不能是浮点)。
3、Double:123.45,浮点数(JSON 没有区分 float 和 double)。
4、Bool:true / false,布尔值。
5、URL:https://…,自动识别成链接类型。
7、Date:”2025-01-01T12:00:00Z”,时间戳字符串(需要设置 decoder 策略)。
8、Optional<T>:缺失字段或 null,某个字段可能为空。
9、[T]:JSON 数组,例如 [Int], [CryptoCurrency]。
10、[String: T]:JSON 对象,用于字典结构,如 [“usd”: 1.0]。
11、enum + Codable:字符串/整数,用于表示固定范围的值
12、struct / class + Codable:JSON 对象,用于嵌套结构。
相关文章
1、自定义编码和解码逻辑(SwiftData中实现Codable协议): https://fangjunyu.com/2024/11/14/%e8%87%aa%e5%ae%9a%e4%b9%89%e7%bc%96%e7%a0%81%e5%92%8c%e8%a7%a3%e7%a0%81%e9%80%bb%e8%be%91%ef%bc%88swiftdata%e4%b8%ad%e5%ae%9e%e7%8e%b0codable%e5%8d%8f%e8%ae%ae%ef%bc%89/
2、Swift Codable的容错机制:解码不完全匹配的JSON:https://fangjunyu.com/2024/12/01/swift-codable%e7%9a%84%e5%ae%b9%e9%94%99%e6%9c%ba%e5%88%b6%ef%bc%9a%e8%a7%a3%e7%a0%81%e4%b8%8d%e5%ae%8c%e5%85%a8%e5%8c%b9%e9%85%8d%e7%9a%84json/
3、Swift Codable 深入解析:理解 CodingKeys 的关键角色:https://fangjunyu.com/2024/10/14/swift-codable-%e6%b7%b1%e5%85%a5%e8%a7%a3%e6%9e%90%ef%bc%9a%e7%90%86%e8%a7%a3-codingkeys-%e7%9a%84%e5%85%b3%e9%94%ae%e8%a7%92%e8%89%b2/
4、Swift为什么定义CodingKeys:https://fangjunyu.com/2024/11/12/swift%e4%b8%ba%e4%bb%80%e4%b9%88%e5%ae%9a%e4%b9%89codingkeys/
5、Swift静态属性与Codable的问题:https://fangjunyu.com/2024/12/24/swift%e9%9d%99%e6%80%81%e5%b1%9e%e6%80%a7%e4%b8%8ecodable%e7%9a%84%e9%97%ae%e9%a2%98/
6、Swift JSON数据解码保存到SwiftData:https://fangjunyu.com/2024/12/25/swift-json%e6%95%b0%e6%8d%ae%e8%a7%a3%e7%a0%81%e4%bf%9d%e5%ad%98%e5%88%b0swiftdata/
7、Swift UI解构JSON文件:https://fangjunyu.com/2024/10/03/swift-ui%e8%a7%a3%e6%9e%84json%e6%96%87%e4%bb%b6/
8、Swift JSON和对象的转换类JSONSerialization:https://fangjunyu.com/2024/11/01/swift-json%e5%92%8c%e5%af%b9%e8%b1%a1%e7%9a%84%e8%bd%ac%e6%8d%a2%e7%b1%bbjsonserialization/
9、Swift 网络请求方法URLSession.shared.dataTask:https://fangjunyu.com/2024/10/31/swift-%e7%bd%91%e7%bb%9c%e8%af%b7%e6%b1%82%e6%96%b9%e6%b3%95urlsession-shared-datatask/
10、Swift构建嵌套复杂的JSON的Codable模型结构:https://fangjunyu.com/2025/05/07/swift%e6%9e%84%e5%bb%ba%e5%b5%8c%e5%a5%97%e5%a4%8d%e6%9d%82%e7%9a%84json%e7%9a%84codable%e6%a8%a1%e5%9e%8b%e7%bb%93%e6%9e%84/
11、Swift JSONDecoder类:https://fangjunyu.com/2025/05/08/swift-jsondecoder%e7%b1%bb/
12、Swift使用JSONDecoder解码日期之ISO 8601 标准格式:https://fangjunyu.com/2024/11/11/swift%e4%bd%bf%e7%94%a8-iso-8601-%e6%a0%87%e5%87%86%e6%a0%bc%e5%bc%8f%e6%9d%a5%e8%a7%a3%e6%9e%90%e6%97%a5%e6%9c%9f/
13、Xcode Playground无法从Bundle读取文件问题:https://fangjunyu.com/2025/05/07/xcode-playground%e6%97%a0%e6%b3%95%e4%bb%8ebundle%e8%af%bb%e5%8f%96%e6%96%87%e4%bb%b6%e9%97%ae%e9%a2%98/
14、Swift处理和存储二进制数据的Data:https://fangjunyu.com/2024/11/21/swift%e5%a4%84%e7%90%86%e5%92%8c%e5%ad%98%e5%82%a8%e4%ba%8c%e8%bf%9b%e5%88%b6%e6%95%b0%e6%8d%ae%e7%9a%84data/
15、Swift UI 深入理解 DecodingError:https://fangjunyu.com/2024/10/05/swift-ui-%e6%b7%b1%e5%85%a5%e4%ba%86%e8%a7%a3decodingerror/
附录
相关代码
SwiftUI代码:
import SwiftUI
struct ContentView: View {
@State private var json:[CryptoDTO] = []
func loadJSON() -> [CryptoDTO] {
print("进入loadJSON方法")
let url = URL(fileURLWithPath: #file)
.deletingLastPathComponent()
.appendingPathComponent("coins.json")
print("url:\(url)")
guard let data = try? Data(contentsOf: url) else {
print("data读取失败")
return []
}
let decoder = JSONDecoder()
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"
formatter.locale = Locale(identifier: "en_US_POSIX")
decoder.dateDecodingStrategy = .formatted(formatter)
do {
print("进入do-catch方法")
let cryptos = try decoder.decode([CryptoDTO].self, from: data)
for crypto in cryptos {
print("Name: \(crypto.name), Price: \(crypto.currentPrice)")
}
return cryptos
} catch DecodingError.keyNotFound(let key, let context) {
print("---1---")
print("Key '\(key)' not found:", context.debugDescription)
print("codingPath:", context.codingPath)
return []
} catch DecodingError.typeMismatch(let type, let context) {
print("---2---")
print("Type '\(type)' mismatch:", context.debugDescription)
print("codingPath:", context.codingPath)
return []
} catch DecodingError.valueNotFound(let type, let context) {
print("---3---")
print("Value '\(type)' not found:", context.debugDescription)
print("codingPath:", context.codingPath)
return []
} catch DecodingError.dataCorrupted(let context) {
print("---4---")
print("Data corrupted:", context.debugDescription)
print("codingPath:", context.codingPath)
return []
} catch {
print("Unexpected error: \(error)")
return []
}
}
var body: some View {
Text("JSONDecoder")
.onAppear {
json = loadJSON()
}
}
}