Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Swift 5 (Xcode 11 Betas 5 & 6) - How to write to a JSON file?

This question has been asked quite a few times over the years, but it has changed again in Swift 5, particularly in the last two betas.

Reading a JSON file seems to be quite simple:

func readJSONFileData(_ fileName: String) -> Array<Dictionary<String, Any>> {

    var resultArr: Array<Dictionary<String, Any>> = []

    if let url = Bundle.main.url(forResource: "file", withExtension: "json") {

        if let data = try? Data(contentsOf: url) {

            print("Data raw: ", data)

            if let json = try? (JSONSerialization.jsonObject(with: data, options: []) as! NSArray) {

                print("JSON: ", json)

                if let arr = json as? Array<Any> {

                    print("Array: ", arr)

                    resultArr = arr.map { $0 as! Dictionary<String, Any> }

                }

            }

        }

    }

    return resultArr

}

But writing is incredibly difficult, and all of the previous methods found on this site have failed in Swift 5 on Xcode 11 betas 5 and 6.

How can I write data to a JSON file in Swift 5?

I tried these approaches:

  • How to save an array as a json file in Swift?
  • Writing JSON file programmatically swift
  • read/write local json file swift 4

There weren't any errors except for deprecation warnings, and when I fixed those, it simply didn't work.

like image 620
Jack Bashford Avatar asked Aug 26 '19 22:08

Jack Bashford


People also ask

Does Xcode 11 support ios8?

Xcode 11 supports on-device debugging for iOS 8 and later, tvOS 9 and later, and watchOS 2 and later. Xcode 11 requires a Mac running macOS Mojave 10.14. 4 or later.

Can I still use Xcode 11?

You can certainly go on playing with Xcode 11 if that is what you feel like doing. But that's all you will be doing - playing. You won't be able to do anything with an app that you create that way.


1 Answers

Let’s assume for a second that you had some random collection (either arrays or dictionaries or some nested combination thereof):

let dictionary: [String: Any] = ["bar": "qux", "baz": 42]

Then you could save it as JSON in the “Application Support” directory like so:

do {
    let fileURL = try FileManager.default
        .url(for: .applicationSupportDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
        .appendingPathComponent("example.json")

    try JSONSerialization.data(withJSONObject: dictionary)
        .write(to: fileURL)
} catch {
    print(error)
}

For rationale why we now use “Application Support” directory rather than the “Documents” folder, see the iOS Storage Best Practices video or refer to the File System Programming Guide. But, regardless, we use those folders, not the Application’s “bundle” folder, which is read only.

And to read that JSON file:

do {
    let fileURL = try FileManager.default
        .url(for: .applicationSupportDirectory, in: .userDomainMask, appropriateFor: nil, create: false)
        .appendingPathComponent("example.json")

    let data = try Data(contentsOf: fileURL)
    let dictionary = try JSONSerialization.jsonObject(with: data)
    print(dictionary)
} catch {
    print(error)
}

That having been said, we generally prefer to use strongly typed custom types rather than random dictionaries where the burden falls upon the programmer to make sure there aren’t typos in the key names. Anyway, we make these custom struct or class types conform to Codable:

struct Foo: Codable {
    let bar: String
    let baz: Int
}

Then we’d use JSONEncoder rather than the older JSONSerialization:

let foo = Foo(bar: "qux", baz: 42)
do {
    let fileURL = try FileManager.default
        .url(for: .applicationSupportDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
        .appendingPathComponent("example.json")

    try JSONEncoder().encode(foo)
        .write(to: fileURL)
} catch {
    print(error)
}

And to read that JSON file:

do {
    let fileURL = try FileManager.default
        .url(for: .applicationSupportDirectory, in: .userDomainMask, appropriateFor: nil, create: false)
        .appendingPathComponent("example.json")

    let data = try Data(contentsOf: fileURL)
    let foo = try JSONDecoder().decode(Foo.self, from: data)
    print(foo)
} catch {
    print(error)
}

For more information about preparing JSON from custom types, see the Encoding and Decoding Custom Types article or the Using JSON with Custom Types sample code.

like image 51
Rob Avatar answered Oct 25 '22 12:10

Rob