{"id":17681246,"url":"https://github.com/jessedoyle/shrink_wrap","last_synced_at":"2025-06-24T10:05:41.714Z","repository":{"id":50218503,"uuid":"160109812","full_name":"jessedoyle/shrink_wrap","owner":"jessedoyle","description":"Transform complex JSON data into custom Ruby objects","archived":false,"fork":false,"pushed_at":"2023-02-04T01:39:35.000Z","size":37,"stargazers_count":3,"open_issues_count":0,"forks_count":1,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-05-06T09:03:33.384Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jessedoyle.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2018-12-03T00:19:41.000Z","updated_at":"2023-02-13T08:35:07.000Z","dependencies_parsed_at":"2023-02-18T13:01:19.444Z","dependency_job_id":null,"html_url":"https://github.com/jessedoyle/shrink_wrap","commit_stats":null,"previous_names":[],"tags_count":6,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jessedoyle%2Fshrink_wrap","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jessedoyle%2Fshrink_wrap/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jessedoyle%2Fshrink_wrap/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jessedoyle%2Fshrink_wrap/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jessedoyle","download_url":"https://codeload.github.com/jessedoyle/shrink_wrap/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":253837459,"owners_count":21971984,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-10-24T09:10:37.023Z","updated_at":"2025-05-12T23:12:09.662Z","avatar_url":"https://github.com/jessedoyle.png","language":"Ruby","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Shrink::Wrap\n\n## What is Shrink::Wrap?\n\nShrink::Wrap is a dead-simple framework to manipulate and map JSON data to Ruby object instances.\n\n## Basic Example\n\nImagine a JSON structure such as:\n\n```json\n{\n  \"name\": \"Milky Way\",\n  \"age\": \"13510000000\",\n  \"solarSystems\": [\n    {\n      \"name\": \"Sol\",\n      \"age\": \"4571000000\",\n      \"planets\": [\n        {\n          \"name\": \"mercury\"\n        },\n        {\n          \"name\": \"venus\"\n        },\n        {\n          \"name\": \"earth\"\n        }\n      ]\n    }\n  ]\n}\n```\n\nWith Shrink::Wrap, you'd be able to map the JSON data to Ruby instances as simply as:\n\n```ruby\ndata = JSON.parse(File.read('galaxy.json'))\nGalaxy.shrink_wrap(data) # =\u003e #\u003cGalaxy:0x007fec71254828\n # @age=13510000000,\n # @name=\"Milky Way\",\n # @solar_systems=\n #  [#\u003cSolarSystem:0x007fec71254850\n #    @age=4571000000,\n #    @name=\"Sol\",\n #    @planets=\n #     [#\u003cPlanet:0x007fec712555c0 @name=\"MERCURY\"\u003e,\n #      #\u003cPlanet:0x007fec712553e0 @name=\"VENUS\"\u003e,\n #      #\u003cPlanet:0x007fec71255200 @name=\"EARTH\"\u003e\u003e]\u003e]\u003e\n```\n\n## How Does it Work?\n\nYou must include the `Shrink::Wrap` module in your classes to gain access to the `shrink_wrap` method.\n\nFor the example above, the implementation would look like:\n\n```ruby\nrequire 'shrink/wrap'\n\nclass Planet\n  include Shrink::Wrap\n\n  transform(Shrink::Wrap::Transformer::Symbolize)\n\n  translate(\n    name: { from: :name }\n  )\n\n  coerce(\n    name: -\u003e(v) { v.upcase }\n  )\n\n  attr_accessor :name\n\n  def initialize(opts = {})\n    self.name = opts.fetch(:name)\n  end\nend\n\nclass SolarSystem\n  include Shrink::Wrap\n\n  transform(Shrink::Wrap::Transformer::Symbolize)\n\n  translate(\n    name: { from: :name },\n    age: { from: :age },\n    planets: { from: :planets }\n  )\n\n  coerce(\n    age: -\u003e(v) { v.to_i },\n    planets: Array[Planet]\n  )\n\n  attr_accessor :name, :age, :planets\n\n  def initialize(opts = {})\n    self.name = opts.fetch(:name)\n    self.age = opts.fetch(:age)\n    self.planets = opts.fetch(:planets)\n  end\nend\n\nclass Galaxy\n  include Shrink::Wrap\n\n  transform(Shrink::Wrap::Transformer::Symbolize)\n\n  translate(\n    name: { from: :name },\n    age: { from: :age },\n    solar_systems: { from: :solarSystems }\n  )\n\n  coerce(\n    age: -\u003e(v) { v.to_i },\n    solar_systems: Array[SolarSystem]\n  )\n\n  attr_accessor :name, :age, :solar_systems\n\n  def initialize(opts = {})\n    self.name = opts.fetch(:name)\n    self.age = opts.fetch(:age)\n    self.solar_systems = opts.fetch(:solar_systems)\n  end\nend\n```\n\n## Order of Operations\n\nShrink::Wrap operations are always deterministically performed in the following order:\n\n1. `transform`\n2. `translate`\n3. `coerce`\n\nShrink::Wrap will call the `initialize` method with the data after all operations have been completed.\n\n## Transform\n\nThe `transform` operation accepts a transformation class as well as an options hash as parameters.\n\nThe transformation class is any Ruby class that inherits from `Shrink::Wrap::Transformer::Base`.\n\nThe class must define a `transform(data = {})` method that returns the data after transformations are applied.\n\nYou can chain transformers together by calling `transform` multiple times in your class. Transformers are always executed sequentially.\n\nYou can create your own transformer as such:\n\n```ruby\nclass FilterEmpty \u003c Shrink::Wrap::Transformer::Base\n  def transform(opts = {})\n    data.each_with_object({}) do |(key, value), memo|\n      memo[key] = value unless value.empty?\n    end\n  end\nend\n```\n\n### Built In Transformers\n\nShrink::Wrap provides a few built-in transformer classes for use with common transformation patterns.\n\n#### Shrink::Wrap::Transformer::Symbolize\n\nIn Ruby, it's very common to convert Hash keys to Symbol instances.\n\nThe Symbolize transformer works similar to ActiveSupport's [`deep_symbolize_keys`](https://api.rubyonrails.org/classes/Hash.html#method-i-deep_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.\n\nThe Symbolize transformer accepts an optional `depth` parameter that defines that maximum depth for symbolization in nested data structures.\n\nExample:\n\n```ruby\nclass Example\n  include Shrink::Wrap\n\n  transform(Shrink::Wrap::Transformer::Symbolize, depth: 2)\n  translate_all\n\n  attr_accessor :data\n\n  def initialize(data)\n    self.data = data\n  end\nend\n\ndata = { 'root' =\u003e [{ 'nested' =\u003e 'test' }] }\ninstance = Example.shrink_wrap(data)\ninstance.data # =\u003e {:root=\u003e[{:nested=\u003e\"test\"}]}\n```\n\n#### Shrink::Wrap::Transformer::CollectionFromKey\n\nSome data Hashes contain keys that contain relevant data for object instances.\n\nThe CollectionFromKey transformer accepts a Hash option that contains a key =\u003e attribute mapping.\n\nThe transformation then creates an Array of elements taken from the key argument and merges the key into each element in the collection.\n\nExample:\n\n```ruby\nclass Example\n  include Shrink::Wrap\n\n  transform(Shrink::Wrap::Transformer::CollectionFromKey, weekends: :day)\n  translate_all\n\n  attr_accessor :data\n\n  def initialize(data)\n    self.data = data\n  end\nend\n\ndata = {\n  weekends: {\n    saturday: {\n      index: 6\n    },\n    sunday: {\n      index: 0\n    }\n  }\n}\ninstance = Example.shrink_wrap(data)\ninstance.data # =\u003e {:weekends=\u003e[{:index=\u003e6, :day=\u003e:saturday}, {:index=\u003e0, :day=\u003e:sunday}]}\n```\n\n## Translate\n\nThe `translate` operation accepts a Hash that contains key/value pairs that map incoming data to attributes.\n\nYou can pass the following parameters for each attribute:\n\n* `from` [**required**]: The input key that the attribute is mapped to.\n* `allow_nil` [**optional**]: If `true`, the mapped value may be `nil`. When `false`, raises `KeyError` if the mapped value is `nil`.\n* `default` [**optional**]: A `Proc` or `Lambda` that returns a particular value. This argument will be called if the value is `nil`.\n\n### Passing Data During Initialization\n\nBy default, any attributes not specified in a `translate` call are not passed to the underlying object during initialization.\n\nYou can call `translate_all` if you wish to pass all of the data but don't wish to explicitly define the attributes in a corresponding `translate` call.\n\n## Coerce\n\nThe `coerce` operation accepts a hash of attribute keys and coercion values.\n\nCoercions must be one of the following types:\n\n* `Class`: Tries calling `Class.shrink_wrap(data)`, `Class.coerce(data)`, then `Class.new(data)`, returning the first successful result.\n* `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 =\u003e MyClass]`).\n* `Lambda/Proc`: Coerces the value by calling the `Proc` with the value as the only argument.\n\n## Contributing\n\nBug reports and pull requests are welcome on GitHub at https://github.com/jessedoyle/shrink_wrap.\n\nWhen 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.\n\nThis project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.\n\n## License\n\nThe gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).\n\n## Code of Conduct\n\nEveryone interacting in the ShrinkWrap project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/jessedoyle/shrink_wrap/blob/master/CODE_OF_CONDUCT.md).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjessedoyle%2Fshrink_wrap","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjessedoyle%2Fshrink_wrap","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjessedoyle%2Fshrink_wrap/lists"}