无法使用 SwiftUI 将 JSON(嵌套字典)解析为 ForEach

Cant parse JSON (nested dictionary) into ForEach with SwiftUI

我想使用 SwiftUI 将以下 JSON 解析为列表,但是我遇到了一些问题。

JSON(以随机标识符作为条目的条目比这多得多。这就是导致它更难执行的原因):

{
  "DCqkboGRytms": {
    "name": "graffiti-brush-3.zip",
    "downloads": "5",
    "views": "9",
    "sha256": "767427c70401d43da30bc6d148412c31a13babacda27caf4ceca490813ccd42e"
  },
  "gIisYkxYSzO3": {
    "name": "twitch.png",
    "downloads": "19",
    "views": "173",
    "sha256": "aa2a86e7f8f5056428d810feb7b6de107adefb4bf1d71a4ce28897792582b75f"
  },
  "bcfa82": {
    "name": "PPSSPP.ipa",
    "downloads": "7",
    "views": "14",
    "sha256": "f8b752adf21be6c79dae5b80f5b6176f383b130438e81b49c54af8926bce47fe"
  }
}

SwiftUI 代码:

struct Feed: Codable {
    let list: [FeedValue]
}

struct FeedValue: Codable, Identifiable {
    var id = UUID()
    let name, downloads, views, sha256: String
}

JSON 获取方法(和解析):

func getFeeds(completion:@escaping (Feed) -> ()) {

    // URL hidden to respect API privacy
    
    guard let url = URL(string: "") else { return }
    URLSession.shared.dataTask(with: url) { (data, _, _) in
        
        let feed = try! JSONDecoder().decode(Feed.self, from: data!)
        
        DispatchQueue.main.async {
            completion(feed)
        }
        
    }.resume()
    
}

最后,包含我的 ForEach 循环的视图:

struct HomeView: View {
    
    @State private var scrollViewContentOffset: CGFloat = .zero
    
    @State var listFeed = Feed(list: [])
    
    var body: some View {
        
        // Z-coord status bar holder
        ZStack(alignment: .top) {
            TrackableScrollView(.vertical, showIndicators: true, contentOffset: $scrollViewContentOffset) {
                
                
                // Public files Start
                LazyVStack(spacing: 0) { {
                    // Withholds our content for each asset retrieved.

                    // Main part
                    ForEach(listFeed.list, id: \.id) { download in
                        AssetView(name: .constant(download.name), downloads: .constant(download.downloads), views: .constant(download.views))
                    }

                }.padding([.leading, .top, .trailing], 25)
                // Public files End
                
            }.edgesIgnoringSafeArea([.leading, .trailing, .top])
            
        }.background(Color.backgroundColor.ignoresSafeArea())
            .onAppear() {
                getFeeds { feed in
                    //print(feed) : this did happen to work at the time of testing
                    self.listFeed = feed
                }
            }
        
    }
}

对于出现的错误:

Starfiles/HomeView.swift:32:致命错误:'try!' 表达式意外引发错误:Swift.DecodingError.keyNotFound(CodingKeys(stringValue: "list ", intValue: nil), Swift.DecodingError.Context(codingPath: [], debugDescription: "No value associated with key CodingKeys(stringValue: "list", intValue: nil) ("list").", underlyingError: nil) )

我今天花了几个小时更改解析方法,但无法自行找到解决方案。

编辑 1:我想提一下,我还需要一种访问字典 ID 的方法,因为它们将用于进入新视图。

编辑 2:使用@jnpdx 提供的内容,JSON 解析现在按预期工作,并获取我需要的信息。然而,ForEach 循环存在一个问题,与之前存在的问题相同(它确实在 List() 中工作,如他的回答所示)。

这是更新后的代码(不会显示任何不相关的代码)

                    ForEach(listItems) { item in // Cannot convert value of type '[IdentifiableFeedValue]' to expected argument type 'Binding<C>' &  Generic parameter 'C' could not be inferred
                        AssetView(name: .constant(item.feedValue.name), // Cannot convert value of type 'Binding<Subject>' to expected argument type 'String'
    downloads: .constant(item.feedValue.downloads), views: .constant(item.feedValue.views))
                    }.onAppear() {
                        // URL hidden to respect API privacy, refer to JSON above.
                        guard let url = URL(string: "") else { return }
                        URLSession.shared.dataTask(with: url) { (data, _, _) in
                            
                            do {
                                list = try JSONDecoder().decode([String:FeedValue].self, from: data!)
                            } catch {
                                print("error")
                            }
                            
                        }.resume()
                    }

// AssetView
struct AssetView: View {
    
    @Binding var name: String
    @Binding var downloads: String
    @Binding var views: String

...

}

您的 JSON 没有任何名为 list 的密钥,这就是它失败的原因。因为它是 dynamically-keyed 字典,所以您需要将其解码为 [String:FeedValue].

我使用包装器将 id 与原始 JSON 保持一致,但如果您想将其保留在 one-level [=14] 中,您也可以进行一些更高级的解码=],但这超出了这个问题的范围。

let jsonData = """
{
  "DCqkboGRytms": {
    "name": "graffiti-brush-3.zip",
    "downloads": "5",
    "views": "9",
    "sha256": "767427c70401d43da30bc6d148412c31a13babacda27caf4ceca490813ccd42e"
  },
  "gIisYkxYSzO3": {
    "name": "twitch.png",
    "downloads": "19",
    "views": "173",
    "sha256": "aa2a86e7f8f5056428d810feb7b6de107adefb4bf1d71a4ce28897792582b75f"
  },
  "bcfa82": {
    "name": "PPSSPP.ipa",
    "downloads": "7",
    "views": "14",
    "sha256": "f8b752adf21be6c79dae5b80f5b6176f383b130438e81b49c54af8926bce47fe"
  }
}
""".data(using: .utf8)!

struct FeedValue: Codable {
    let name, downloads, views, sha256: String
}

struct IdentifiableFeedValue : Identifiable {
    let id: String
    let feedValue: FeedValue
}

struct ContentView: View {
    @State var list : [String:FeedValue] = [:]
    
    var listItems: [IdentifiableFeedValue] {
        list.map { IdentifiableFeedValue(id: [=10=].key, feedValue: [=10=].value) }
    }
    
    var body: some View {
        List(listItems) { item in
            Text(item.feedValue.name)
        }.onAppear {
            do {
                list = try JSONDecoder().decode([String:FeedValue].self, from: jsonData)
            } catch {
                print(error)
            }
        }
    }
}