{"id":21201656,"url":"https://github.com/oleander/remap","last_synced_at":"2025-07-10T06:31:47.158Z","repository":{"id":45393100,"uuid":"419809574","full_name":"oleander/remap","owner":"oleander","description":"Data mapping made easy","archived":false,"fork":false,"pushed_at":"2024-11-16T06:48:56.000Z","size":1179,"stargazers_count":4,"open_issues_count":9,"forks_count":2,"subscribers_count":2,"default_branch":"development","last_synced_at":"2024-11-16T07:27:49.634Z","etag":null,"topics":["immutable","mapping","ruby","transformation"],"latest_commit_sha":null,"homepage":"http://oleander.io/remap","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/oleander.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2021-10-21T17:04:55.000Z","updated_at":"2024-11-16T06:48:59.000Z","dependencies_parsed_at":"2023-01-29T03:01:16.820Z","dependency_job_id":null,"html_url":"https://github.com/oleander/remap","commit_stats":null,"previous_names":[],"tags_count":86,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/oleander%2Fremap","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/oleander%2Fremap/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/oleander%2Fremap/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/oleander%2Fremap/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/oleander","download_url":"https://codeload.github.com/oleander/remap/tar.gz/refs/heads/development","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":225622846,"owners_count":17498168,"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":["immutable","mapping","ruby","transformation"],"created_at":"2024-11-20T20:10:22.453Z","updated_at":"2024-11-20T20:10:22.999Z","avatar_url":"https://github.com/oleander.png","language":"Ruby","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Re:map [![remap](https://github.com/oleander/remap/actions/workflows/main.yml/badge.svg?branch=development)](https://github.com/oleander/remap/actions/workflows/main.yml) [![Gem](https://img.shields.io/gem/v/remap)](https://rubygems.org/gems/remap) [![Maintainability](https://api.codeclimate.com/v1/badges/0c09721ad5a3b646a6d3/maintainability)](https://codeclimate.com/github/oleander/remap/maintainability) [![Test Coverage](https://api.codeclimate.com/v1/badges/0c09721ad5a3b646a6d3/test_coverage)](https://codeclimate.com/github/oleander/remap/test_coverage)\n\n`Re:map`; an expressive and feature-rich data transformer written in Ruby 3.\nIt gives the developer the expressive power of JSONPath, without the hassle of using strings. Its compiler is written on top of an immutable, primitive data structure utilizing ruby's refinements \u0026 pattern matching capabilities – making it blazingly fast\n\n- Overview\n  - [API Documentation](http://oleander.io/remap)\n  - [Quick start](#quick-start)\n  - [Installation](#installation)\n  - [Introduction](#introduction)\n    - [Selectors](#selectors)\n    - [Callbacks](#callbacks)\n    - [Fixed \u0026 semi-fixed values](#fixed--semi-fixed-values)\n    - [Type casting](#type-casting)\n    - [Operators](#operators)\n    - [Error handling](#error-handling)\n    - [Constructors](#constructors)\n    - [Schemas \u0026 rules](#schemas--rules)\n\n## Quick start\n\n``` ruby\nrequire \"remap\"\n\nclass Mapper \u003c Remap::Base\n  option :date # \u003c= Custom required value\n\n  define do\n    # Fixed values\n    set :description, to: value(\"This is a description\")\n\n    # Semi-dynamic values\n    set :date, to: option(:date)\n\n    # Required rules\n    get :friends do\n      each do\n        # Post processors\n        map(:name, to: :id).then do |value:|\n          \"#{value.upcase}!\"\n        end\n\n        # Field conditions\n        get?(:age).if do |age|\n          (30..50).cover?(age)\n        end\n\n        # Map to a finite set of values\n        get :phones do\n          each do\n            map.enum do\n              from \"iPhone\", to: \"iOS\"\n              value \"iOS\", \"Android\"\n\n              otherwise \"Unknown\"\n            end\n          end\n        end\n      end\n    end\n\n    # Composable mappers\n    class Linux \u003c Remap::Base\n      define do\n        get :kernel\n      end\n    end\n\n    class Windows \u003c Remap::Base\n      define do\n        get :price\n      end\n    end\n\n    # Embed mappers\n    to :os do\n      map :computer, :operating_system do\n        embed Linux | Windows\n      end\n    end\n\n    # Wrapping values in arrays\n    to :houses do\n      wrap :array do\n        map :house\n      end\n    end\n\n    # Nested paths ($.cars[*].model)\n    map :cars, all, :model, to: :cars\n\n    # Or using the #each iterator\n    map :cars do\n      each do\n        map :model, to: :cars\n      end\n    end\n  end\nend\n```\n\nInput hash to be mapped\n\n``` ruby\ninput = {\n  house: \"100kvm\",\n  friends: [\n    {\n      name: \"Lisa\",\n      age: 20,\n      phones: [\"iPhone\"]\n    }, {\n      name: \"Jane\",\n      age: 40,\n      phones: [\"Samsung\"]\n    }\n  ],\n  computer: {\n    operating_system: {\n      kernel: :latest\n    }\n  },\n  cars: [\n    {\n      owners: [\n        {\n          name: \"John\"\n        }\n      ]\n    }\n  ]\n}\n```\n\nThe expected mapped output\n\n```ruby\noutput = {\n  friends: [\n    {\n      id: \"LISA!\",\n      phones: [\"iOS\"]\n    }, {\n      age: 40,\n      id: \"JANE!\",\n      phones: [\"Unknown\"]\n    }\n  ],\n  description: \"This is a description\",\n  cars: [{ owners: [\"John\"] }],\n  houses: [\"100kvm\"],\n  date: Date.today,\n  os: {\n    kernel: :latest\n  }\n}\n```\n\nInvoking the mapper with input and the `date` option\n\n``` ruby\nMapper.call(input, date: Date.today) # =\u003e output\n```\n\n## Installation\n\nAdd `remap` to your `Gemfile`\n\n``` ruby\n# Use Github as source\nsource \"https://rubygems.pkg.github.com/oleander\" do\n  gem \"remap\"\nend\n\n# Or Rubygems\ngem \"remap\"\n```\n\nThen run `bundle install`\n\n## Introduction\n\nTo create a mapper, inherit from `Remap::Base` and define your rules using `define`.\n\n``` ruby\nclass Mapper \u003c Remap::Base\n  define do\n    # rules goes here\n  end\nend\n\nMapper.call(input)\n```\n\nOr use `define` method directly on the Remap module\n\n``` ruby\nmapper = Remap.define do\n  # rules goes here\nend\n\nmapper.call(input)\n```\n\nThe easiest way to get started is using `map`.\n`map` transform a value from one path to another.\n\n``` ruby\nclass Mapper \u003c Remap::Base\n  define do\n    map :name, to: :nickname\n  end\nend\n```\n\nTo invoke the mapper, call `Mapper.call` with any input, i.e\n\n``` ruby\nMapper.call({ name: \"John\" }) # =\u003e { nickname: \"John\" }\n```\n\nIf the input data doesn't match the defined rule, an exception\nwill be thrown explaining what went wrong and where.\nTo prevent this, you can pass a block to `.call`.\nThe mapper will yield failures to the block instead of raising an error.\n\n``` ruby\nMapper.call({ something: \"value\" }) do |failure|\n  # ...\nend\n```\n\nUse `map?`, `to?` and `get?` to map partial data structures.\n\n``` ruby\nclass Mapper \u003c Remap::Base\n  define do\n    map? :key1\n    map? :key2\n  end\nend\n```\n\nIf one of the two rules succeeds, the mapper returns a value.\n\n``` ruby\nMapper.call({ key1: \"value1\" }) # =\"value1\"\nMapper.call({ key2: \"value2\" }) # =\"value2\"\n```\n\nIf none of the rules succeeds, the mapper invokes the error block.\n\n``` ruby\nMapper.call({ nope: \"value\" }) do |failure|\n  # ...\nend\n```\n\nRules can be expressed in a variety of ways to best fit the\nproblem at hand.\n\nThe following rules yields the same output\n\n``` ruby\n# Flat map\nmap :person, :name, to: :first_name\n\n# Flat to\nto :first_name, map: [:person, :name]\n\n# Nested map\nmap :person do\n  map :name do\n    to :first_name\n  end\nend\n\n# Nested to\nto :first_name do\n  map :person do\n    map :name\n  end\nend\n```\n\nTo select a value *and* its path, use `get`, or `get?`.\n\n``` ruby\nclass Mapper \u003c Remap::Base\n  define do\n    get :person\n  end\nend\n\nMapper.call({ person: \"John\" }) # =\u003e { person: \"John\" }\n```\n\nUse `each` when iterating over arrays and hashes.\n\n``` ruby\nclass Mapper \u003c Remap::Base\n  define do\n    map :people do\n      each do\n        map :name\n      end\n    end\n  end\nend\n\nMapper.call({ people: [{ name: \"John\" }, { name: \"Jane\" }] }) # =\u003e [\"John\", \"Jane\"]\n```\n\n### Selectors\n\nUse the `all` selector as part of the path instead of `each`.\n\n\u003e `all` is similar to JSONPath’s `[*]` selector\n\n``` ruby\nclass Mapper \u003c Remap::Base\n  define do\n    map :people, all, :name\n  end\nend\n```\n\n`first` selects the first element in an array and `last` the last element.\n\n\u003e `first` \u0026 `last` is similar to JSONPath’s `[0]` `[-1]` selectors\n\n``` ruby\nclass Mapper \u003c Remap::Base\n  define do\n    map :people do\n      map first, :name, to: :name\n    end\n  end\nend\n\nMapper.call({ people: [{ name: \"John\" }] }) # =\u003e { name: \"John\" }\n```\n\n### Callbacks\n\nSelected values can easily be processed before being returned using call-backs.\n\n\u003e See `Remap::Rule::Map` for more information\n\n``` ruby\nclass Mapper \u003c Remap::Base\n  using Remap::Extensions::Hash\n\n  define do\n    map :people, all do\n      # Pass a proc\n      map(:name).then(\u0026:upcase)\n\n      # Or pass a block\n      map(:name).then do |value:|\n        value.upcase\n      end\n\n      # Manually skip a mapping\n      map(:name).then do |\u0026error|\n        error[\"skip\"]\n      end\n\n      # Add conditions\n      map?(:name).if do |value:|\n        value.include?(\"John\")\n      end\n\n      map?(:name).if_not do |value:|\n        value.include?(\"Lisa\")\n      end\n\n      # Pending mappings\n      map(:name).pending(\"I'll do this later\")\n\n      # Define rules for a finite set of values\n      map(:name).enum do\n        from \"John\", to: \"Joe\"\n        value \"Lisa\", \"Jane\"\n        otherwise \"Unknown\"\n      end\n\n      # Get is defined by the Remap::Extensions::Hash refinement\n      # and allows for a path to be passed. If the path is missing,\n      # the rule will be ignored in the case of `map?` and `map`\n      # a detailed error message will be thrown with a detailed path\n      map(:name).then do |value:|\n        value.get(:a, :b)\n      end\n    end\n  end\nend\n```\n\nThe callback context has access to the following values\n\n* `value` current value\n* `element` - defined by `each`\n* `index` defined by `each`\n* `key` defined by `to`, `map` and `each` on hashes\n* `values` \u0026 `input` yields the mapper input\n* `mapper` the current mapper\n\n``` ruby\nclass Person \u003c Remap::Base\n  using Remap::Extensions::Enumerable\n\n  define do\n    get :person do\n      get(:name)\n      get?(:age).if do |values:|\n        values.get(:person, :name) == \"John\"\n      end\n    end\n  end\nend\n```\n\n\u003e See `Remap::State::Extension#execute` for more details\n\n### Fixed \u0026 semi-fixed values\n\nA mapper can require options using the `option` method.\nAn option can be referenced from within callbacks and via `set`.\n\n``` ruby\nclass Mapper \u003c Remap::Base\n  option :code\n\n  define do\n    set :secret, to: option(:code)\n\n    # Access {code} inside a callback\n    map(:pin_code, to: :seed).then do |pin, code:|\n      code**pin\n    end\n  end\nend\n```\n\nThe second argument to `Mapper.call` takes a hash and is used as options for the mapper.\n\n``` ruby\nMapper.call({\n  pin_code: 1234\n}, code: 5678) # =\u003e { secret: 5678, seed: 3.2*10^10 }\n```\n\n`set` can also take a fixed value using the `value` method\n\n``` ruby\nclass Mapper \u003c Remap::Base\n  define do\n    set :api_key, to: value(\"ABC-123\")\n  end\nend\n\nMapper.call(input) # =\u003e { api_key: \"ABC-123\" }\n```\n\n### Type casting\n\n`wrap` allows output values to be type casts into an array.\n\n``` ruby\nclass Mapper \u003c Remap::Base\n  define do\n    to :names do\n      wrap(:array) do\n        map :name\n      end\n    end\n  end\nend\n\nMapper.call({ name: \"John\" }) # ={ names: [\"John\"] }\n```\n\n### Operators\n\nMappers can be composed using the `|` (or), `\u0026` (and) and `^` (xor) operators.\nComposed mappers can then be embedded into other mappers using `embed`.\n\n``` ruby\nclass Bicycle \u003c Remap::Base\n  contract do\n    required(:gears)\n    required(:brand)\n  end\n\n  define do\n    to :bicycle\n  end\nend\n\nclass Car \u003c Remap::Base\n  contract do\n    required(:hybrid)\n    required(:fuel)\n  end\n\n  define do\n    to :car\n  end\nend\n\nclass Vehicle \u003c Remap::Base\n  define do\n    each do\n      embed Bicycle | Car\n    end\n  end\nend\n\nVehicle.call([\n  {\n    gears: 3,\n    brand: \"Rose\"\n  }, {\n    hybrid: false,\n    fuel: \"Petrol\"\n  }\n]) # =\u003e [{ bicycle: { gears: 3, brand: \"Rose\" } }, { car: { hybrid: false, fuel: \"Petrol\" } }]\n```\n\n### Error handling\n\nTODO\n\n### Constructors\n\nTODO\n\n### Schemas \u0026 rules\n\nTODO\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Foleander%2Fremap","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Foleander%2Fremap","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Foleander%2Fremap/lists"}