Ruby JSON Mapping

Usually when we build apps we generate JSON on the backend using Ruby/Rails and send it to iOS side. On the iOS side we parse it and map it with Mantle a great tool for model layer on iOS.

Lately I had a need to do the same type of parsing using Ruby. My research gave me many results for parsing JSON but only a few that were parsing and mapping JSON to Ruby objects. json_mapper and representable

Here is an example of JSON that I was mapping:

{
        "status" : 200,
        "requestId" : "9ffd07d9-2ef9-4fd6-994a-228472a5be6f",
        "category" : [ {
          "name" : "Other",
          "code" : "OTHER"
        } ],
        "logo" : "https://d2ojpxxtu63wzl.cloudfront.net/static/e9f3aeb8965684906efa7ae514988ffb_0837a93ef09a70f8b9ff73efac18176225fd0b9cb8bf84a60c5926701b4c5033",
        "website" : "https://www.fullcontact.com",
        "languageLocale" : "en",
        "organization" : {
          "name" : "FullContact Inc.",
          "approxEmployees" : 50,
          "founded" : "2010",
          "overview" : "Solving the world's contact information problem!",
          "contactInfo" : {
            "emailAddresses" : [ {
              "value" : "support@fullcontact.com",
              "label" : "support"
            }, {
              "value" : "sales@fullcontact.com",
              "label" : "sales"
            } ],
            "phoneNumbers" : [ {
              "number" : "+1 (888) 330-6943",
              "label" : "other"
            } ],
            "addresses" : [ {
              "addressLine1" : "1755 Blake Street",
              "addressLine2" : "Suite 450",
              "locality" : "Denver",
              "region" : {
                "name" : "Colorado",
                "code" : "CO"
              },
              "country" : {
                "name" : "United States",
                "code" : "US"
              },
              "postalCode" : "80202",
              "label" : "work"
            } ]
          },
          "links" : [ {
            "url" : "https://www.fullcontact.com/developer",
            "label" : "other"
          }, {
            "url" : "https://fullcontact.com/blog",
            "label" : "blog"
          }, {
            "url" : "https://www.youtube.com/watch?v=koFtyUDbYak",
            "label" : "youtube"
          }, {
            "url" : "https://www.fullcontact.com/home/feed",
            "label" : "rss"
          }, {
            "url" : "https://www.fullcontact.com/feed",
            "label" : "rss"
          }, {
            "url" : "https://www.fullcontact.com/comments/feed",
            "label" : "rss"
          } ],
          "images" : [ {
            "url" : "https://d2ojpxxtu63wzl.cloudfront.net/static/1c390465c39c998a59c8717034982dfc_ff8af9a9f14da29ee687053e65d2ed103b8ab6f95b1f4d0147b9077ea04a3d6c",
            "label" : "facebook"
          }, {
            "url" : "https://d2ojpxxtu63wzl.cloudfront.net/static/edaa53d9a080aea37ddfb85d775620a9_98a2d7beef6a5b4a53f43da4dd1a90bda21dc18f755394fdbf9b6cf3283853a0",
            "label" : "twitter"
          }, {
            "url" : "https://d2ojpxxtu63wzl.cloudfront.net/static/1bacd7306731a30d2a9f024eeb1dcff1_94d77dcdedbfe40707ac4a75ca4f4d2978bffc20b2e33a3288ea9e4d47f5af6c",
            "label" : "twitter"
          }, {
            "url" : "https://d2ojpxxtu63wzl.cloudfront.net/static/3f64db7ba9331fbd1e4cc11655e2d3d4_a2477a83cafc8a98d5533f3617f0b1db2796ad0826482e2eabdc8d3345d70c17",
            "label" : "twitter"
          }, {
            "url" : "https://d2ojpxxtu63wzl.cloudfront.net/static/ee07ac81180408fde663426d3b0afb3f_3a1154347631c037b9bd2b2f33d4cbc8511d58f5c11ad3cbbc319957d1a5149b",
            "label" : "pinterest"
          }, {
            "url" : "https://d2ojpxxtu63wzl.cloudfront.net/static/80885c5e8b570e69bdc55d29aad115cd_a1ce9fb51ea43971d861e452034056d807422a391ac8e27f76ee4a9e803698d1",
            "label" : "googleplus"
          }, {
            "url" : "https://d2ojpxxtu63wzl.cloudfront.net/static/4be5211e4b0129d1c8d41e84f257f343_3d84b3de68d6060243972af12a8ca67c4a595fd86a4419d50bf429e6d778ce2d",
            "label" : "other"
          }, {
            "url" : "https://d2ojpxxtu63wzl.cloudfront.net/static/7e9aa6402ff2975e297a01243c358619_c0b8d4a63a52f4a47106494561c0332b79f848b40fcbe92336a0a17b843f44f8",
            "label" : "other"
          } ],
          "keywords" : [ "APIs", "Boulder", "Contact Management", "Denver", "Developer APIs", "Social Media", "Techstars" ]
        },
        "socialProfiles" : [ {
          "bio" : "FullContact automatically cleans your contacts, enriches them with social profile data, and continually syncs them with all your contact sources. For developers, we offer a suite of contact management APIs to normalize, de-duplicate, and enrich contact data with social profiles. As Dropbox did for files and Evernote did for notes, FullContact is doing for contacts. And we’re providing it to individuals, businesses, and developers.",
          "typeId" : "facebook",
          "typeName" : "Facebook",
          "url" : "https://www.facebook.com/FullContactAPI",
          "username" : "FullContactAPI",
          "id" : "159926450745554"
        }, {
          "bio" : "We're solving the world's contact information problem. Get your contacts under control with @FullContactApp & check out @FullContactAPI for our APIs.",
          "followers" : 6277,
          "following" : 1758,
          "typeId" : "twitter",
          "typeName" : "Twitter",
          "url" : "https://twitter.com/FullContactInc",
          "username" : "FullContactInc",
          "id" : "142954090"
        }, {
          "bio" : "The API that turns partial contact information into full contact information. We provide data enrichment, de-duplication, normalization, and much more.",
          "followers" : 5032,
          "following" : 2444,
          "typeId" : "twitter",
          "typeName" : "Twitter",
          "url" : "https://twitter.com/FullContactAPI",
          "username" : "FullContactAPI",
          "id" : "340611236"
        }, {
          "bio" : "Keep your contact information clean, complete & current across all your address books.",
          "followers" : 3171,
          "following" : 1561,
          "typeId" : "twitter",
          "typeName" : "Twitter",
          "url" : "https://twitter.com/FullContactApp",
          "username" : "FullContactApp",
          "id" : "451688048"
        }, {
          "bio" : "FullContact's address book brings all of your contacts into one place and keeps them automatically up to date on the web, as well as on your iPhone and iPad. Add photos to your contacts. Find them on social networks like Twitter, Facebook, LinkedIn and of course AngelList. It's the address book that busy professionals from any walk of life can appreciate, and best of all it's free. For developers, the suite of FullContact APIs builds powerful, complete profiles of contacts that can be included in any application.",
          "followers" : 259,
          "typeId" : "angellist",
          "typeName" : "AngelList",
          "url" : "https://angel.co/fullcontact",
          "username" : "fullcontact"
        }, {
          "bio" : "FullContact provides a suite of cloud-based contact management solutions for businesses, developers, and individuals.",
          "typeId" : "crunchbasecompany",
          "typeName" : "CrunchBase",
          "url" : "http://www.crunchbase.com/organization/fullcontact",
          "username" : "fullcontact"
        }, {
          "bio" : "FullContact is the API that keeps contact information current. We build APIs that developers can integrate into their applications using any language.",
          "followers" : 28,
          "following" : 55,
          "typeId" : "pinterest",
          "typeName" : "Pinterest",
          "url" : "http://www.pinterest.com/fullcontact/",
          "username" : "fullcontact"
        }, {
          "bio" : "All your contacts in one place and automatically up-to-date. we're solving the world's contact information problem at https://www.fullcontact.com.",
          "typeId" : "google",
          "typeName" : "GooglePlus",
          "url" : "https://plus.google.com/u/0/107620035082673219790",
          "id" : "107620035082673219790"
        }, {
          "typeId" : "klout",
          "typeName" : "Klout",
          "url" : "http://klout.com/FullContactAPI",
          "username" : "FullContactAPI",
          "id" : "33777001971317895"
        }, {
          "bio" : "FullContact is solving the world's contact information problem by providing APIs to software developers to keep contact information clean, complete and current. FullContact provides identity resolution for all of the disparate pieces of contact information out there on the web. We do this by aggregating billions of contact records, all with numerous attributes, including quality, freshness and frequency. Our patent pending algorithms process all of this data and automatically produce clean, accurate full contact records. As a final step, we then check each data element to make sure that it's publicly available before providing it to our customers. FullContact is a TechStars Boulder 2011 Company.",
          "typeId" : "linkedincompany",
          "typeName" : "LinkedIn",
          "url" : "https://www.linkedin.com/company/fullcontact-inc-",
          "username" : "fullcontact-inc-",
          "id" : "2431118"
        } ],
        "traffic" : {
          "topCountryRanking" : [ {
            "rank" : 7770,
            "locale" : "us"
          }, {
            "rank" : 11728,
            "locale" : "in"
          }, {
            "rank" : 11388,
            "locale" : "gb"
          } ],
          "ranking" : [ {
            "rank" : 18640,
            "locale" : "global"
          }, {
            "rank" : 7770,
            "locale" : "us"
          } ]
        }
      }

json_mapper

Here is how quick mapping of some of the data looks like with json_mapper:

class Company
  include JSONMapper

  json_attribute :logo, String
  json_attribute :website, String

  json_attribute :name, { :organization => :name }, String
  json_attribute :number_of_employees, { :organization => :approx_employees }, Integer
  json_attribute :founded_date, { :organization => :founded }, String

  json_attribute :links, { :organization => :links }, Array
  json_attribute :images, { :organization => :images }, Array
  json_attribute :keywords, { :organization => :keywords }, Array

  json_attribute :social_profiles, Array          

end

Initialization of mapping was using this call: company = Company.parse company_json

representable

Here is what mapping looks like using representable gem:

require 'representable/json'

class Company

  attr_accessor :logo, :website, :name, :number_of_employees, :founded_date, :links, :images, :keywords

  class CompanyRepresenter < Representable::Decorator
    include Representable::JSON

    property :logo
    property :website
    nested :organization do
      property :name
      property :number_of_employees, as: :approx_employees
      property :founded_date, as: :founded_date

      property :links
      property :images
      property :keywords
    end
  end

end

And mapping initialization is: Company::CompanyRepresenter.new(company).from_json(company_json)

Of course I could've extracted CompanyRepresenter into its own file.

Overall it feels like representable is a bit more "wordy" and needs some setup to get going comparing to json_mapper but at the same time it is more powerful and flexible.
Oh, and one disadvantage json_mapper has is the fact that you have to define the type of your properties/attributes.

Conclusion

Most likely we will go with json_mapper and use it until we reach its limits in our internal small projects because it seems to be easier and quicker to hack together. But we'll use representable in client/production projects for its flexibility and power. Also it makes sense to separate mapping from models.

comments powered by Disqus