Search code examples
jsonswiftpretty-printcodable

How can I easily see the JSON output from my objects that conform to the `Codable` Protocol


I deal with lots of objects that I serialize/deserialize to JSON using the Codable protocol.

It isn't that hard to create a JSONEncoder, set it up to pretty-print, convert the object to JSON, and then convert that to a string, but seems like a lot of work. Is there a simple way to say "please show me the JSON output for this object?"

EDIT:

Say for example I have the following structures:

struct Foo: Codable {
    let string1: String?
    let string2: String?
    let date: Date
    let val: Int
    let aBar: Bar
}

struct Bar: Codable {
    let name: String
}

And say I've created a Foo object:

let aBar = Bar(name: "Fred")
let aFoo = Foo(string1: "string1", string2: "string2", date: Date(), val: 42, aBar: aBar)

I could print that with a half-dozen lines of custom code:

let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
guard let data = try? encoder.encode(aFoo),
    let output = String(data: data, encoding: .utf8)
    else { fatalError( "Error converting \(aFoo) to JSON string") }
print("JSON string = \(output)")

Which would give the output:

JSON string = {
  "date" : 557547327.56354201,
  "aBar" : {
    "name" : "Fred"
  },
  "string1" : "string1",
  "val" : 42,
  "string2" : "string2"
}

I get tired of writing the same half-dozen lines of code each time I need it. Is there an easier way?


Solution

  • I would recommend creating a static encoder so you don't create a new encoder every time you call that property:

    extension JSONEncoder {
        static let shared = JSONEncoder()
        static let iso8601 = JSONEncoder(dateEncodingStrategy: .iso8601)
        static let iso8601PrettyPrinted = JSONEncoder(dateEncodingStrategy: .iso8601, outputFormatting: .prettyPrinted)
    }
    

    extension JSONEncoder {    
        convenience init(dateEncodingStrategy: DateEncodingStrategy,
                             outputFormatting: OutputFormatting = [],
                          keyEncodingStrategy: KeyEncodingStrategy = .useDefaultKeys) {
            self.init()
            self.dateEncodingStrategy = dateEncodingStrategy
            self.outputFormatting = outputFormatting
            self.keyEncodingStrategy = keyEncodingStrategy
        }
    }
    

    Considering that you are calling this method inside a Encodable extension you can just force try!. You can also force the conversion from data to string:

    extension Encodable {
        func data(using encoder: JSONEncoder = .iso8601) throws -> Data {
            try encoder.encode(self)
        }
        func dataPrettyPrinted() throws -> Data {
            try JSONEncoder.iso8601PrettyPrinted.encode(self)
        }
        // edit if you need the data using a custom date formatter
        func dataDateFormatted(with dateFormatter: DateFormatter) throws -> Data {
            JSONEncoder.shared.dateEncodingStrategy = .formatted(dateFormatter)
            return try JSONEncoder.shared.encode(self)
        }
        func json() throws -> String {
             String(data: try data(), encoding: .utf8) ?? ""
        }
        func jsonPrettyPrinted() throws -> String {
            String(data: try dataPrettyPrinted(), encoding: .utf8) ?? ""
        }
        func jsonDateFormatted(with dateFormatter: DateFormatter) throws -> String {
            return String(data: try dataDateFormatted(with: dateFormatter), encoding: .utf8) ?? ""
        }
    }
    

    Playground testing

    struct Foo: Codable {
        let string1: String
        let string2: String
        let date: Date
        let val: Int
        let bar: Bar
    }
    struct Bar: Codable {
        let name: String
    }
    

    let bar = Bar(name: "Fred")
    let foo = Foo(string1: "string1", string2: "string2", date: Date(), val: 42, bar: bar)
    
    try! print("JSON\n=================\n", foo.json(), terminator: "\n\n")
    try! print("JSONPrettyPrinted\n=================\n", foo.jsonPrettyPrinted(), terminator: "\n\n")
    let dateFormatter = DateFormatter()
    dateFormatter.dateStyle = .long
    try! print("JSONDateFormatted\n=================\n", foo.jsonDateFormatted(with: dateFormatter))
    

    This will print

    JSON
    =================
    {"date":"2020-11-06T20:22:55Z","bar":{"name":"Fred"},"string1":"string1","val":42,"string2":"string2"}

    JSONPrettyPrinted
    =================
    {
    "date" : "2020-11-06T20:22:55Z",
    "bar" : {
    "name" : "Fred"
    },
    "string1" : "string1",
    "val" : 42,
    "string2" : "string2"
    }

    JSONDateFormatted
    =================
    {"date":"6 November 2020","bar":{"name":"Fred"},"string1":"string1","val":42,"string2":"string2"}