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

SwiftUI语录App JSON解码报错:无对应CodingKeys值求助

问题:JSON解码错误导致QuotesDetailView无法显示

开发语录App时,尝试添加CodingKeys枚举、修改JSON后仍遇到JSON解码错误,提示找不到对应CodingKeys("name")的值。项目可编译运行,但无法显示QuotesDetailView页面。

相关代码及数据

Model代码

import Foundation

class AllQuotes: Identifiable, Decodable {
    
    var id:UUID?
    var quoteTopic:String
    var topicImage:String
    var featured:Bool
    var QuotesList:[Quotes]
}

class Quotes: Identifiable, Decodable {
    var id:UUID?
    var name:String
    var actualQuote:String
    var image:String?
    
    private enum CodingKeys: String, CodingKey {
        case id = "id"
        case name = "name"
        case actualQuote = "actualQuote"
        case image = "image"
    }
}

JSON数据

[
  {
    "quoteTopic": "Wise Quotes",
    "topicImage": "wise quotes",
    "featured": false,
    "QuotesList": [
      {
        "name": "Lao Tzu",
        "actualQuote": "The journey of a thousand miles begins with one step.",
        "image": null
      },
      {
        "name": "Mark Twain",
        "actualQuote": "It is better to keep your mouth closed and let people think you are a fool than to open it and remove all doubt.",
        "image": null
      },
      {
        "name": "Mark Twain",
        "actualQuote": "The secret of getting ahead is getting started.",
        "image": null
      },
      {
        "name": "Babe Ruth",
        "actualQuote": "It’s hard to beat a person who never gives up.",
        "image": null
      }
    ]
  },
  {
    "quoteTopic": "Motivational Quotes",
    "topicImage": "motivational quotes",
    "featured": true,
    "QuotesList": [
      {
        "name": "Mark Twain",
        "actualQuote": "Age is an issue of mind over matter. If you don't mind, it doesn't matter.",
        "image": null
      },
      {
        "name": "Mahatma Gandhi",
        "actualQuote": "Learn as if you will live forever, live like you will die tomorrow.",
        "image": null
      },
      {
        "name": "Mary Kay Ash",
        "actualQuote": "Don’t limit yourself. Many people limit themselves to what they think they can do. You can go as far as your mind lets you. What you believe, remember, you can achieve.",
        "image": null
      },
      {
        "name": "Unknown",
        "actualQuote": "Hold the vision, trust the process.",
        "image": null
      }
    ]
  },
  {
    "quoteTopic": "Success Quotes",
    "topicImage": "success quotes",
    "featured": false,
    "QuotesList": [
      {
        "name": "Estee Lauder",
        "actualQuote": "I never dreamed about success. I worked for it.",
        "image": null
      },
      {
        "name": "Thomas Edison",
        "actualQuote": "Opportunity is missed by most people because it is dressed in overalls and looks like work.",
        "image": null
      },
      {
        "name": "Tom Lehrer",
        "actualQuote": "Life is like a sewer… what you get out of it depends on what you put into it.",
        "image": null
      },
      {
        "name": "Walt Disney",
        "actualQuote": "All our dreams can come true, if we have the courage to pursue them.",
        "image": null
      }
    ]
  }
]

解码代码

class DataService {
    
    static func getLocalData() -> [AllQuotes] {
        let pathString = Bundle.main.path(forResource: "quotes", ofType: "json")
        guard pathString != nil else{ return [AllQuotes]() }
        let url = URL(fileURLWithPath: pathString!)
        do {
            let data = try Data(contentsOf: url)
            let decoder = JSONDecoder()
            do {
                let quoteData = try decoder.decode([AllQuotes].self, from: data)
                for newQuote in quoteData { newQuote.id = UUID() }
                return quoteData
            } catch {
                print("Couldn't decode json, try again (to get the quotes TOPIC)!")
                print(error)
            }
        } catch {
            print("There was an error fetching the data from the file. - with the quote list!")
            print(error)
        }
        return [AllQuotes]()
    }
    
    static func getActualQuote() -> [Quotes] {
        let pathString = Bundle.main.path(forResource: "quotes", ofType: "json")
        guard pathString != nil else{ return [Quotes]() }
        let url = URL(fileURLWithPath: pathString!)
        do {
            let data = try Data(contentsOf: url)
            let decoder = JSONDecoder()
            do {
                let actualQuoteData = try decoder.decode([Quotes].self, from: data)
                for actualQuote in actualQuoteData { actualQuote.id = UUID() }
                return actualQuoteData
            } catch {
                print("Couldn't decode json, try again (to get the actual quote)!")
                print(error)
            }
        } catch {
            print("There was an error fetching the data from the file. - with the actual quote!")
            print(error)
        }
        return [Quotes]()
    }
}

错误信息

Couldn't decode json, try again (to get the actual quote)!
keyNotFound(CodingKeys(stringValue: "name", intValue: nil), Swift.DecodingError.Context(codingPath: [_JSONKey(stringValue: "Index 0", intValue: 0)], debugDescription: "No value associated with key CodingKeys(stringValue: \"name\", intValue: nil) (\"name\").", underlyingError: nil))

解决方案

错误根源在于getActualQuote()方法的解码逻辑错误:你的JSON根结构是[AllQuotes]数组,每个AllQuotes对象包含QuotesList数组,而不是直接的[Quotes]数组。直接解码为[Quotes].self会让解码器尝试在JSON的顶层元素(也就是AllQuotes对象)中寻找name字段,自然找不到。

修改getActualQuote()方法,先解码得到[AllQuotes],再提取所有QuotesList中的元素合并为一个数组:

static func getActualQuote() -> [Quotes] {
    let pathString = Bundle.main.path(forResource: "quotes", ofType: "json")
    guard pathString != nil else{ return [Quotes]() }
    let url = URL(fileURLWithPath: pathString!)
    do {
        let data = try Data(contentsOf: url)
        let decoder = JSONDecoder()
        do {
            // 先解码为[AllQuotes]
            let allQuotes = try decoder.decode([AllQuotes].self, from: data)
            // 提取所有QuotesList并合并成一个数组
            let actualQuoteData = allQuotes.flatMap { $0.QuotesList }
            // 为每个Quotes设置id
            for quote in actualQuoteData { quote.id = UUID() }
            return actualQuoteData
        } catch {
            print("Couldn't decode json, try again (to get the actual quote)!")
            print(error)
        }
    } catch {
        print("There was an error fetching the data from the file. - with the actual quote!")
        print(error)
    }
    return [Quotes]()
}

另外,建议将AllQuotes和Quotes改为结构体(struct)而不是类(class),因为它们是数据模型,值类型更适合,同时默认的合成解码实现也能正常工作,不需要额外的CodingKeys(你的情况字段名完全匹配,Quotes里的CodingKeys可以直接去掉)。

修改后的Model示例:

import Foundation

struct AllQuotes: Identifiable, Decodable {
    var id: UUID?
    var quoteTopic: String
    var topicImage: String
    var featured: Bool
    var QuotesList: [Quotes]
}

struct Quotes: Identifiable, Decodable {
    var id: UUID?
    var name: String
    var actualQuote: String
    var image: String?
}

内容的提问来源于stack exchange,提问作者Kiarra J.

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.18 02:20:37