{"id":32187573,"url":"https://github.com/lemonteaa/relabel","last_synced_at":"2025-10-22T00:10:12.049Z","repository":{"id":57716732,"uuid":"60103384","full_name":"lemonteaa/relabel","owner":"lemonteaa","description":"An (almost) trivial declarative domain converter","archived":false,"fork":false,"pushed_at":"2017-07-17T15:59:28.000Z","size":35,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-10-22T00:06:50.082Z","etag":null,"topics":["clojure","converter","mapper"],"latest_commit_sha":null,"homepage":null,"language":"Clojure","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"epl-1.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/lemonteaa.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2016-05-31T15:54:23.000Z","updated_at":"2017-07-17T14:36:42.000Z","dependencies_parsed_at":"2022-08-24T01:50:36.225Z","dependency_job_id":null,"html_url":"https://github.com/lemonteaa/relabel","commit_stats":null,"previous_names":[],"tags_count":4,"template":false,"template_full_name":null,"purl":"pkg:github/lemonteaa/relabel","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lemonteaa%2Frelabel","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lemonteaa%2Frelabel/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lemonteaa%2Frelabel/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lemonteaa%2Frelabel/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/lemonteaa","download_url":"https://codeload.github.com/lemonteaa/relabel/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lemonteaa%2Frelabel/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":280354908,"owners_count":26316566,"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","status":"online","status_checked_at":"2025-10-21T02:00:06.614Z","response_time":58,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"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":["clojure","converter","mapper"],"created_at":"2025-10-22T00:06:27.505Z","updated_at":"2025-10-22T00:10:12.041Z","avatar_url":"https://github.com/lemonteaa.png","language":"Clojure","funding_links":[],"categories":[],"sub_categories":[],"readme":"# relabel\n\n[![Build Status](https://travis-ci.org/lemonteaa/relabel.svg?branch=master)](https://travis-ci.org/lemonteaa/relabel)\n\nAn (almost) trivial declarative domain converter based on the idea of doing destructuring in reverse.\n\n## Note\n\nAs the problem this library is trying to solve (clean declarative domain conversion) appear difficult when one begin to consider all sorts of edge case (or just not-so-basic usage), one namespace is allocated for each solution approach.\n\n## Latest Version\n\n![](https://clojars.org/lemonteaa/relabel/latest-version.svg)\n\n## Usage\n\n```clojure\n;; In ns:\n(ns my-project.core\n  (:require [relabel.lazy :refer :all]))\n```\n\nBasic mapping:\n\n```clojure\n(def college-to-fb (converter { :username (from :name)\n                                :age (from :age)\n                                :school (from :college) }))\n\n(college-to-fb { :name \"Peter\" :age 24 :college \"test\" })\n;; =\u003e { :username \"Peter\" :age 24 :school \"test\" }\n```\n\nSchema can be nested:\n\n```clojure\n(def addressbook (converter { :name (from :username)\n                              :address { :country (from :loc-country)\n                                         :region (from :loc-region)\n                                         :location (from :loc-rest) }}))\n\n(addressbook { :username \"Brown\"\n               :loc-country \"Switzerland\" :loc-region \"Aargau\" :loc-rest \"test 1234\" })\n;; =\u003e { :name \"Brown\"\n;;      :address { :country \"Switzerland\"\n;;                 :region \"Aargau\"\n;;                 :location \"test 1234\" }}\n```\n\nThe modifiers serves as a form of DSL. For now we focus on providing their functional form (functions that either produce a function or transform another function) as they are more composable in this way. Macro could eventually be added once a good interface has solidified.\n\nThe `from` modifier extract values by key:\n\n```clojure\n((from :tags) { :title \"Hello world!\" :tags [\"test\" \"first post\"] })\n;; =\u003e [\"test\" \"first post\"]\n\n((from :tags) { :title \"Nothing\" :tags nil })\n;; =\u003e nil\n\n((from :tags) { :title \"Another post\" })\n;; =\u003e raise ExceptionInfo\n```\n\nTo extract values nested in the source object, `from` also supports extraction through a [Specter](https://github.com/nathanmarz/specter) path:\n\n```clojure\n(require '[com.rpl.specter :as spct])\n\n(let [data { :reqId 123\n             :req { :type :buy-scissor\n                    :remarks [{ :msg \"Hello world!\" :by \"Chris\" }\n                              { :msg \"Me too\" :by \"Katie\" }\n                              { :msg \"We've got too many paper here, would be nice if we can cut 'em all ;)\" :by \"Carol\" }\n                              { :msg \"So that we can throw a party?\" :by \"Cindy\" }]\n                    :by \"Tom\" }\n             :state :pending }]\n  ((from [:req :remarks spct/LAST :msg]) data))\n;; =\u003e \"So that we can throw a party?\"\n```\n\n`from` accept an optional parameter `:then`, which is a function to apply to after extracting value:\n\n```clojure\n((from :tags :then #(clojure.string/join \", \" %)) { :title \"TDD in action\" :tags [\"TDD\" \"Best practice\" \"Experience Sharing\"] })\n;; =\u003e \"TDD, Best practice, Experience Sharing\"\n```\n\nIt also accept an optional parameter `:default`, which can be used to avoid Exception if a matching value is not found, by using the value of the parameter instead:\n\n```clojure\n((from :tags :default \"#blogging\") { :title \"Another post\" })\n;; =\u003e \"#blogging\"\n```\n\nException can also be avoided by using loose mode, see [Configs](#configs) section for details.\n\nThe `literal` modifier allows you to set constant/fixed field:\n\n```clojure\n(def test-convert (converter { :user-id (from :user-key)\n                               :version (literal \"2.3\") }))\n\n(test-convert { :user-key 4 })\n;; =\u003e { :user-id 4 :version \"2.3\" }\n(test-convert { :user-key 5 :version \"1.2\" })\n;; =\u003e { :user-id 5 :version \"2.3\" }\n```\n\nThe `one-or-more` modifier change a function so that it can deal with single object and a sequence in the same way:\n\n```clojure\n(def test-convert (converter { :foo (from :bar) }))\n\n((one-or-more test-convert) {:bar 3})\n;; =\u003e {:foo 3}\n((one-or-more test-convert) [{:bar 3} {:bar 42}])\n;; =\u003e [{:foo 3} {:foo 42}]\n```\n\nSee unit tests for these examples.\n\n## Configs\n\nThe library has a global config stored in the dynamic variable `*config*`, which is a map of configuration options vs value. One can either change them \"permanantly\" through the standard function `alter-var-root`, or apply change to a range of converters using the scoped version `binding`.\n\nCurrently there are two config options:\n\n`:automap-seq` determines whether post-processing is applied flexibly to values extracted from source object's field in the `from` modifier. If true, then the function specified in the `:then` optional parameter will be changed with the `one-or-more` modifier before applying to extracted value.\n\n```clojure\n;; Suppose *config* is { :automap-seq false }\n\n((from :param-vals :then #(Integer/parseInt %)) { :param-vals [\"25\" \"4\" \"56\"] })\n;; =\u003e raise Exception\n(binding [*config* { :automap-seq true }]\n  ((from :param-vals :then #(Integer/parseInt %)) { :param-vals [\"25\" \"4\" \"56\"] })\n  ;; =\u003e [25 4 56]\n  ((from :param-vals :then #(Integer/parseInt %)) { :param-vals \"116\" })\n  ;; =\u003e 116\n)\n```\n\nTo allow more fine-grained control, the config can also be specified in the `from` modifier itself through the optional parameter `:automap?`. As it is more specific, it will override the global config if present.\n\n```clojure\n;; Suppose *config* is { :automap-seq false }\n\n((from :param-vals :then #(Integer/parseInt %) :automap? true)\n  { :param-vals [\"25\" \"4\" \"56\"] })\n;; =\u003e [25 4 56]\n```\n\n`:strict` controls the mode for the `from` modifier. It is in strict mode if `:strict` is true, and loose mode if false. In strict mode, `from` will raise an Exception if a value cannot be extracted from the object:\n\n+ When selecting by keywords, this happens if the object has no key named by the keyword.\n+ When selecting by a Specter path, this happens if there is no matching value or more than one match.\n\nIn loose mode, a default value of `nil` will be returned if there is no match. This default value can be overriden by the `:default` optional parameter. Note that if this parameter is present, loose mode will be used for that call of `from` even if we are in strict mode otherwise.\n\n## TODO\n\n- ~~Support something like XPath for modifier from (use Specter?)~~\n- ~~Support nested map for schema declaration in converter~~\n- ~~Control on strict/loose setting when key cannot be mapped~~\n\n## Contact\n\nThis project is currently developed and maintained by @lemonteaa, which can be reached through email listed on the [github account](https://github.com/lemonteaa).\n\n## Contributing\n\nContributors welcome. Just report an issue or submit a pull request.\n\n## License\n\nDistributed under the Eclipse Public License either version 1.0 or (at\nyour option) any later version.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flemonteaa%2Frelabel","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flemonteaa%2Frelabel","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flemonteaa%2Frelabel/lists"}