2.17 Addendum: Rendering JSON

Imagine that we wanted to build a native iPhone app that asked our server for some information; in this simple example, for a random computer move and an outcome, but in the real-world things like the local weather given a latitude and a longitude.

Rather than rendering a pre-defined message in plain text, it’s usually more helpful to the iPhone developer to render the data in JSON format, so that they can parse it, easily fetch whichever values they need, and assemble their own interface.

Here is some JSON that would be convenient for an external application to parse:

{ "player_move":"rock", "comp_move":"paper", "outcome":"lost" }

Notice that JSON uses strings as keys — this is because JavaScript doesn’t have the equivalent of Ruby’s Symbol class. Also, there are no hash rockets; JSON just uses colons to separate keys and values.

Fortunately, just as it was easy for us to convert a String containing JSON into Ruby Arrays/Hashes using the JSON.parse method, it is also easy for us to go in the other direction: both Array and Hash have methods called .to_json. Let’s create a Ruby Hash that resembles the JSON above:

response_hash = { :player_move => "rock", :comp_move => "paper", :outcome => "lost" }

We can then convert this into a String in JSON format with .to_json:

response_hash.to_json

returns:

"{\"player_move\":\"rock\",\"comp_move\":\"paper\",\"outcome\":\"lost\"}"

The \" represents double-quotes; we need the backslash, known as an “escape”, because we’re already within a double-quoted string and don’t want to terminate it. You can puts the string to see it formatted:

puts response_hash.to_json

displays:

{"player_move":"rock","comp_move":"paper","outcome":"lost"}

Great! That means we can update our action if we want to send back JSON instead:

class ApplicationController < ActionController::Base
  def play_rock
    moves = ["rock", "paper", "scissors"]

    comp_move = moves.sample
    
    if comp_move == "rock"
      outcome = "tied"
    elsif comp_move == "paper"
      outcome = "lost"
    elsif comp_move == "scissors"
      outcome = "won"
    end

    response_hash = { :player_move => "rock", :comp_move => "paper", :outcome => "lost" }

    render({ :plain =>  response_hash.to_json })
  end
end

Congratulations — you just built your first API endpoint! 🙌🏾