You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.09 10:25:24