从 JSON 到字典并返回到 JSON 的意外转换

Getting unexpected conversion from JSON to dictionary and back to JSON

我正在尝试从 Bundle 中的 myFile.json 读取一个 JSON 文件,修改一些元素并将其作为 JSON 文件存储在文档目录中。我认为这将是一种保存数据的简单方法。随后,我打算读写文件目录。以下代码显示了我所做的并且有大量评论。似乎我错过了一些重要的步骤,因为预期的 JSON 转换与 JSON 规范不匹配。我愿意接受有关如何在操场上进行测试的建议。代码基于

Convert Dictionary to JSON in Swift

    import UIKit

/* Trying to read a json file from myFile.json in Bundle, modify some elemnts and store it
 in the document directory as a json file. Subsequently, intent to read and write to document directory

 myFile.json consistes of

 {
 "record": {"id": "A1234", "customer": "Customer-1"}
 }

*/


typealias MyRecord = [String: AnyObject]
var json:MyRecord!
let fileName = "myFile"
var dictionary = MyRecord()

func loadJsonFromBundle (forFilename fileName: String) -> MyRecord {

    if let url = Bundle.main.url(forResource: fileName, withExtension: "json") {
        if let data = NSData(contentsOf: url) {
            do {
                let dictionary = try JSONSerialization.jsonObject(with: data as Data, options: .allowFragments) as? [String:Any]
                print("dictionary = \(dictionary!)")
                /* displays
                 dictionary = ["record": {
                 customer = "Customer-1";
                 id = A1234;
                 }]
                */

                return dictionary! as MyRecord
            } catch {
                print("Error!! Unable to parse  \(fileName).json")
            }
        }
        print("Error!! Unable to load  \(fileName).json")
    }

    return [:]
}

func loadJsonFromDocument (forFilename fileName: String) -> MyRecord {

    let docDirectory = try? FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
    if let url = docDirectory?.appendingPathComponent(fileName).appendingPathExtension("json") {
        if let data = NSData(contentsOf: url) {
            do {
                let dictionary = try JSONSerialization.jsonObject(with: data as Data, options: .allowFragments) as? [String:Any]
                print("dictionary = \(dictionary!)")
                return dictionary! as MyRecord
            } catch {
                print("Error!! Unable to parse  \(fileName).json")
            }
        }
        print("Error!! Unable to load  \(fileName).json")
    }

    return [:]
}


func saveJsonToFile (_ fileName:String, outString: String) -> URL {
    let docDirectory = try? FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
    if let fileURL = docDirectory?.appendingPathComponent(fileName).appendingPathExtension("json") {
        print("fileURL = \(fileURL)")
        // Write to a file on disk

        do {
            try outString.write(to: fileURL, atomically: true, encoding: .utf8)
        } catch {
            print("Failed writing to URL: \(fileURL), Error: " + error.localizedDescription)
        }
        return fileURL
    }
    return URL(string: "")!
}

let sq = "\""
func q(_ x:String) -> String {
    return "\(sq)\(x)\(sq)"
}


dictionary = loadJsonFromBundle (forFilename: fileName)
var a = dictionary["record"] as? [String:String]
a?["customer"] = "newName"
var dict = MyRecord()
dict["record"] = a as AnyObject?
print(dict)

/* prints:

 ["record": {
 customer = newName;
 id = A1234;
 }]

*/

// 
do {
    let jsonData = try JSONSerialization.data(withJSONObject: dict, options: .prettyPrinted)
    // here "jsonData" is the dictionary encoded in JSON data

    let decoded = try JSONSerialization.jsonObject(with: jsonData, options: [])
    // here "decoded" is of type `Any`, decoded from JSON data

    // you can now cast it with the right type
    if let dictFromJSON = decoded as? [String:Any] {

        // need to save dictFromJson to a file in document directory
        // saveJsonToFile is expecting a String for the json argument
        // I converted dictFromJson to a string so I can save it
        var outString = String(describing: dictFromJSON)

        print("outString = \(outString)")

        /* Notice that the internal structure is not quoted and there are semi-colons

         outString = ["record": {
         customer = newName;
         id = A1234;
         }]

        */
        outString = outString.replacingOccurrences(of: "[", with: "{")
        outString = outString.replacingOccurrences(of: "]", with: "}")



        let url = saveJsonToFile("newFile", outString: String(describing: outString) )
        print(url)
        /* Resulting File looks like this:

         {"record": {
         customer = newName;
         id = A1234;
         }}

         Question: Why were the braces swapped with brackets. The internal elements
         were not quoted.

        */

        // Will try to read back the json string from document directory
        dictionary = loadJsonFromDocument(forFilename: fileName)
        // results in ERROR (Unable to load myFile.json


        a = dictionary["record"] as? [String:String]
        a?["customer"] = "newName"
        dict = MyRecord()
        dict["record"] = a as AnyObject?
        print(dict)

    }
} catch {
    print(error.localizedDescription)
}

问题是您将字典对象保存到文件而不是编码 JSON。

仅使用JSON对象到数据的序列化,不传递.prettyprinted

do {
    let jsonData = try JSONSerialization.data(withJSONObject: dict, options:[])
    // here "jsonData" is the dictionary encoded in JSON data

    let outString = String(data:jsonData, encoding:.utf8)

    print("outString = \(outString)")
    let url = saveJsonToFile("newFile", outString: outString )
    print(url)
    }
} catch {
    print(error.localizedDescription)
}

vadian 指出的问题是正确的,您正在存储 Dictionary 对象,但不是将 Data 转换为 String 然后写 String 您可以直接写 DataDocumentDirectory.

所以我更改了你的 saveJsonToFile 函数,它接受 Data 作为第二个参数而不是 String

func saveJsonToFile (_ fileName:String, jsonData: Data) -> URL {
    let docDirectory = try? FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
    if let fileURL = docDirectory?.appendingPathComponent(fileName).appendingPathExtension("json") {
        print("fileURL = \(fileURL)")
        // Write to a file on disk

        do {
            try jsonData.write(to: fileURL)
        } catch {
            print("Failed writing to URL: \(fileURL), Error: " + error.localizedDescription)
        }
        return fileURL
    }
    return URL(string: "")!
}

现在只需在更改 json 结果并将其转换为数据后调用此函数即可。

do {
    let jsonData = try JSONSerialization.data(withJSONObject: dict, options: [])
    let url = saveJsonToFile("newFile", jsonData: jsonData )
    print(url)
} catch {
    print(error.localizedDescription)
}