Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 284
Duncan C Avatar asked Sep 02 '18 00:09

Duncan C


People also ask

Which method is used to read data from JSON?

You can use json. load() method to read a file containing JSON object.

What is JSON parse ()?

JSON.parse() The JSON.parse() method parses a JSON string, constructing the JavaScript value or object described by the string. An optional reviver function can be provided to perform a transformation on the resulting object before it is returned.

What does the Codable protocol do?

Codable is the combined protocol of Swift's Decodable and Encodable protocols. Together they provide standard methods of decoding data for custom types and encoding data to be saved or transferred.


2 Answers

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"}

like image 178
Leo Dabus Avatar answered Oct 23 '22 22:10

Leo Dabus


There isn't a stock way to convert a Codable object graph to a "pretty" JSON string, but it's pretty easy to define a protocol to do it so you don't write the same conversion code over and over.

You can simply create an extension to the Encodable protocol, like this:

extension Encodable {
    var prettyJSON: String {
        let encoder = JSONEncoder()
        encoder.outputFormatting = .prettyPrinted
        guard let data = try? encoder.encode(self),
            let output = String(data: data, encoding: .utf8)
            else { return "Error converting \(self) to JSON string" }
        return output
    }
}

Then for any JSON object

print(myJSONobject.prettyJSON)

and it displays the JSON text in "pretty printed" form.

One thing the above won't do is support custom formatting of dates. To do that we can modify prettyJSON to be a function rather than a computed property, where it takes an optional DateFormatter as a parameter with a default value of nil.

extension Encodable {
    func prettyJSON(formatter: DateFormatter? = nil) -> String {
        let encoder = JSONEncoder()
        if let formatter = formatter {
            encoder.dateEncodingStrategy = .formatted(formatter)
        }
        encoder.outputFormatting = .prettyPrinted
        guard let data = try? encoder.encode(self),
            let output = String(data: data, encoding: .utf8)
            else { return "Error converting \(self) to JSON string" }
        return output
    }
}

Then you can use it just like the above, except that you need to add parentheses after prettyJSON, e.g.

print(myJSONobject.prettyJSON())

That form ignores the new DateFormatter parameter, and will output the same JSON string as the above. However,If you have a custom date formatter:

var formatter = DateFormatter()
formatter.dateFormat = "MM-dd-yyyy HH:mm:ss"

print(myJSONobject.prettyJSON(formatter: formatter))

Then the dates in your object graph will be formatted using the specified DateFormatter

like image 22
Duncan C Avatar answered Oct 23 '22 23:10

Duncan C