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?"
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?
You can use json. load() method to read a file containing JSON object.
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.
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.
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"}
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With