初探 Swift 中的 JSON 解析协议 - Decodable

2,056 阅读3分钟

「这是我参与2022首次更文挑战的第6天,活动详情查看:2022首次更文挑战」。

对于需要进行网络交互的 APP 来说,JSON 解析是开发者绕不开的工作。在 Swift 中,解析 JSON 非常简单,不需要借助任何外部的库,它本身自带的 API 就可以满足我们 99% 的开发需求。

在 Swift 中,我们使用 Decodable 来进行解析,下面让我们开始吧!

小试牛刀

这里,由一个简单例子来开始:

{ "title": "Optionals in Swift explained: 5 things you should know", "url": "https://www.avanderlee.com/swift/optionals-in-swift-explained-5-things-you-should-know/", "category": "swift", "views": 47093 }

对于我们的模型来说,只要遵守了 Decodable 协议就可以实现 JSON 解析了:

struct BlogPost: Decodable {
    enum Category: String, Decodable {
        case swift, combine, debugging, xcode
    }

    let title: String
    let url: URL
    let category: Category
    let views: Int
}

在上面的代码中,对于枚举 Category ,我们也让它遵守了 Decodable 协议。而且我们的字段与 JSON 数据中的字段完全一致。这样,我们的模型就可以自动进行解析了:

let JSON = """
{
    "title": "Optionals in Swift explained: 5 things you should know",
    "url": "https://www.avanderlee.com/swift/optionals-in-swift-explained-5-things-you-should-know/",
    "category": "swift",
    "views": 47093
}
"""

let jsonData = JSON.data(using: .utf8)!
let blogPost: BlogPost = try! JSONDecoder().decode(BlogPost.self, from: jsonData)

print(blogPost.title) // Optionals in Swift explained: 5 things you should know

在这里需要说明一点,上述的 JSON 字符串的字段并不是一定都要写全。也就是说,我们可以用哪几个就写哪几个:

struct BlogPost: Decodable {
    let title: String
    let url: URL
}

上面的结构体也是可以解析示例中的 JSON 数据的。

将字段声明为 optional

在开发中,我们时常会碰到自己写的模型字段与服务器返回的字段对不上的情况。在我们不确定服务器是否返回某个字段的情况下,我们应该将此字段声明为可选类型。若我们没有将字段声明为可选类型,而且服务器未返回该字段,则 JSON 会解析失败:

// JSON 中并没有返回 title 字段
let JSON = """
{
    "url": "https://www.avanderlee.com/swift/optionals-in-swift-explained-5-things-you-should-know/",
    "category": "swift",
    "views": 47093
}
"""

let jsonData = JSON.data(using: .utf8)!
// crash
let blogPost: BlogPost = try! JSONDecoder().decode(BlogPost.self, from: jsonData)

解析 JSON 数组

对于 Swift 来说,解析数组就和解析单个对象一样的简单。只需将模型类型加个中括号即可:

let blogPosts: [BlogPost] = try! JSONDecoder().decode([BlogPost].self, from: jsonData)

映射自定义字段名

因为 JSON 解析需要前后端进行交互,这就会设计多种语言环境。那么就会有可能某个名字在服务器的语言环境中可以使用,但到了客户端改字段名可能就成了关键字而无法使用。比如常见的 id ,在 OC 中就是无法使用的。

这时候,我们就需要来对字段名来进行一个映射,下面的代码将 JSON 字符串中的 url 映射为了 htmlLink:

struct BlogPost: Decodable {
    enum Category: String, Decodable {
        case swift, combine, debugging, xcode
    }

    enum CodingKeys: String, CodingKey {
        case title, category, views
        // 将 "url" 映射为 "htmlLink"
        case htmlLink = "url"
    }

    let title: String
    let htmlLink: URL
    let category: Category
    let views: Int
}

let JSON = """
{
    "title": "title",
    "url": "https://www.avanderlee.com/swift/optionals-in-swift-explained-5-things-you-should-know/",
    "category": "swift",
    "views": 47093
}
"""

let jsonData = JSON.data(using: .utf8)!

let blogPost: BlogPost = try! JSONDecoder().decode(BlogPost.self, from: jsonData)
print(blogPost.htmlLink)

因为我们并没有修改 title、category 和 views 的名字,所以在 case 中保持它们的名字不变。由于 JSONDecoder 会遍历我们定义的所有属性,所以我们不得不在枚举中包含这些 case。如果不包含,那么代码就会报下面的错误:

Type ‘BlogPost’ does not conform to protocol ‘Decodable’