So we’re building this JSON API at autobutler.dk, in which we want the JSON outputted to be human readable (pretty).
Nothing is worse than trying to debug a JSON service, and always having to parse it through e.g. json_pp
.
Instead we just want the service to output the pretty JSON. It doesn’t consume a lot of resources to do so, and the pretty JSON is equivalent to the non-pretty one, that is the following are the same:
{"this": "that"}
and
{
"this": "that"
}
In this simple example it doesn’t really matter, but this JSON is entirely unreadable:
{"id": 54,"created_at": "2013-12-13T13:33:53Z","updated_at": "2013-12-19T07:40:02Z","registration_number": "SY32554","data":{"chassis_number": "WVWZZZ6NZXW005709","registration_date": "1998-06-12","inspection_date": "2012-07-11","make": "VW","model": "Polo","variant": "1,6 ","vehicle_code": null},"country_id": 1,"car_id": 113232,"make_and_model_id": null,"make": "VW","model": "Polo","variant": "1,6 ","chassis_number": "WVWZZZ6NZXW005709","registration_date": "1998-06-12","inspection_date": "2012-07-11"}
while this is not
{
"id": 54,
"created_at": "2013-12-13T13:33:53Z",
"updated_at": "2013-12-19T07:40:02Z",
"registration_number": "SY32554",
"data": {
"chassis_number": "WVWZZZ6NZXW005709",
"registration_date": "1998-06-12",
"inspection_date": "2012-07-11",
"make": "VW",
"model": "Polo",
"variant": "1,6 ",
"vehicle_code": null
},
"country_id": 1,
"car_id": 113232,
"make_and_model_id": null,
"make": "VW",
"model": "Polo",
"variant": "1,6 ",
"chassis_number": "WVWZZZ6NZXW005709",
"registration_date": "1998-06-12",
"inspection_date": "2012-07-11"
}
To create this JSON API we’re using Grape.
When defining the response format in Grape as :json
, it automatically calls to_json
on the objects returned by the controller.
So in our app.rb
we just do
class Autobutler
format :json
content_type :json, 'application/json'
...
# Mount all APIs!
constants.each do |c|
mount const_get(c) if c.to_s =~ /^ApiV/
end
end
Now everything is “ugly” JSON.
To get pretty JSON, we have to create our own formatter, overriding Grape’s default.
The PrettyJSON
formatter could look like:
module PrettyJSON
def self.call(object, env)
JSON.pretty_generate(JSON.parse(object.to_json))
end
end
and then to use it, all we have to do is tell Grape:
class Autobutler
format :json
formatter :json, PrettyJSON
...
end
The trained programmer will notice something weird about the PrettyJSON#call
method.
Why are we calling object.to_json
and then parsing this as JSON, resulting in an hash again?
This is because we can have nested objects, which JSON.pretty_generate
cannot seem to handle, however JSON.parse(object.to_json)
will not give us nested objects, but rather nested hashes (yeah, hashes are objects too) which can be prettified by JSON.
Thus, with just 6 lines of extra code, we can have pretty JSON in our Grape API app.