Swift UI解构JSON文件
Swift UI解构JSON文件

Swift UI解构JSON文件

在学习Swift UI JSONDecoder解构JSON字符串时,发现存在JSONDecoder解码失败:

struct Astronaut: Codable, Identifiable {
    let id: String
    let name: String
    let description: String
}

struct Astronauts: View {
    var body: some View {
        Button("获取Astronaut数据") {
            if let fileURL = Bundle.main.url(forResource: "astronauts", withExtension: "json") {
                if let data = try? Data(contentsOf: fileURL) {
                    let decoder = JSONDecoder()
                    if let decoded = try? decoder.decode(Astronaut.self, from: data) {
                        print("decoded:\(decoded)")
                    } else {
                        print("JSONDecoder解码失败")
                    }
                } else {
                    print("Data转换失败")
                }
            } else {
                print("获取astronauts.json文件失败")
            }
        }
    }
}

问题出在下面这段解码的代码中:

if let decoded = try? decoder.decode(Astronaut.self, from: data) {

经排查发现,问题为我的JSON文件为包含多个宇航员对象的字典,不是单一的Astronaut对象,所以无法使用Astronaut.self解码。

{
    "grissom": {
        "id": "grissom",
        "name": "Virgil I. \"Gus\" Grissom",
        "description": "Virgil Ivan \"Gus\" Grissom (April 3, 1926 – January 27, 1967) was one of the seven original National Aeronautics and Space Administration's Project Mercury astronauts, and the first of the Mercury Seven to die. He was also a Project Gemini and an Apollo program astronaut. Grissom was the second American to fly in space, and the first member of the NASA Astronaut Corps to fly in space twice.\n\nIn addition, Grissom was a World War II and Korean War veteran, U.S. Air Force test pilot, and a mechanical engineer. He was a recipient of the Distinguished Flying Cross, and the Air Medal with an oak leaf cluster, a two-time recipient of the NASA Distinguished Service Medal, and, posthumously, the Congressional Space Medal of Honor."
    },
    "white": {
        "id": "white",
        "name": "Edward H. White II",
        "description": "Edward Higgins White II (November 14, 1930 – January 27, 1967) (Lt Col, USAF) was an American aeronautical engineer, U.S. Air Force officer, test pilot, and NASA astronaut. On June 3, 1965, he became the first American to walk in space. White died along with astronauts Virgil \"Gus\" Grissom and Roger B. Chaffee during prelaunch testing for the first crewed Apollo mission at Cape Canaveral.\n\nHe was awarded the NASA Distinguished Service Medal for his flight in Gemini 4 and was then awarded the Congressional Space Medal of Honor posthumously."
    }
    ...
}

所以,当我们想要使用Astronaut.self形式进行Decoder解码时,我们解码的对象只能是单一的Astronaut对象:

struct Astronaut: Codable, Identifiable {
    let id: String
    let name: String
    let description: String
}

let name = """
    {
        "id": "grissom",
        "name": "Virgil I. Grissom",
        "description": "Virgil Ivan Grissom was one of the seven original NASA astronauts."
    }
"""

还需要注意的是,当我们单一的Astronaut对象在Swift文件中时,对象内不要有”\”等特殊字符,只有在JOSN文件中可以有”\”字符,如果在Swift文件中就会报错。

"name": "Virgil I. \"Gus\" Grissom",

上面这个name在JSON文件中可以正常解码,但如果是在Swift文件中定义的变量内容存在“\”字符就会报错。

回到正文,因为我们本次解码的JSON文件是包含多个宇航员对象的字典,因此我们应该使用字典的形式进行解码:

if let astronautsInfo = try? decoder.decode([String: Astronaut].self, from: data) {

注意,我们将原来的Astronaut.self改为[String: Astronaut].self,原因为我们的JSON结构现在是一组键值对,所以我们需要用对应的键值对形式进行解码。

当我们使用键值对解码后,可以通过for-in的形式进行轮训并展示对应的数据:

if let astronautsInfo = try? decoder.decode([String: Astronaut].self, from: data) {
    for (key, astronaut) in astronautsInfo {
        print("astronaut.key:\(key)")
        print("astronaut.id:\(astronaut.id)")
        print("astronaut.name:\(astronaut.name)")
        print("astronaut.description:\(astronaut.description)")
    }
}

初次学习可能不明白这里为什么要用[String: Astronaut].self,以及为什么要用for-in轮训。

这里简单的说,原因为首先我们的JSON文件是键值对形式,因此我们必须使用对应的键值对格式进行解码,也因此我们应该使用[String: Astronaut]进行解码,通过.self表示解码的类型本身,因此,我们使用的就是:

[String: Astronaut].self

接着可以看到我们使用了:

for (key, astronaut) in astronautsInfo {

for-in循环语句中包含了key,这是因为我们的astronautsInfo返回的是元组的形式,即:

(key: "grissom", value: Astronaut) 

所以,我们需要先解构元组,然后再访问Astronaut的形式,如果我们不解构元组,而是直接简单的for-in形式的话:

for astronaut in astronautsInfo {
    print("astronaut.id:\(astronaut.id)")
    print("astronaut.name:\(astronaut.name)")
    print("astronaut.description:\(astronaut.description)")
}

就会报下方类似的错误;

Value of tuple type '(key: String, value: Astronaut)' has no member 'id'

原因就是因为我们无法通过astronaut.id直接访问元组,因为astronaut代表一个元组,而不是一个Astronaut对象。因此,我们只能先解构元组,再访问对应的Astronaut对象的属性。

for (key, astronaut) in astronautsInfo {
    print("astronaut.id:\(astronaut.id)")
    print("astronaut.name:\(astronaut.name)")
    print("astronaut.description:\(astronaut.description)")
}

此外,如果不想要使用Key(键)的话,也可以使用 _ 忽略它:

for (_, astronaut) in astronautsInfo {
    print("astronaut.id:\(astronaut.id)")
    print("astronaut.name:\(astronaut.name)")
    print("astronaut.description:\(astronaut.description)")
}

最后就可以完成整个JSON文件的输出:

astronaut.id:cernan
astronaut.name:Eugene A. Cernan
astronaut.description:Eugene Andrew Cernan (/ˈsɜːrnən/; March 14, 1934 – January 16, 2017) was an American astronaut, naval aviator, electrical engineer, aeronautical engineer, and fighter pilot. During the Apollo 17 mission, Cernan became the eleventh person to walk on the Moon. Since he re-entered the Apollo Lunar Module after Harrison Schmitt on their third and final lunar excursion, he was the last person to walk on the Moon.

完整的文件代码:

import SwiftUI

struct Astronaut: Codable, Identifiable {
    let id: String
    let name: String
    let description: String
}

struct Astronauts: View {
    
    // 简化的 JSON 数据,没有任何特殊字符
    
    var body: some View {
        Button("获取Astronaut数据") {
            if let astronauts = Bundle.main.url(forResource: "astronauts", withExtension: "json") {
                if let data = try? Data(contentsOf: astronauts) {
                    let decoder = JSONDecoder()
                    if let astronautsInfo = try? decoder.decode([String: Astronaut].self, from: data) {
                        for (_, astronaut) in astronautsInfo {
                            print("astronaut.id:\(astronaut.id)")
                            print("astronaut.name:\(astronaut.name)")
                            print("astronaut.description:\(astronaut.description)")
                        }
                    } else {
                        print("解码失败")
                    }
                } else {
                    print("未转换为Data形式")
                }
            } else {
                print("未找到该文件")
            }
        }
    }
}

#Preview {
    Astronauts()
}

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

发表回复

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