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

问题描述 投票:0回答:1

这个数据模型可以嵌套吗,我试过了 很多事情,但我无法让它发挥作用。


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

这不行,有办法吗?

供参考: 这个例子来自 Paul Hudson 视频教程。

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 nested datamodel
1个回答
0
投票

正如工作狗所说,您可以嵌套类型,但您的属性必须反映完全限定的类型名称,现在

Response.Result

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

无关,但我也建议:

  1. 避免
    try?
    (因为如果失败了,你就会默默地忽略这个问题);
  2. 尽可能在对象的属性中优先使用
    let
    而不是
    var
    ,因为推理不可变对象总是比推理可变对象更容易……仅在需要可变属性时使用
    var
    ;和
  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 中显示一些用户友好的内容等),但这可能超出了这个问题的范围。


作为最后的观察,虽然您可以将嵌套类型嵌入到

Response
对象中,但它引出了一个问题:您是否应该。这是两种不同类型的事物。
Response
是一个API结构。
results
数组的类型是模型类型(“曲目”、“专辑”等)。我不建议将它们纠缠在一起。

例如,此 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>
。等等

恕我直言,这些模型对象(

Track
Album
等)不属于
Response
类型。

然后你可以做这样的事情:

@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)
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.