Swift新手求助:如何从JSON API数组中显示指定元素?
SwiftUI中从JSON API获取并显示特定元素的解决方案
问题描述
我是Swift新手,尤其不熟悉JSON解析,希望从指定JSON API的数组中显示特定1-2个元素(用于展示角色和背景)。目前已编写了JSON解码类和SwiftUI视图代码,但尝试用索引[1]选取元素失败,已花费一周时间仍未解决。
问题分析
你的代码当前是遍历所有层级的元素,但直接使用索引选取时失败,核心原因是JSON结构中存在大量可选类型(比如transaction_elements、component_map),且数组可能为空,直接用索引会因可选值未正确绑定或数组越界导致失效。
解决方案
1. 优化数据模型命名(可选但推荐)
把容易混淆的结构体名修改得更清晰,避免和数组名重名,减少代码歧义:
import Foundation @MainActor class JsonNovella: ObservableObject { private struct Returned: Codable { // 原Slides改为SlideItem,避免和slides数组混淆 var slides: [SlideItem] var status: String } struct SlideItem: Codable, Hashable { var slide: Slide } struct Slide: Codable, Hashable { var action: [Action] var id: String } struct Action: Codable, Hashable{ var name: String var map_action: MapAction } struct MapAction: Codable, Hashable{ var transaction_elements: [TransitionElements]? } struct TransitionElements: Codable, Hashable{ var component_map: ComponentMap? } struct ComponentMap: Codable, Hashable{ var elements: [Elements] } struct Elements: Codable, Hashable{ var color: String var name: String var image_url: String? var type: String var text: String? } @Published var urlString = "https://api.novella-designer.com/api/:version/tree/preview/307ea989-a7aa-4496-a86e-0577cec10754" @Published var slides: [SlideItem] = [] @Published var status = "" func getData() async { print("We are accessing the url \(urlString)") guard let url = URL(string: urlString) else { print("ERROR: could not create URL from \(urlString)") return } do { let (data, _) = try await URLSession.shared.data(from: url) // 新增:打印原始JSON,方便确认数据结构 if let rawJson = String(data: data, encoding: .utf8) { print("原始JSON数据:\(rawJson)") } guard let returned = try? JSONDecoder().decode(Returned.self, from: data) else { print("😠ERROR: could not decode data from \(urlString)") return } self.status = returned.status self.slides = returned.slides // 新增:打印解析后的层级信息,确认元素数量 if let firstSlide = returned.slides.first, let firstAction = firstSlide.slide.action.first { print("transaction_elements数量:\(firstAction.map_action.transaction_elements?.count ?? 0)") print("elements数量:\(firstAction.map_action.transaction_elements?.first?.component_map?.elements.count ?? 0)") } } catch { print("ERROR: could not get data from \(urlString), error: \(error)") } } }
2. 安全获取特定索引的元素
在SwiftUI视图中,通过多层可选绑定确保能安全访问到目标数组,再选取指定索引的元素(以下示例假设第一个slide里的第0、1个元素是背景和角色):
import SwiftUI struct NovellaViewer: View { @StateObject var novellaJS = JsonNovella() var body: some View { ScrollView { VStack(spacing: 20) { // 逐层绑定可选值,确保数据存在且数组元素足够 if let firstSlideItem = novellaJS.slides.first, let firstAction = firstSlideItem.slide.action.first, let transactionElements = firstAction.map_action.transaction_elements, let firstTransition = transactionElements.first, let componentMap = firstTransition.component_map, componentMap.elements.count >= 2 { // 显示背景(索引0) AsyncImage(url: URL(string: componentMap.elements[0].image_url ?? "")) { phase in switch phase { case .success(let image): image .resizable() .scaledToFill() .frame(height: 400) .clipped() case .failure: Color.gray .frame(height: 400) .overlay(Text("背景加载失败")) case .empty: ProgressView() .frame(height: 400) @unknown default: EmptyView() } } // 显示角色(索引1) AsyncImage(url: URL(string: componentMap.elements[1].image_url ?? "")) { phase in switch phase { case .success(let image): image .resizable() .scaledToFit() .frame(height: 300) case .failure: Color.gray .frame(height: 300) .overlay(Text("角色加载失败")) case .empty: ProgressView() .frame(height: 300) @unknown default: EmptyView() } } } else { // 数据未加载或不符合预期时的占位 Text("加载中或无有效数据") .font(.title) .padding() } } .padding() .task { await novellaJS.getData() } } } } #Preview{ NovellaViewer() }
关键注意事项
- 可选值安全绑定:所有可选类型的属性必须通过
if let确认不为nil后再访问,避免崩溃。 - 数组边界检查:使用索引前必须确认数组元素数量足够(比如
array.count >= 2才能用array[1])。 - 调试辅助:通过打印原始JSON和解析后的层级数据,确认你要选取的元素确实在目标索引位置。
内容的提问来源于stack exchange,提问作者Kancey
相关产品推荐
相关产品推荐

