Handle API response with value objects

This article is a quick tip for anyone working with APIs in a Rails application and parsing the data without modeling it. Let’s assume that we request the API to get the list of the users from which we would like to pull only active users and save some data for later usage:

response = Request.call('some_url')
parsed_response = JSON.parse(response)
 
eligible_users = []
 
parsed_response['people'].each do |person|
  if person['active'] == true
    eligible_users << {
      name: "#{person['firstName']} #{person['lastName']}",
      email: person['emailAddress']
    }
  end
end
 
eligible_users

The above code is a pretty common approach that you can spot in many legacy applications. If you would like to test this code, you would have to test the request, filtering, and parsing part at once. Also, such code is far from being responsible for one thing.

Usually, when using external APIs, you deal with some resources and data that can be structured similarly to the models in the Rails application. This is the case also right now.

We can say that we have the Person model, which responds to the following attributes:

  • active
  • name
  • e-mail

We also have code responsible for parsing the response, which we can call as PeopleParser. Let’s implement the parser first.

The parser for the people

The parser responsibility is to receive a raw response and provide a method that will return an array of the user’s attributes:

class PeopleParser
  def initialize(raw_response)
    @raw_response = raw_response
  end
 
  def people
    parsed_json['people']
  end
 
  private
 
  def parsed_json
    @parsed_json ||= JSON.parse(@raw_response)
  end
end

The parsing logic is now extracted (it would make more sense after introducing more refactoring so don’t worry about it now):

response = Request.call('some_url')
parser = PeopleParser.new(response)
 
eligible_users = []
 
parser.people.each do |person|
  if person['active'] == true
    eligible_users << {
      name: "#{person['firstName']} #{person['lastName']}",
      email: person['emailAddress']
    }
  end
end
 
eligible_users

The last step is to build the Person value object.

Value object for a person

All we need is just a pure ruby object that represents the domain which are the people in our case:

class Person
  def initialize(attributes)
    @attributes = attributes
  end
 
  def active?
    @attributes['active']
  end
 
  def email
    @attributes['emailAddress']
  end
 
  def name
    "#{@attributes['firstName']} #{@attributes['lastName']}"
  end
end

Just a little bit of parsing that is super easy to test. With such a class we can also update our parser:

class PeopleParser
  def initialize(raw_response)
    @raw_response = raw_response
  end
 
  def people
    parsed_json['people'].map { |attributes| Person.new(attributes) }
  end
 
  private
 
  def parsed_json
    @parsed_json ||= JSON.parse(@raw_response)
  end
end

We don’t have to worry now about transforming the attributes as parser would provide the necessary object:

response = Request.call('some_url')
parser = PeopleParser.new(response)
 
eligible_users = []
 
parser.people.each do |person|
  if person.active?
    eligible_users << {
      name: person.name,
      email: person.email
    }
  end
end
 
eligible_users

We now have our value objects in action. Guess what? It is not the end of the refactoring. How about duck typing?

Let person behave like a hash

To the array of the eligible users, we need to add a simple hash that consists of the two attributes. How about calling the to_h method that will transform the Person object into a hash? Let’s give it a try:

class Person
  def initialize(attributes)
    @attributes = attributes
  end
 
  def to_h
    {
      name: name,
      email: email
    }
  end
 
  def active?
    @attributes['active']
  end
 
  def email
    @attributes['emailAddress']
  end
 
  def name
    "#{@attributes['firstName']} #{@attributes['lastName']}"
  end
end

The code inside the main loop is less complicated and more intuitive:

response = Request.call('some_url')
parser = PeopleParser.new(response)
 
eligible_users = []
 
parser.people.each do |person|
  if person.active?
    eligible_users << person.to_h
  end
end
 
eligible_users

We could say that we are done, but there is still space for improvements.

Making things more readable

With the Ruby syntax we can make the above code a one liner:

response = Request.call('some_url')
parser = PeopleParser.new(response)
 
eligible_users = parser.people.select(&:active?).map(&:to_h)

I think we are finally done, nothing left to simplify or make more readable. I am aware that many people dislike such an approach as we transform 12 lines of code into 35 lines, but it’s just a demonstration, an idea I wanted to share.

I hope it would become more reasonable for you with more complex use cases where parsing is not about fetching values from three attributes. Domain design is a powerful tool that makes things more accessible, and value object is a friend of such approach.