[SC]()

iOS. Apple. Indies. Plus Things.

Converting Data into a Formatted JSON Swift String

// Written by Jordan Morgan // Feb 2nd, 2023 // Read it in about 2 minutes // RE: Swift

This post is brought to you by Emerge Tools, the best way to build on mobile.

Recently I’ve started a large project which involves converting a number of models written in Objective-C over to Swift. As part of my work, I’ve been porting swathes of model definitions that are represented by a flavor of JSON that were used with Pinterest’s excellent Plank library1.

I quickly discovered that there wasn’t a fantastic shortcut to this work, since the definitions we’ve created for our Objective-C codebase aren’t plug-and-play JSON. Here’s an example:

{
    "id": "composerLinkImage.json",
    "title": "composerLinkImage",
    "description" : "Schema definition of Multiple Composer Link Image",
    "$schema": "http://json-schema.org/schema#",
    "type": "object",
    "properties": {
    	"height" : {"type" : "integer"},
        "width" : {"type" : "integer"},
		"type" : {"type" : "string"},
		"url" : { "type": "string", "format": "uri"}
	}
}

If we want to use some model generation tool to get us 90% of the way there, such as QuickType, this won’t cut it for a few reasons:

  1. There’s extraneous data I don’t need.
  2. The only things I do need are keyed to properties.
  3. Plus, while it lists the properties and their types, that’s not super awesome because what I actually need are examples of those properties with their values (i.e. "width" : 200).

At best, I could copy and paste the properties and tweak some of it to work:

{
    "height" : 0,
    "width" : 0,
	"type" : "",
	"url" : "https://www.test.com"
}

At this point, QuickType will spit out most of what I’m after:

// This file was generated from JSON Schema using quicktype, do not modify it directly.
// To parse the JSON, add this file to your project and do:
//
//   let welcome = try? JSONDecoder().decode(Welcome.self, from: jsonData)

import Foundation

// MARK: - Welcome
struct ComposerLinkImage: Codable {
    let height, width: Int
    let type: String
    let url: String
}

Of course, this doesn’t scale when I have near 50-60 models to convert over. So, taking stock of the situation, I accepted the fact those pesudo-json definitions weren’t worth much. It was like having a cheat sheet for a test, but all of the answers were written backwards.

But!

What I did have was an internal app I created to hit all of our endpoints to fetch those models:

Screenshots of an internal iOS running that shows APIs and their responses.

And therein lied my salvation.

By hitting our own API, I could get grab the raw Data and then use a little extension to get that into a formatted JSON string. With that, I could get cut out of some of the grunt work by copy and pasting the correctly formatted JSON into a model generation tool. There’d still be tweaks to make, but it does save me a bit of time.

And so it was, I used #ThisOneSimpleTrick it to create a beautiful, copyable and readable JSON string using our good friend Foundation:

extension Data {
    var prettyPrintedJSONString: NSString? {
        guard let jsonObject = try? JSONSerialization.jsonObject(with: self, options: []),
              let data = try? JSONSerialization.data(withJSONObject: jsonObject, 
              										 options: [.prettyPrinted]),
              let prettyJSON = NSString(data: data, encoding: String.Encoding.utf8.rawValue) else { 
              	return nil 
         	  }

        return prettyJSON
    }
}

From there, I simply looped through our API calls and used this little helper function to print everything out that I needed:

func dumpRawJSONResponse(for endpoint: URL, params: [String : Any], method: HTTPMethod) {
	Task {
	    let response = await AF.request(endpoint,
	                                    method: method,
	                                    parameters: params)
	                                    .serializingResponse(using: .data).response
	    
	    guard let responseData = response.data else {
	        print("There was no data from the response.")
	        return
	    }
	    
	    print(responseData.prettyPrintedJSONString ?? "Couldn't create a .json string.")
	}
}

And zing ✨ - there was all of the JSON I needed perfectly formatted. From our previous example, now I had something like this:

{
	"height" : 200,
	"width" : 200,
	"type" : "square",
	"url" : "https://www.test.com"
}

And that, as my good friends in the U.K. would say, is a job that’s done and dusted.

Until next time ✌️

  1. This tool has saved me several hours, and it’s perfect for generating immutable models for Objective-C codebases. 

···

Spot an issue, anything to add?

Reach Out.