了解DecodingError
本篇文章主要讲述了DecodingError的四种报错情况,我们将根据不同的报错情况进行分析,了解和学习对应的报错原因,熟练掌握DecodingError。
DecodingError是Swift中的一个枚举,用来表示使用JSONDecoder解析JSON数据时出现的报错,它提供了四种不同的错误情况。
当JSON的结构和我们期望的类型不匹配时,Swift就会抛出相应的DecodingError。
我们将调整JSON字符串和Swift结构,让每个报错进行复现并了解报错的原因以及报错的捕获内容。
下面,我们创建了一个Persons的结构,给视图中的按钮添加了JSONDecode解码以及捕获相应的错误。
struct Persons: View {
var body: some View {
Button("对比两个参数") {
do {
let _ = try JSONDecoder().decode(Person.self, from: jsonMissingKey)
print("Conversion Successful")
} catch DecodingError.keyNotFound(let key, let context) {
print("---1---")
print("Key '\(key)' not found:", context.debugDescription)
print("codingPath:", context.codingPath)
} catch DecodingError.typeMismatch(let type, let context) {
print("---2---")
print("Type '\(type)' mismatch:", context.debugDescription)
print("codingPath:", context.codingPath)
} catch DecodingError.valueNotFound(let type, let context) {
print("---3---")
print("Value '\(type)' not found:", context.debugDescription)
print("codingPath:", context.codingPath)
} catch DecodingError.dataCorrupted(let context) {
print("---4---")
print("Data corrupted:", context.debugDescription)
print("codingPath:", context.codingPath)
} catch {
print("Unexpected error: \(error)")
}
}
}
}
1、DecodingError.keyNotFound缺少预期的键
代码部分:
DecodingError.keyNotFound(let key, let context)
其中let key缺少的键,是CodingKey类型的实例,表示解码时没有找到预期的字段。Let context是一个DecodingError.Context对象,提供有关错误发生的上下文信息,比如发生错误的路径。
扩展知识:
1)CodingKey是一个协议,用于定义在编码和解码时使用的键。它定义了键在编码和解码时的标识。每个键都可能是String或Int类型。在keyNotFound错误中,key是实现了CodingKey协议的实例,标识Swift期望在JSON中找到的字段名(键名)。
2)DecodingError.Context是包含有关解码错误的上下文信息对象。它提供错误的详细信息,包括:
- codingPath:报错发生时的JSON路径(从根节点到出错键的路径)
- debugDescription:错误的描述,帮助调试。
struct Person: Codable {
let name: String
let age: Int
}
let jsonMissingKey = """
{
"age": 30
}
""".data(using: .utf8)!
在这段代码中,我们的jsonMissingKey字符串只有一个age字段,当尝试使用Person结构进行解码时,就会在解码过程中缺少了name字段。
catch DecodingError.keyNotFound(let key, let context) {
print("---1---")
print("Key '\(key)' not found:", context.debugDescription)
print("codingPath:", context.codingPath)
}
因此,当我们点击按钮时,就会报:
---1---
Key 'CodingKeys(stringValue: "name", intValue: nil)' not found: No value associated with key CodingKeys(stringValue: "name", intValue: nil) ("name").
codingPath: []
提示我们没有找到键stringValue:”name”。因此,当我们的字符串中没有对应字段时,JSON解码就会抛出keyNotFound错误。
只有我们在jsonMissingKey中添加对应的name字段后,就可以输出正常。
struct Person: Codable {
let name: String
let age: Int
}
let jsonMissingKey = """
{
"name": "fangjunyu",
"age": 30
}
Xcode输出:
Conversion Successful
2、 DecodingError.typeMismatch预期类型不匹配
代码部分:
DecodingError.typeMismatch(let type, let context)
type:表示解码时预期的类型。即,Swift 期望从 JSON 中获取的某个值是某个特定类型(如 String、Int、Array 等),但实际提供的数据类型不符。例如,Swift 可能期望的是一个整数(Int),但你在 JSON 中提供了一个字符串(String)。
struct Person: Codable {
let name: String
let age: Int
}
let jsonMissingKey = """
{
"name": "fangjunyu",
"age": "thirty"
}
""".data(using: .utf8)!
在这段代码中,我们将jsonMissingKey的age字段改为了String类型,而Person结构的age是Int类型,导致两者的类型不匹配。
catch DecodingError.typeMismatch(let type, let context) {
print("---2---")
print("Type '\(type)' mismatch:", context.debugDescription)
print("codingPath:", context.codingPath)
}
就会产生报错:
---2---
Type 'Int' mismatch: Expected to decode Int but found a string instead.
codingPath: [CodingKeys(stringValue: "age", intValue: nil)]
输出表示类型Int不匹配,应该解码Int,但发现却是字符串。
解决方案还是将我们的age改为Int类型。
3、DecodingError.valueNotFound值缺失
代码部分
DecodingError.valueNotFound(let type, let context)
其中let type为JSON中缺失的预期类型。context还是提供上下文。
struct Person: Codable {
let name: String
let age: Int
}
let jsonMissingKey = """
{
"name": "fangjunyu",
"age": null
}
""".data(using: .utf8)!
当我们的字符串中存在null时,就会触发DecodingError.valueNotFound。
此外,目前只有存在值为null或数组的元素为null时才会触发这一报错。
数组的元素为null时,代码如下:
struct Person: Codable {
let name: String
let age: [Int]
}
let jsonMissingKey = """
{
"name": "fangjunyu",
"age": [1,null]
}
""".data(using: .utf8)!
这两种情况都会触发DecodingError.valueNotFound。
catch DecodingError.valueNotFound(let type, let context) {
print("---3---")
print("Value '\(type)' not found:", context.debugDescription)
print("codingPath:", context.codingPath)
}
Xcode报错输出:
---3---
Value 'Int' not found: Cannot get unkeyed decoding container -- found null value instead
codingPath: [CodingKeys(stringValue: "age", intValue: nil), _JSONKey(stringValue: "Index 1", intValue: 1)]
还需要注意的是,当我们存在字段缺失时,只会触发第一种DecodingError.keyNotFound,不会触发DecodingError.valueNotFound。因为DecodingError.valueNotFound主要处理字段有键但缺失有效值的情况(例如null)。
4、DecodingError.dataCorrupted数据损坏
代码部分:
DecodingError.dataCorrupted(let context)
当JSON数据本身损坏或无法解析时,就会触发dataCorrupted错误,例如JSON包含非法字符或格式错误。
struct Person: Codable {
let name: String
let age: Int
}
let jsonMissingKey = """
{
"name": "fangjunyu",
"age": 25,
""".data(using: .utf8)!
在这段代码中,jsonMissingKey的格式不完整,缺少正确的闭合括号或包含非法字符,导致无法解析,因此触发DecodingError.dataCorrupted错误。
catch DecodingError.dataCorrupted(let context) {
print("---4---")
print("Data corrupted:", context.debugDescription)
print("codingPath:", context.codingPath)
}
Xcode输出如下:
---4---
Data corrupted: The given data was not valid JSON.
codingPath: []
实战运用
在我开发《汇率仓库》应用时,发现do-catch没有捕获到错误,后来发现是因为使用了try?导致的问题。
后来在代码中添加了多处注释,尝试通过下面的代码调取汇率数据:
func update() async {
print("进入update函数")
guard let url = URL(string: ChinaForeignExchangeTradingCenter) else {
print("Invalid URL")
return
}
print("url转码成功")
print("进入do-catch块")
do {
print("尝试使用try URLSession访问url")
let (data, _) = try await URLSession.shared.data(from: url)
let jsonString = String(data: data, encoding: .utf8)
print("原始 JSON 数据: \(jsonString ?? "无效数据")")
print("尝试使用try 转码data")
let decodedResponse = try JSONDecoder().decode(ForexDataStruct.self, from: data)
updateERInfo = decodedResponse
print("获取数据成功")
} catch {
print("获取以及解码数据失败")
}
}
Xcode输出为:
进入task
进入update函数
url转码成功
进入do-catch块
尝试使用try URLSession访问url
原始 JSON 数据: {"head":
...
}
尝试使用try 转码data
获取以及解码数据失败
发现到解码这一步卡住了,但又不知道是缺少数据还是我定义的结构类型有问题,后来想起通过DecodingError来捕获错误:
do {
...
} catch {
print("获取以及解码数据失败")
switch error {
case DecodingError.keyNotFound(let key, let context):
print("解码失败: 缺少键 \(key.stringValue) - \(context.debugDescription)")
case DecodingError.typeMismatch(let type, let context):
print("解码失败: 类型不匹配 \(type) - \(context.debugDescription)")
case DecodingError.valueNotFound(let type, let context):
print("解码失败: 缺少值 \(type) - \(context.debugDescription)")
case DecodingError.dataCorrupted(let context):
print("解码失败: 数据损坏 - \(context.debugDescription)")
default:
print("未知错误: \(error.localizedDescription)")
}
}
再次运行后,发现Xcode输出中多了一行:
解码失败: 缺少键 vrtENmae - No value associated with key CodingKeys(stringValue: "vrtENmae", intValue: nil) ("vrtENmae").
通过查看原始JSON数据发现原始JSON没有这个vrtENmae字段,是我敲错了。而是vrtEName字段。
修改后,重新执行代码,问题得到解决。
DecodingError总结
根据上面的DecodingError的四种报错,我们了解到:
- DecodingError.keyNotFound缺少预期的键
- DecodingError.typeMismatch预期类型不匹配
- DecodingError.valueNotFound值缺失
- DecodingError.dataCorrupted数据损坏
当我们的JSON字符串在结构或类缺失对应字段(键)时,就会报keyNotFound。当我们JSON字符串的字段类型,如String与结构或类的类型Int不一致时,就会报typeMismatch。当JSON字符串的值或数组的元素为null时,就报valueNotFount。当JSON字符串存在括号缺失或者非法符号时,就会报dataCorrupted。
以上就是DecodingError的全部总结。
扩展内容
通用catch部分
我们学会了在DecodingError的这四种错误,但是我们的代码中还有一个catch,那就是:
catch {
print("Unexpected error: \(error)")
}
那就是我们这四种错误都没有捕获到的话,其他错误就会这个catch所捕获。
这个catch捕获的内容可能是文件不存在、损坏或网络请求失败。再或者是内存以及系统资源不足,自定义的错误等各种情况。
data(using:)
这时一个String的实例方法,用于将字符串按照指定的字符编码转换为Data。.utf8表示我们希望将字符串以UTF-8编码进行转换。它也会返回一个可选的Data?类型,如果字符串无法使用指定编码转换,它就返回nil。
let jsonString = """
{
"age": 30
}
"""
let data = jsonString.data(using: .utf8) // 将字符串转换为 UTF-8 编码的 Data 类型
那么,它跟Data(string.utf8)的区别是什么?
let jsonString = """
{
"age": 30
}
"""
let data = Data(jsonString.utf8) // 直接通过字符串的 UTF8View 转换为 Data 类型
data(using:)是更通用的方法,它返回的结构是一个可选类型Data?,而Data(string.utf8)则更简洁,但不会返回nil。