Return Struct as JSON
I am working on a side project in which I want to use Sinatra as a simple backend and experiment with a more rich Javascript front end.
The Setup
The idea was to respond to a GET
request to my endpoint with a JSON-ified array of simple Ruby Structs.
The route looked something like
get '/api/v1/tracks' do
track_lister = TackLister.new
track_lister.tracks.to_json
end
the call to the TrackLister
's tracks
was to return an array of Track
structs.
class TrackLister
Track = Struct.new(:name, :artist)
def tracks
[].tap do |tracks|
2.times { |i| tracks << Track.new("Track #{i}", "Band #{i}") }
end
end
end
firing up irb
this seems to have worked well enough
>> TrackLister.new.tracks
#=> [
#<struct TrackLister::Track name="Track 0", artist="Band 0">,
#<struct TrackLister::Track name="Track 1", artist="Band 1">
]
The Problem
The to_json
version of my response looks a little funny
>> require 'json' #=> true
>> TrackLister.new.tracks.to_json
#=> "[\"#<struct TrackLister::Track name=\\\"Track 0\\\", artist=\\\"Band 0\\\">\",\"#<struct TrackLister::Track name=\\\"Track 1\\\", artist=\\\"Band 1\\\">\"]"
Since this was supposed to be a response Javascript would need to parse I figured I would check to see how Javascript would handle it:
>> JSON.parse(my_long_json_string)
=> [
"#<struct TrackLister::Track name="Track 0", artist="Band 0">",
"#<struct TrackLister::Track name="Track 1", artist="Band 1">"
]
It can parse the JSON but it results in an array of strings and there seemed to be no easy way for me to get to the data
>> tracks[0]
=> "#<struct TrackLister::Track name="Track 0", artist="Band 0">"
>> tracks[0]['name']
=> undefined
>> tracks[0].name
=> undefined
>> JSON.parse(tracks[0])
=> Uncaught SyntaxError: Unexpected identifier
It seemed that the JSON version of my structs was not being deserialized the way I would have liked.
The Solution
The solution was to serialize my data in a way that Javascript would like and could easily deserialize - a Hash
.
With the addition of to_h
in Ruby 2 converting my Track
struct into a Hash
was very easy:
>> TrackLister.new.tracks.map(&:to_h)
#=> [{:name=>"Track 0", :artist=>"Band 0"}, {:name=>"Track 1", :artist=>"Band 1"}]
this could then be converted to JSON just like before
>> TrackLister.new.tracks.map(&:to_h).to_json
#=> "[{\"name\":\"Track 0\",\"artist\":\"Band 0\"},{\"name\":\"Track 1\",\"artist\":\"Band 1\"}]"
Now when test out this new JSON string in Javascript's JSON.parse
I end up with Javascript objects
>> tracks = JSON.parse(my_long_json_string)
=> [Object, Object]
which allow me to easily access my data's attributes
>> tracks[0]
=> Object {name: "Track 0", artist: "Band 0"}
>> tracks[0].name
=> "Track 0"
>> tracks[0].artist
=> "Band 0"