Получение неожиданного преобразования из JSON в словарь и обратно в JSON

Я пытаюсь прочитать файл JSON из myFile.json в Bundle, изменить некоторые элементы и сохранить его в каталоге документов в виде файла JSON. Я думал, что это будет простой способ сохранить данные. Впоследствии я намерен читать и писать в каталог документов. Следующий код показывает, что я сделал, и комментируется. Кажется, я пропускаю какой-то важный шаг, потому что ожидаемые преобразования JSON не соответствуют спецификации JSON. Я открыт для предложений о том, как я проверил на детской площадке. Код основан на

Преобразовать словарь в JSON в 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;
 }]

*/

// https://stackru.com/questions/29625133/convert-dictionary-to-json-in-swift/29628000#29628000
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)
}

2 ответа

Решение

Проблема, указанная vadian, правильна: вы сохраняете объект Dictionary, но не конвертируете Data в String а потом напиши что String вы можете напрямую написать Data в DocumentDirectory,

Так что я изменил ваш 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)
}

Проблема в том, что вы сохраняете объект словаря в файл, а не в кодировке JSON.

Используйте только JSONSerialization от объекта к данным и не передавайте .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)
}
Другие вопросы по тегам