Beyond Scripting in Swift: Implementing JSON to CSV conversion

Having discovered out how it should work using a Swift Playground, it was time to build the code into Consolation, to parse a log excerpt in JSON format, and convert it to CSV. I thought that I knew how to do it, and would now face the ultimate test.

As with my time in the playground, I thought it best to work from the bottom upwards. I started with getting the code working right for a single JSON attribute of the first object, then extended it to iterate through all the 16 attributes which appear to occur in log entries, and finally looped through all the objects which had been imported from JSON.

This is set in a little control logic. My plan was for the user to obtain a log extract in JSON format, and once that is displayed in the text panel, to allow them to convert it in place into CSV. This is an unconventional method of exporting data, but it is easy to ensure that the log is in the right format, and allows the user to check that the conversion looks reasonable.

I am also well aware that this task could easily prove too great, if there are practical limits on calls such as that for JSON ‘serialisation’. The best way to avoid those limits is to step through the JSON one object at a time, but I would then have to parse it myself. I don’t fancy having to face that just yet.

The initial flow was simple:
if theCanDoExport {
let json = try? JSONSerialization.jsonObject(with: theJSONdata, options: [])
let json1 = json as? NSArray

then take the first object only, and obtain the value for a given key using
let json3 = json2?.object(forKey: theKey)

With that working, I built an array of keys, and a matching array of integers to indicate whether the value for that key would be an integer (1) or string (0)
let theKeys = ["timestamp", "machTimestamp", "messageType", "category", "subsystem", "processUniqueID", "threadID", "traceID", "senderProgramCounter", "processID", "eventMessage", "processImagePath", "processImageUUID", "senderImagePath", "senderImageUUID", "timezoneName"]
let theKeysNum = [0, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0]

I then simply iterated through those arrays
for index in 0...15 {
let json3 = json2?.object(forKey: theKeys[index])
var json4 = ""
if (json3 != nil) {
if (theKeysNum[index] < 1) {
json4 = json3 as! String
} else {
let json5 = json3 as! Int
json4 = json5.description
}
} else {
json4 = ""
}
if (theKeysNum[index] < 1) {
theObjOut += ("\"" + json4 + "\"")
} else {
theObjOut += json4
}
if (index < 15) {
theObjOut += ","
}
}
theObjOut += "\n"

The last lines insert a comma delimiter between values, except after the last, and write a new line character at the end of the converted data for that object.

With that working, I set it inside the control structure to step sequentially through all the objects
for jsonObj in json1! {
let json2 = jsonObj as? NSDictionary


}

At the start, I made a mutable string containing the first output row, the ordered list of column headers
var theObjOut = "timestamp,machTimestamp,messageType,category,subsystem,processUniqueID,threadID,traceID, senderProgramCounter,processID,eventMessage,processImagePath,processImageUUID,senderImagePath,senderImageUUID,timezoneName\n"

At the end, I turned the string into an AttributedString, and replaced the current text in that panel with the CSV version
let attr = NSAttributedString(string: theObjOut)
theResTxt.textStorage?.setAttributedString(attr)

Because the JSON data has gone now, I have to disable further attempts at export, and set the button back to being non-functional
theCanDoExport = false
theExportButton.state = NSOffState
theExportButton.title = "N/A"

You can see all the above in their properly-formatted form below.

consoln2b31

There were a few glitches along the way, but once again, thanks to my previous work in the playground, it coded in a very straightforward fashion.

I don’t know how limiting the crucial JSONSerialization.jsonObject() will prove. The first test of the complete code was on a set of nearly 19,000 log entries, and generation of the CSV version was almost instantaneous. We’ll see how it works in practice.