Skip to content

Instantly share code, notes, and snippets.

@jlecour
Last active December 24, 2015 13:39
Show Gist options
  • Save jlecour/dc18c1202e0e1bbe14e3 to your computer and use it in GitHub Desktop.
Save jlecour/dc18c1202e0e1bbe14e3 to your computer and use it in GitHub Desktop.

I have a JSON blob, received from a webservice and I'd like to build some Ruby Objects.

I don't want to :

  • have a 1-1 mapping of each JSON key to a Ruby attribute

I want to :

  • rename attributes names
  • coerce attributes values
  • have nested objects and collection of objects
  • add custom methods to Ruby objects
  • be able to lookup values in parent objects, or pass them when instanciating children objects
# Here is an example using APISmith https://github.com/filtersquad/api_smith
# It could be simpler, especially with RateInfoList and RoomGroup
# that are useless intermediate data structures
# I'm not sure about the custom transformers for Date, Boolean and BigDecimal
# it seems a bit weird to do this but it works.
require 'rubygems'
require 'pathname'
require 'bigdecimal'
require 'multi_json'
require 'money'
require 'pry'
require 'api_smith/smash.rb'
module Mapping
class DateParser
def self.call(v)
Date.strptime(v, '%d/%M/%Y')
end
end
class BigDecimalParser
def self.call(v)
BigDecimal.new(v)
end
end
class BooleanParser
def self.call(v)
true_values = %w[ 1 on t true y yes ].freeze
false_values = %w[ 0 off f false n no ].freeze
boolean_map = ::Hash[ true_values.product([ true ]) + false_values.product([ false ]) ].freeze
case v
when String
boolean_map.fetch(v.downcase)
when Numeric
v > 0
else
!!v
end
end
end
class Room < APISmith::Smash
property :number_of_adults, :from => :numberOfAdults, :transformer => :to_i
property :number_of_children, :from => :numberOfChildren, :transformer => :to_i
end
class PriceRateInfo < APISmith::Smash
property :average_base_rate, :from => :@averageBaseRate, :transformer => BigDecimalParser
property :average_rate, :from => :@averageRate, :transformer => BigDecimalParser
property :commissionable_usd_total, :from => :@commissionableUsdTotal, :transformer => BigDecimalParser
property :currency_code, :from => :@currencyCode
property :max_nightly_rate, :from => :@maxNightlyRate, :transformer => BigDecimalParser
property :nightly_rate_total, :from => :@nightlyRateTotal, :transformer => BigDecimalParser
property :total, :from => :@total, :transformer => BigDecimalParser
def price
::Money.new(total * 100, currency_code)
end
end
class RoomGroup < APISmith::Smash
property :room, :from => :Room, :transformer => Room
end
class RateInfo < APISmith::Smash
property :price_breakdown, :from => :@priceBreakdown, :transformer => BooleanParser
property :promo, :from => :@promo, :transformer => BooleanParser
property :rate_change, :from => :@rateChange, :transformer => BooleanParser
property :rooms, :from => :RoomGroup, :transformer => RoomGroup
property :chargeable_rate_info, :from => :ChargeableRateInfo, :transformer => PriceRateInfo
property :converted_rate_info, :from => :ConvertedRateInfo, :transformer => PriceRateInfo
end
class RateInfoList < APISmith::Smash
property :rate_info, :from => :RateInfo, :transformer => RateInfo
end
class Room < APISmith::Smash
property :cancellation_policy, :from => :cancellationPolicy
property :rate_code, :from => :rateCode, :transformer => :to_i
property :room_type_code, :from => :roomTypeCode, :transformer => :to_i
property :rate_description, :from => :rateDescription
property :room_type_description, :from => :roomTypeDescription
property :supplier_type, :from => :supplierType
property :tax_rate, :from => :taxRate, :transformer => BigDecimalParser
property :rate_change, :from => :rateChange, :transformer => BooleanParser
property :non_refundable, :from => :nonRefundable, :transformer => BooleanParser
property :guarantee_required, :from => :guaranteeRequired, :transformer => BooleanParser
property :deposit_required, :from => :depositRequired, :transformer => BooleanParser
property :immediate_charge_required, :from => :immediateChargeRequired, :transformer => BooleanParser
property :current_allotment, :from => :currentAllotment, :transformer => :to_i
property :promo_description, :from => :promoDescription
property :smoking_preferences, :from => :smokingPreferences
property :rate_occupancy_per_room, :from => :rateOccupancyPerRoom, :transformer => :to_i
property :quoted_occupancy, :from => :quotedOccupancy, :transformer => :to_i
property :min_guest_age, :from => :minGuestAge, :transformer => :to_i
property :rate_infos, :from => :RateInfos, :transformer => RateInfoList
end
class HotelResponse < APISmith::Smash
property :hotel_id, :from => :hotelId, :transformer => :to_i
property :arrival_date, :from => :arrivalDate, :transformer => DateParser
property :departure_date, :from => :departureDate, :transformer => DateParser
property :rooms_count, :from => :numberOfRoomsRequested, :transformer => :to_i
property :rate_key, :from => :rateKey
property :rooms, :from => :HotelRoomResponse, :transformer => Room
end
end
json = Pathname.new('/Users/jlecour/Desktop/data.json').read;
data = MultiJson.load(json)["HotelRoomAvailabilityResponse"];
response = Mapping::HotelResponse.new(data)
puts response.rooms[0].rate_infos.rate_info.chargeable_rate_info.price
binding.pry
{
"HotelRoomAvailabilityResponse": {
"@size": "1",
"hotelId": 369364,
"arrivalDate": "10/21/2013",
"departureDate": "10/22/2013",
"hotelName": "1 Lexham Gardens Hotel",
"hotelAddress": "1 Lexham Gardens",
"hotelCity": "London",
"hotelCountry": "GB",
"numberOfRoomsRequested": 1,
"rateKey": "8b2829d3-f997-4a7b-9b04-06c1cecab544",
"HotelRoomResponse": [{
"cancellationPolicy": "Dieses Angebot ist nicht erstattungsfähig und kann nicht geändert oder storniert werden. Wenn Sie diese Buchung ändern oder stornieren, wird Ihnen Ihr Geld nicht zurückerstattet.",
"rateCode": 200457634,
"roomTypeCode": 200038931,
"rateDescription": "Standard-Zweibettzimmer - Nicht erstattungsfähiges Angebot inkl. Frühstück",
"roomTypeDescription": "Standard-Zweibettzimmer - Nicht erstattungsfähiges Angebot inkl. Frühstück",
"supplierType": "E",
"taxRate": "0.00",
"rateChange": false,
"nonRefundable": true,
"guaranteeRequired": false,
"depositRequired": true,
"immediateChargeRequired": true,
"currentAllotment": 2,
"propertyId": 4149877,
"promoId": 200790274,
"promoDescription": "Bester verfügbarer Preis",
"BedTypes": {
"@size": "1",
"BedType": {
"@id": "25",
"description": "2 Einzelbetten"
}
},
"CancelPolicyInfoList": {
"CancelPolicyInfo": [{
"versionId": 208899887,
"cancelTime": "18:00:00",
"startWindowHours": 0,
"nightCount": 0,
"percent": 100,
"currencyCode": "USD",
"timeZoneDescription": "(GMT) Greenwich Mean Time : Dublin, Edinburgh, Lisbon, London"
}, {
"versionId": 209024183,
"cancelTime": "18:00:00",
"startWindowHours": 999,
"nightCount": 0,
"percent": 100,
"currencyCode": "USD",
"timeZoneDescription": "(GMT) Greenwich Mean Time : Dublin, Edinburgh, Lisbon, London"
}]
},
"smokingPreferences": "NS",
"rateOccupancyPerRoom": 2,
"quotedOccupancy": 2,
"minGuestAge": 0,
"RateInfos": {
"@size": "1",
"RateInfo": {
"@priceBreakdown": "true",
"@promo": "true",
"@rateChange": "false",
"RoomGroup": {
"Room": {
"numberOfAdults": 2,
"numberOfChildren": 0
}
},
"ChargeableRateInfo": {
"@averageBaseRate": "153.87",
"@averageRate": "146.18",
"@commissionableUsdTotal": "146.18",
"@currencyCode": "USD",
"@maxNightlyRate": "146.18",
"@nightlyRateTotal": "146.18",
"@total": "146.18",
"NightlyRatesPerRoom": {
"@size": "1",
"NightlyRate": {
"@baseRate": "153.87",
"@rate": "146.18",
"@promo": "true"
}
}
},
"ConvertedRateInfo": {
"@averageBaseRate": "2793.0176",
"@averageRate": "2653.4302",
"@currencyCode": "CZK",
"@maxNightlyRate": "2653.4302",
"@nightlyRateTotal": "2653.4302",
"@total": "2653.43",
"NightlyRatesPerRoom": {
"@size": "1",
"NightlyRate": {
"@baseRate": "2793.0176",
"@rate": "2653.4302",
"@promo": "true"
}
}
}
}
},
"ValueAdds": {
"@size": "1",
"ValueAdd": {
"@id": "2",
"description": "Kontinentales Frühstück"
}
}
}]
}
}
# encoding: utf-8
require 'money'
module Mapping
class Room
attribute :number_of_adults, Integer
attribute :number_of_children, Integer
end
class PriceRateInfo
attribute :average_base_rate, BigDecimal
attribute :average_rate, BigDecimal
attribute :commissionable_usd_total, BigDecimal
attribute :currency_code, String
attribute :max_nightly_rate, BigDecimal
attribute :nightly_rate_total, BigDecimal
attribute :total, BigDecimal
def price
Money.new(total * 100, currency_code)
end
end
class RateInfo
attribute :price_breakdown, Boolean
attribute :promo, Boolean
attribute :rate_change, Boolean
attribute :rooms, [Room]
attribute :chargeable_rate_info, PriceRateInfo
attribute :converted_rate_info, PriceRateInfo
end
class RoomResponse
attribute :cancellation_policy, String
attribute :rate_code, Integer
attribute :room_type_code, Integer
attribute :rate_description, String
attribute :room_type_description, String
attribute :supplier_type, String
attribute :tax_rate, BigDecimal
attribute :rate_change, Boolean
attribute :non_refundable, Boolean
attribute :guarantee_required, Boolean
attribute :deposit_required, Boolean
attribute :immediate_charge_required, Boolean
attribute :current_allotment, Integer
attribute :promo_description, String
attribute :smoking_preferences, String
attribute :rate_occupancy_per_room, Integer
attribute :quoted_occupancy, Integer
attribute :min_guest_age, Integer
attribute :rate_infos, [RateInfo]
end
class HotelResponse
attribute :hotel_id, Integer
attribute :arrival_date, Date
attribute :departure_date, Date
attribute :rooms, Integer
attribute :rate_key, String
attribute :rooms, [RoomResponse]
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment