Search code examples
jsonswiftserializationswift4codable

How to write a Swift 4 encoded JSON to a file?


How do you write a JSON object encoded via Swift 4 Codable protocol to a file? Before Swift 4 I used JSONSerialization.writeJSONObject but JSONSerialization.isValidJSONObject now returns false on the created data (or string). An example:

import Foundation

class Shark : Codable
{
    var name:String = ""
    var carnivorous:Bool = true
    var numOfTeeth:Int = 0
    var hobbies:[String] = []
}

class JSON
{
    class func encode<T:Encodable>(_ obj:T) -> String?
    {
        if let encodedData = try? JSONEncoder().encode(obj)
        {
            return String(data: encodedData, encoding: .utf8)
        }
        return nil
    }

    class func writeToStream(data:Any, path:String) -> Bool
    {
        var success = false
        if JSONSerialization.isValidJSONObject(data)
        {
            if let stream = OutputStream(toFileAtPath: "\(path)", append: false)
            {
                stream.open()
                var error:NSError?
                JSONSerialization.writeJSONObject(data, to: stream, options: [], error: &error)
                stream.close()
                if let error = error
                {
                    print("Failed to write JSON data: \(error.localizedDescription)")
                    success = false
                }
            }
            else
            {
                print("Could not open JSON file stream at \(path).")
                success = false
            }
        }
        else
        {
            print("Data is not a valid format for JSON serialization: \(data)")
            success = false
        }
        return success
    }
}


let shark = Shark()
shark.name = "Nancy"
shark.carnivorous = true
shark.numOfTeeth = 48
shark.hobbies = ["Dancing", "Swiming", "Eating people"]

if let jsonString = JSON.encode(shark)
{
    let success = JSON.writeToStream(data: jsonString.data(using: .utf8), path: "\(NSHomeDirectory())/Documents")
}

Both of these formats are invalid for JSONSerialization.isValidJSONObject():

JSON.writeToStream(data: jsonString, path: "\(NSHomeDirectory())/Documents")
JSON.writeToStream(data: jsonString.data(using: .utf8), path: "\(NSHomeDirectory())/Documents")

Data is not a valid format for JSON serialization:
{"numOfTeeth":48,"hobbies":["Dancing","Swiming","Eating people"],"name":"Nancy","carnivorous":true}
Data is not a valid format for JSON serialization: Optional(99 bytes)

How do I get it passed for JSON validation and then write it to a file?


Solution

  • JSONSerialization. Your JSONSerialization.isValidJSONObject usage is wrong. As the documentation states:

    A Foundation object that may be converted to JSON must have the following properties:
    • The top level object is an NSArray or NSDictionary.
    • All objects are instances of NSString, NSNumber, NSArray, NSDictionary, or NSNull.
    • All dictionary keys are instances of NSString.

    As such, Data or String types aren’t valid at all ;)

    Writing the encoded data. To actually write the generated Data, use the corresponding Data.write(to: URL) method instead. For instance:

    if let encodedData = try? JSONEncoder().encode(obj) {
        let path = "/path/to/obj.json"
        let pathAsURL = URL(fileURLWithPath: path)
        do {
            try encodedData.write(to: pathAsURL)
        } 
        catch {
            print("Failed to write JSON data: \(error.localizedDescription)")
        }
    }
    

    As long as the JSON data is generated by the standard JSONEncoder no additional validation is really made necessary ;)