【问题标题】:How do I correctly write this data to a JSON file without overwriting the file?如何在不覆盖文件的情况下正确将此数据写入 JSON 文件?
【发布时间】:2020-10-06 14:35:07
【问题描述】:

我正在将 JSON 文件写入文档目录,我想将其保存在一个文件中,稍后再阅读。结构如下所示:

struct SymptomD:Codable
{
var symptom:String
var severity:String
var comment:String
var timestamp:String
}

然后我像这样写文件:

var completeData = SymptomD(symptom: "", severity: "", comment: "", timestamp: "")
func writeTrackedSymptomValues(symptom: String, comment: String, time: String, timestamp: String) {
    completeData.symptom = symptom
    completeData.severity = self.severity
    completeData.comment = comment
    completeData.timestamp = timestamp

    createJSON()
}

    var logFile: URL? {
        guard let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else { return nil }
        let fileName = "symptom_data.json"
        return documentsDirectory.appendingPathComponent(fileName)
    }

func createJSON() {
    guard let logFile = logFile else {
        return
    }

    let jsonData = try! JSONEncoder().encode(completeData)
    let jsonString = String(data: jsonData, encoding: .utf8)!
    print(jsonString)

    if FileManager.default.fileExists(atPath: logFile.path) {
        if let fileHandle = try? FileHandle(forWritingTo: logFile) {
            fileHandle.seekToEndOfFile()
            fileHandle.write(completeData) //This does not work, I am not sure how to add data without overwriting the previous file.
            fileHandle.closeFile()
        }
    } else {

         do {

             try JSONEncoder().encode(completeData)
                 .write(to: logFile)
         } catch {
             print(error)
         }
    }
}

有了这个我只能添加一次数据,我不确定我应该如何将另一个“行”基本上添加到 JSON 文件中,以便我可以读取这些数据并使用我的结构对其进行解码以用于 @ 987654325@ 稍后。生成的 JSON 文件如下所示:

有什么方法可以在不覆盖整个文件的情况下再次调用createJSON 函数,以及我应该如何组织它以便在读取 JSON 时可以简单地对其进行解码并访问信息。

更新:

使用它我可以向 JSON 添加更多行,

   let jsonData = try! JSONEncoder().encode(completeData)
    let jsonString = String(data: jsonData, encoding: .utf8)!
    print(jsonString)

    if FileManager.default.fileExists(atPath: logFile.path) {
        if let fileHandle = try? FileHandle(forWritingTo: logFile) {
            fileHandle.seekToEndOfFile()
            fileHandle.write(jsonData)
            fileHandle.closeFile()
        }

给我这个:

   {"timestamp":"1592341465","comment":"","severity":"Mild","symptom":"Anxiety"}{"timestamp":"1592342433","comment":"","severity":"Moderate","symptom":"Anxiety"}{"timestamp":"1592342458","comment":"","severity":"Mild","symptom":"Anxiety"}{"timestamp":"1592343853","comment":"","severity":"Mild","symptom":"Anxiety"}{"timestamp":"1592329440","comment":"","severity":"Mild","symptom":"Fatigue"}{"timestamp":"1592344328","comment":"","severity":"Mild","symptom":"Mood Swings"}{"timestamp":"1592257920","comment":"test","severity":"Mild","symptom":"Anxiety"}

但是当试图解析这个时,它会崩溃并出现错误:

Code=3840 "Garbage at end."

我做错了什么?

【问题讨论】:

    标签: json swift xcode codable nsdocumentdirectory


    【解决方案1】:

    这个问题对我来说很清楚。您正在将另一个字典附加到现有字典,但您应该创建一个字典数组以便能够将字典附加到它。

    struct SymptomD: Codable {
        var symptom, severity, comment, timestamp: String
        init(symptom: String = "", severity: String = "", comment: String = "", timestamp: String = "") {
            self.symptom = symptom
            self.severity = severity
            self.comment = comment
            self.timestamp = timestamp
        }
    }
    

    如果您想手动将文本附加到您的 json 字符串,您需要查找文件末尾之前的位置,在下一个 json 对象之前添加一个逗号并在其后添加一个右括号:

    extension SymptomD {
        func write(to url: URL) throws {
            if FileManager.default.fileExists(atPath: url.path) {
                let fileHandle = try FileHandle(forWritingTo: url)
                try fileHandle.seek(toOffset: fileHandle.seekToEndOfFile()-1)
                let data = try JSONEncoder().encode(self)
                fileHandle.write(Data(",".utf8) + data + Data("]".utf8))
                fileHandle.closeFile()
            } else {
                try JSONEncoder().encode([self]).write(to: url)
            }
        }
    }
    

    游乐场测试:

    var logFile: URL? {
        FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first?.appendingPathComponent("symptom_data.json")
    }
    
    var symptomD = SymptomD()
    symptomD.symptom = "Anxiety"
    symptomD.severity = "Mild"
    symptomD.timestamp = .init(Date().timeIntervalSince1970)
    do {
        if let logFile = logFile {
            try symptomD.write(to: logFile)
        }
    } catch {
        print(error)
    }
    
    var symptomD2 = SymptomD()
    symptomD2.symptom = "Depression"
    symptomD2.severity = "Moderate"
    symptomD2.timestamp = .init(Date().timeIntervalSince1970)
    do {
        if let logFile = logFile {
            try symptomD2.write(to: logFile)
        }
    } catch {
        print(error)
    }
    
    do {
        if let logFile = logFile {
            let symptoms = try JSONDecoder().decode([SymptomD].self, from: .init(contentsOf: logFile))
            print(symptoms)
        }
    } catch {
        print(error)
    }
    

    这将打印:

    [__lldb_expr_532.SymptomD(症状:“焦虑”,严重性:“轻度”, 评论:“”,时间戳:“1592356106.9662929”), __lldb_expr_532.SymptomD(症状:“抑郁”,严重性:“中度”,评论:“”,时间戳:“1592356106.978864”)]

    编辑/更新:

    如果您需要更新 JSON 的单个“行”,则需要使您的 struc 符合 equatable,读取您的集合并找到它的索引:

    extension SymptomD: Equatable {
        static func ==(lhs: SymptomD, rhs: SymptomD) {
            (lhs.symptom, lhs.severity, lhs.comment ,lhs.timestamp) ==
            (rhs.symptom, rhs.severity, rhs.comment ,rhs.timestamp)
        }
        @discardableResult
        mutating func updateAndWrite(symptom: String? = nil, severity: String? = nil, comment: String? = nil, timestamp: String? = nil, at url: URL) throws -> [SymptomD]? {
            var symptoms = try JSONDecoder().decode([SymptomD].self, from: .init(contentsOf: url))
            if let index = symptoms.firstIndex(of: self) {
                self.symptom = symptom ?? self.symptom
                self.severity = severity ?? self.severity
                self.comment = comment ?? self.comment
                self.timestamp = timestamp ?? self.timestamp
                symptoms[index] = self
                try JSONEncoder().encode(symptoms).write(to: url, options: .atomic)
                return symptoms
            }
            return nil
        }
    }
    

    【讨论】:

    • 非常感谢!使用这种方法,我以后可以访问某些“行”并更改/更新/删除它们吗?或者我应该将它们添加到新树下,例如通过它们的时间戳,以便我可以找到并编辑它们?谢谢
    • 我认为你不能在不覆盖整个文件的情况下编辑它们
    • 我在if let logFile = logFileInitializer for conditional binding must have Optional type, not 'URL 遇到错误,不知道为什么。以及扩展,对于 ios 13.0 下的设备,由于 seek 不可用,我该怎么办
    • 我没有看到使用 FileHandle 的任何优势。我认为您应该始终覆盖整个文件。
    • @PeterRuppert 我添加了更新丢失的属性值的选项。
    猜你喜欢
    • 1970-01-01
    • 2011-07-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-12-11
    相关资源
    最近更新 更多