Swift UI 深入理解 DecodingError
Swift UI 深入理解 DecodingError

Swift UI 深入理解 DecodingError

了解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的四种报错,我们了解到:

  1. DecodingError.keyNotFound缺少预期的键
  2. DecodingError.typeMismatch预期类型不匹配
  3. DecodingError.valueNotFound值缺失
  4. 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。

如果您认为这篇文章给您带来了帮助,您可以在此通过支付宝或者微信打赏网站开放者。

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注