iOS应用JSON读写报错:JSONSerialization Invalid value at character 0
解决iOS JSON写入后读取崩溃的问题
嘿,我一眼就瞅出问题所在了——你试图往App的Bundle目录里写文件,但这个目录是只读的!
问题根源
App安装后,Bundle里的所有文件都是只读的,你调用dict.write(to: url, atomically: true)其实根本没成功写入(或者写了个无效的内容),导致再次读取时拿到的是损坏的数据,JSON解析直接崩溃,就出现了你看到的Invalid value around character 0错误。而初始读取正常是因为Bundle里的原始JSON文件是完好的。
解决方案:改用Documents目录读写
App的沙盒里只有Documents目录是可读写的,我们需要把JSON文件放到这里来操作。下面是修正后的完整代码:
1. 先写个工具方法获取Documents里的文件URL
private func getSaveDataURL() -> URL { // 获取Documents目录路径 let documentsDir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first! // 返回SaveData.json的完整URL return documentsDir.appendingPathComponent("SaveData.json") }
2. 修改读取按钮的代码(添加错误处理,避免崩溃)
@IBAction func UpdateLabelTapped(_ sender: UIButton) { let url = getSaveDataURL() do { let data = try Data(contentsOf: url) // 用Swift原生字典替代NSDictionary,类型更安全 if let jsonDict = try JSONSerialization.jsonObject(with: data, options: .allowFragments) as? [String: Any], let message = jsonDict["message"] as? String { self.DataLabel.text = message } else { self.DataLabel.text = "无有效数据" } } catch { // 处理文件不存在或解析错误的情况(比如第一次运行) self.DataLabel.text = "初始默认内容" // 可选:第一次运行时写入默认数据 let defaultData = try! JSONSerialization.data(withJSONObject: ["message": "初始默认内容"], options: .prettyPrinted) try! defaultData.write(to: url) } }
3. 修改写入按钮的代码(用标准JSON序列化)
@IBAction func WriteToJSONTapped(_ sender: UIButton) { let url = getSaveDataURL() // 用Swift原生字典,避免OC桥接的潜在问题 let contentDict: [String: Any] = ["message": "Writing JSON!!"] do { // 手动生成标准JSON数据,比NSDictionary的write方法更可靠 let jsonData = try JSONSerialization.data(withJSONObject: contentDict, options: .prettyPrinted) // 原子写入,保证文件完整性 try jsonData.write(to: url, options: .atomic) print("JSON写入成功!") } catch { print("写入失败:\(error.localizedDescription)") } }
可选:复制Bundle里的初始JSON到Documents
如果你希望第一次运行时读取Bundle里的初始JSON内容,可以在App启动时添加复制逻辑:
private func copyInitialSaveDataIfNeeded() { let targetURL = getSaveDataURL() // 如果Documents里已经有文件,就不用复制了 guard !FileManager.default.fileExists(atPath: targetURL.path) else { return } // 从Bundle获取初始文件 if let sourceURL = Bundle.main.url(forResource: "SaveData", withExtension: "json") { do { try FileManager.default.copyItem(at: sourceURL, to: targetURL) print("初始JSON文件复制成功") } catch { print("复制初始文件失败:\(error)") } } }
然后在AppDelegate的didFinishLaunchingWithOptions或者SceneDelegate的sceneDidBecomeActive里调用这个方法即可。
额外提示
- 尽量避免使用
try!,改用do-catch处理错误,这样App不会因为小问题直接崩溃。 - 优先使用Swift原生的
[String: Any]字典,而不是NSMutableDictionary,类型更安全,也符合Swift的开发习惯。
内容的提问来源于stack exchange,提问作者Mrosky
相关产品推荐
相关产品推荐

