Advanced Xcode debugging: Creating an lldb alias for printing dictionaries as formatted JSON
The Xcode console gives you access to a powerful scriptable debugger: LLDB. One of the first LLDB commands I learned was po
, which prints out the description of an Objective c or Swift object. I often find myself needing to display a Swift dictionary as a valid JSON string. Before I knew about LLDB I would write a couple of lines of code for encoding a dictionary to JSON, add a print statement, set a break point, recompile, copy the string from the console and then go on to remove escape characters to obtain valid JSON. Not OK. Let’s iterate through the process of streamlining this process.
Ditch print()
statements
Since we have breakpoints and the ability to print application state to the console with po
, there is really no need for littering the code with print()
statements. Many times I have accidentally checked in a stray print()
statements.
Ditch the recompile
LLDB is not just for printing out variables. You can write code to format the output.
Let’s say you have a Swift dictionary jsonDict
that you would like to print out as a JSON string. Lets set the breakpoint and use po
to do that:
po NSString(string: String(data: try! JSONSerialization.data(withJSONObject: jsonDict, options: .prettyPrinted), encoding: .utf8)!)
Awesome! No more recompiling for converting a dictionary to JSON when debugging. But we can do better! LLDB allows us to create aliases.
Alias!
If you follow an LLDB tutorial like this one you might thing the following should work:
command alias poj po NSString(string: String(data: try! JSONSerialization.data(withJSONObject: json, options: .prettyPrinted), encoding: .utf8)!)
This is when we learn that po
itself is an alias (for expression -O -l swift — -
) and apparently nesting aliases is not allowed. Let’s try again:
command alias poj expression -O -l swift - - NSString(string: String(data: try! JSONSerialization.data(withJSONObject: jsonDict, options: .prettyPrinted), encoding: .utf8)!)
That works. We can now set a breakpoint and print out a JSON dictionary named jsonDict
. But what if the dictionary has a different name? When working with aliases we should be able to work with parameters, but for some reason we can’t use them when printing to the console.
We can save the alias:
command alias poj expression -O -l swift - - NSString(string: String(data: try! JSONSerialization.data(withJSONObject: %1, options: .prettyPrinted), encoding: .utf8)!)
but when we try to use it:
poj jsonDict
we get the following error:
error: <EXPR>:3:75: error: ‘%’ is not a prefix unary operator
This is where it gets weired. The workaround requires us to store our expression as a regular expression:
command regex poj 's/(.+)/expr NSString(string: String(data: try! JSONSerialization.data(withJSONObject: %1, options: .prettyPrinted), encoding: .utf8)!)/'
Now we can finally do use our alias that accepts the name of the dictionary as a parameter, but there are two more issues:
The JSON string is escapded!
Let’s fix that by adding print()
:
command regex poj 's/(.+)/expr print(NSString(string: String(data: try! JSONSerialization.data(withJSONObject: %1, options: .prettyPrinted), encoding: .utf8)!))/'
and voila, we have a butifully formatted JSON string. Done!
Until you close Xcode, try to use your alias and get the following:
error: 'poj' is not a valid command.
error: Unrecognized command 'poj'.
Load you aliases when starting Xcode
There is one last step left and you will be in dictionary JSON heaven. Create a file named .lldbinit
in your user directory and add your alias to it. Files that start with a period are hidden files so either enable viewing of hidden files for Finder or use the command line to edit this file.
Now you are able to close Xcode and you alias will await you when you open Xcode again.
Revisited:
Thanks to André Pinto for suggesting sorting the JSON keys by passing in .prettyPrinted
into the JSONSerialization
options :
command regex poj 's/(.+)/expr print(NSString(string: String(data: try! JSONSerialization.data(withJSONObject: %1, options: [.prettyPrinted, .sortedKeys]), encoding: .utf8)!))/'
I also found myself frequently wanting to turn Encodable
instances into formatted JSON. I created poe
for that purpose:
command regex poe 's/(.+)/expr print(NSString(string: String(data: try! JSONSerialization.data(withJSONObject: (try! JSONSerialization.jsonObject(with: try! JSONEncoder().encode(%1))), options: .prettyPrinted), encoding: .utf8)!))/'