无法使用 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)
}
}
}
}
我想使用 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)
}
}
}
}