- Notifications
You must be signed in to change notification settings - Fork1
Transform complex JSON data into custom Ruby objects
License
jessedoyle/shrink_wrap
Folders and files
Name | Name | Last commit message | Last commit date | |
---|---|---|---|---|
Repository files navigation
Shrink::Wrap is a dead-simple framework to manipulate and map JSON data to Ruby object instances.
Imagine a JSON structure such as:
{"name":"Milky Way","age":"13510000000","solarSystems": [ {"name":"Sol","age":"4571000000","planets": [ {"name":"mercury" }, {"name":"venus" }, {"name":"earth" } ] } ]}
With Shrink::Wrap, you'd be able to map the JSON data to Ruby instances as simply as:
data=JSON.parse(File.read('galaxy.json'))Galaxy.shrink_wrap(data)# => #<Galaxy:0x007fec71254828# @age=13510000000,# @name="Milky Way",# @solar_systems=# [#<SolarSystem:0x007fec71254850# @age=4571000000,# @name="Sol",# @planets=# [#<Planet:0x007fec712555c0 @name="MERCURY">,# #<Planet:0x007fec712553e0 @name="VENUS">,# #<Planet:0x007fec71255200 @name="EARTH">>]>]>
You must include theShrink::Wrap
module in your classes to gain access to theshrink_wrap
method.
For the example above, the implementation would look like:
require'shrink/wrap'classPlanetincludeShrink::Wraptransform(Shrink::Wrap::Transformer::Symbolize)translate(name:{from::name})coerce(name:->(v){v.upcase})attr_accessor:namedefinitialize(opts={})self.name=opts.fetch(:name)endendclassSolarSystemincludeShrink::Wraptransform(Shrink::Wrap::Transformer::Symbolize)translate(name:{from::name},age:{from::age},planets:{from::planets})coerce(age:->(v){v.to_i},planets:Array[Planet])attr_accessor:name,:age,:planetsdefinitialize(opts={})self.name=opts.fetch(:name)self.age=opts.fetch(:age)self.planets=opts.fetch(:planets)endendclassGalaxyincludeShrink::Wraptransform(Shrink::Wrap::Transformer::Symbolize)translate(name:{from::name},age:{from::age},solar_systems:{from::solarSystems})coerce(age:->(v){v.to_i},solar_systems:Array[SolarSystem])attr_accessor:name,:age,:solar_systemsdefinitialize(opts={})self.name=opts.fetch(:name)self.age=opts.fetch(:age)self.solar_systems=opts.fetch(:solar_systems)endend
Shrink::Wrap operations are always deterministically performed in the following order:
transform
translate
coerce
Shrink::Wrap will call theinitialize
method with the data after all operations have been completed.
Thetransform
operation accepts a transformation class as well as an options hash as parameters.
The transformation class is any Ruby class that inherits fromShrink::Wrap::Transformer::Base
.
The class must define atransform(data = {})
method that returns the data after transformations are applied.
You can chain transformers together by callingtransform
multiple times in your class. Transformers are always executed sequentially.
You can create your own transformer as such:
classFilterEmpty <Shrink::Wrap::Transformer::Basedeftransform(opts={})data.each_with_object({})do |(key,value),memo|memo[key]=valueunlessvalue.empty?endendend
Shrink::Wrap provides a few built-in transformer classes for use with common transformation patterns.
In Ruby, it's very common to convert Hash keys to Symbol instances.
The Symbolize transformer works similar to ActiveSupport'sdeep_symbolize_keys
method, but it is also able to traverse nested data structures (Array
,Enumerable
) and symbolize the keys of any nested elements as well.
The Symbolize transformer accepts an optionaldepth
parameter that defines that maximum depth for symbolization in nested data structures.
Example:
classExampleincludeShrink::Wraptransform(Shrink::Wrap::Transformer::Symbolize,depth:2)translate_allattr_accessor:datadefinitialize(data)self.data=dataendenddata={'root'=>[{'nested'=>'test'}]}instance=Example.shrink_wrap(data)instance.data# => {:root=>[{:nested=>"test"}]}
Some data Hashes contain keys that contain relevant data for object instances.
The CollectionFromKey transformer accepts a Hash option that contains a key => attribute mapping.
The transformation then creates an Array of elements taken from the key argument and merges the key into each element in the collection.
Example:
classExampleincludeShrink::Wraptransform(Shrink::Wrap::Transformer::CollectionFromKey,weekends::day)translate_allattr_accessor:datadefinitialize(data)self.data=dataendenddata={weekends:{saturday:{index:6},sunday:{index:0}}}instance=Example.shrink_wrap(data)instance.data# => {:weekends=>[{:index=>6, :day=>:saturday}, {:index=>0, :day=>:sunday}]}
Thetranslate
operation accepts a Hash that contains key/value pairs that map incoming data to attributes.
You can pass the following parameters for each attribute:
from
[required]: The input key that the attribute is mapped to.allow_nil
[optional]: Iftrue
, the mapped value may benil
. Whenfalse
, raisesKeyError
if the mapped value isnil
.default
[optional]: AProc
orLambda
that returns a particular value. This argument will be called if the value isnil
.
By default, any attributes not specified in atranslate
call are not passed to the underlying object during initialization.
You can calltranslate_all
if you wish to pass all of the data but don't wish to explicitly define the attributes in a correspondingtranslate
call.
Thecoerce
operation accepts a hash of attribute keys and coercion values.
Coercions must be one of the following types:
Class
: Tries callingClass.shrink_wrap(data)
,Class.coerce(data)
, thenClass.new(data)
, returning the first successful result.Enumerable
: Coerces the value into the collection of type defined as the Enumerable elements. The first element of the enumerable must be a class name (eg.Array[MyClass]
,Hash[MyClass => MyClass]
).Lambda/Proc
: Coerces the value by calling theProc
with the value as the only argument.
Bug reports and pull requests are welcome on GitHub athttps://github.com/jessedoyle/shrink_wrap.
When making code changes please fork the main repository, create a feature branch and then create a pull request with your change. All code changes must contain adequate test coverage.
This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to theContributor Covenant code of conduct.
The gem is available as open source under the terms of theMIT License.
Everyone interacting in the ShrinkWrap project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow thecode of conduct.
About
Transform complex JSON data into custom Ruby objects