这个数据模型可以嵌套吗?

Can this data model be nested?

提问人:stoney 提问时间:11/17/2023 更新时间:11/17/2023 访问量:78

问:

这个数据模型可以嵌套吗,我已经试过了 很多事情,我无法让它工作。


struct Response: Codable {
   struct Result: Codable {
       var trackId: Int
       var trackName: String
       var collectionName: String
   }
    var results: [Result]
}

这行不通,有办法吗?

供参考: 这个例子来自保罗·哈德森 视频教程。

import SwiftUI

struct Response: Codable {
    var results: [Result]
}
struct Result: Codable {
    var trackId: Int
    var trackName: String
    var collectionName: String
}

struct ContentView: View {
    @State private var results = [Result]()
    
    var body: some View {
        List {
            ForEach(results, id: \.trackId) { item in
                VStack(alignment: .leading) {
                    Text(item.trackName)
                        .font(.headline)
                    Text(item.collectionName)
                        .font(.caption)
                }
            }
        }
        .task {
            await loadData()
        }
    }
    
    func loadData() async {
        guard let url = URL(string: "https://itunes.apple.com/search?term=taylor+swift&entity=song&limit=4") else {
            print("Invalid URL")
            return }
        do {
            let (data, _) = try await URLSession.shared.data(from: url)
            if let decodedResponse = try? JSONDecoder().decode(Response.self, from: data) {
                results = decodedResponse.results
            }
        } catch {
            print("Invalid Data")
        }
    }
}

我想不出更多的细节。我只是 在这一点上不明白这一点。

SwiftUI 嵌套 数据模型

评论

0赞 workingdog support Ukraine 11/17/2023
是的,它可以。在 中,使用它会起作用。再读一遍 Swift 的基础知识。ContentView@State private var results = [Response.Result]()
0赞 lorem ipsum 11/17/2023
切勿使用并更改为try?print("Invalid Data")print("Invalid Data \(error)")
0赞 malhal 11/18/2023
自从我上次检查以来,Paul Hudson 的 SwiftUI 代码看起来有了很大的改进,你能分享一下教程的链接吗?谢谢
0赞 stoney 11/18/2023
PH 示例来自 Cupcake Corner SwiftUI 教程 1/9。有一个旧版本使用了请求、数据、错误 URLSession,并且此异步等待更新版本。

答:

0赞 Rob 11/17/2023 #1

正如 workingdog 所说,您可以嵌套类型,但您的属性必须反映完全限定的类型名称,现在:Response.Result

@State private var results = [Response.Result]()

无关紧要,但我也建议:

  1. 避免(因为如果它失败了,你会默默地忽略这个问题);try?
  2. 尽可能在对象的属性中优先,因为推理不可变对象总是比推理可变对象更容易......仅在需要可变属性的地方使用;和letvarvar
  3. 如果出现错误,不要只打印“无效数据”,还要包含实际错误。

例如

struct Response: Codable {
    let results: [Result]                             // use `let` instead of `var`, unless you really need mutability
    
    struct Result: Codable {
        let trackId: Int
        let trackName: String
        let collectionName: String
    }
}

struct ContentView: View {
    @State private var results = [Response.Result]()  // `Response.Result`
    
    var body: some View {
        List {
            ForEach(results, id: \.trackId) { item in
                VStack(alignment: .leading) {
                    Text(item.trackName)
                        .font(.headline)
                    Text(item.collectionName)
                        .font(.caption)
                }
            }
        }
        .task {
            await loadData()
        }
    }
    
    func loadData() async {
        guard let url = URL(string: "https://itunes.apple.com/search?term=taylor+swift&entity=song&limit=4") else {
            print("Invalid URL")
            return
        }

        do {
            let (data, _) = try await URLSession.shared.data(from: url)
            results = try JSONDecoder()              // use `try`, not `try?`
                .decode(Response.self, from: data)
                .results   
        } catch {
            print("Invalid Data", error)             // include the error, so if it failed, you can meaningfully diagnose what went wrong
        }
    }
}

还有很多其他潜在的改进(例如,如果出现错误,在UI中显示一些用户友好的东西等),但这可能超出了这个问题的范围。


最后,虽然您可以将嵌套类型嵌入到对象中,但它回避了是否应该这样做的问题。这是两种不同类型的事物。 是一个 API 结构。数组的类型是模型类型(“曲目”、“专辑”等)。我不建议把它们纠缠在一起。ResponseResponseresults

例如,此 API 遵循一致的约定 ,其中该数组中对象的类型将根据请求的参数而变化。{"results":[…]}entity

因此,这乞求不要将子类型嵌入其中。就我个人而言,我会使用通用模式:Response

struct Track: Codable {
    let trackId: Int
    let trackName: String
    let collectionName: String
}

struct Album: Codable {
    let albumId: Int
    let albumName: String
    
    enum CodingKeys: String, CodingKey {
        case albumId = "collectionId"
        case albumName = "collectionName"
    }
}

struct Response<T: Codable>: Codable {
    let results: T
}

因此,请求相册的函数可以解析 ,但获取相册的端点可以解析 .等。Response<Track>Response<Album>

恕我直言,这些模型对象(、等)不属于内部类型。TrackAlbumResponse

然后,您可以执行以下操作:

@State private var tracks = [Track]()

func loadData() async {
    guard let url = URL(string: "https://itunes.apple.com/search?term=taylor+swift&entity=song&limit=4") else {
        print("Invalid URL")
        return
    }

    do {
        let (data, _) = try await URLSession.shared.data(from: url)
        tracks = try JSONDecoder()
            .decode(Response.self, from: data)   // it infers the generic for us
            .results
    } catch {
        print("Invalid Data", error)
    }
}

评论

0赞 stoney 11/17/2023
我喜欢对 do-catch、try-.decode 内容的改进。非常!对我有帮助。我之前尝试在 .decode 中使用 Response.Result,但没有用。Response.self 在 .decode 中找到 Result 结构,但它在另一个方向上不起作用。我是一个初学者,我被卡住了。哇,评论非常有帮助。
0赞 Rob 11/17/2023
是的,is 仅在 的声明中,但 仍然是 ,如上面的第一个代码片段所示。Response.Resultvar results = [Response.Result]()decodeResponse.self