{"id":15637172,"url":"https://github.com/pragtob/deep_merge","last_synced_at":"2025-10-07T04:40:07.929Z","repository":{"id":37821921,"uuid":"71502105","full_name":"PragTob/deep_merge","owner":"PragTob","description":"Deep (recursive) merge for maps, keywords and others in Elixir","archived":false,"fork":false,"pushed_at":"2023-12-10T10:24:11.000Z","size":88,"stargazers_count":114,"open_issues_count":1,"forks_count":5,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-09-08T01:55:59.155Z","etag":null,"topics":["deepmerge","elixir","keyword-lists","maps","merge"],"latest_commit_sha":null,"homepage":"","language":"Elixir","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/PragTob.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2016-10-20T20:33:42.000Z","updated_at":"2025-07-22T06:40:30.000Z","dependencies_parsed_at":"2024-06-18T20:15:13.015Z","dependency_job_id":null,"html_url":"https://github.com/PragTob/deep_merge","commit_stats":{"total_commits":86,"total_committers":2,"mean_commits":43.0,"dds":"0.023255813953488413","last_synced_commit":"3e3bb4e3c691f4b5a65dd74fa6525e0988d31464"},"previous_names":[],"tags_count":4,"template":false,"template_full_name":null,"purl":"pkg:github/PragTob/deep_merge","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/PragTob%2Fdeep_merge","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/PragTob%2Fdeep_merge/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/PragTob%2Fdeep_merge/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/PragTob%2Fdeep_merge/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/PragTob","download_url":"https://codeload.github.com/PragTob/deep_merge/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/PragTob%2Fdeep_merge/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":278722728,"owners_count":26034461,"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-07T02:00:06.786Z","response_time":59,"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":["deepmerge","elixir","keyword-lists","maps","merge"],"created_at":"2024-10-03T11:10:35.452Z","updated_at":"2025-10-07T04:40:07.905Z","avatar_url":"https://github.com/PragTob.png","language":"Elixir","funding_links":[],"categories":[],"sub_categories":[],"readme":"# DeepMerge [![Hex Version](https://img.shields.io/hexpm/v/deep_merge.svg)](https://hex.pm/packages/deep_merge) [![docs](https://img.shields.io/badge/docs-hexpm-blue.svg)](https://hexdocs.pm/deep_merge/) [![CI](https://github.com/PragTob/deep_merge/actions/workflows/main.yml/badge.svg)](https://github.com/PragTob/deep_merge/actions/workflows/main.yml) [![Coverage Status](https://coveralls.io/repos/github/PragTob/deep_merge/badge.svg?branch=main)](https://coveralls.io/github/PragTob/deep_merge?branch=main) [![Total Download](https://img.shields.io/hexpm/dt/deep_merge.svg)](https://hex.pm/packages/deep_merge) [![License](https://img.shields.io/hexpm/l/deep_merge.svg)](https://github.com/PragTob/deep_merge/blob/main/LICENSE)\n\nProvides functionality for \"deep merging\" maps and keyword lists in elixir, which is if during merging both values at the same key are maps/keyword lists merge them recursively. This is done via a protocol so can be extended for your own structs/data types if needbe.\n\n```\niex\u003e DeepMerge.deep_merge(%{a: 1, b: [x: 10, y: 9]}, %{b: [y: 20, z: 30], c: 4})\n%{a: 1, b: [x: 10, y: 20, z: 30], c: 4}\n```\n\nThis functionality can be useful for instance when merging a default configuration with a user supplied custom configuration:\n\n```elixir\nDeepMerge.deep_merge(default_config, custom_config) # =\u003e merged configuration\n```\n\nFurther features include:\n\n* It handles both maps and keyword lists\n* It does not merge structs or maps with structs…\n* …but you can implement the simple DeepMerge.Resolver protocol for types/structs of your choice to also make them deep mergable (there is also a default implementation)\n* a deep_merge/3 variant that gets a function similar to Map.merge/3 to modify the merging behavior, for instance in case you don't want keyword lists to be merged or you want all lists to be appended\n\nI wanted this to be a feature of Elixir itself, however the proposal [was rejected](https://github.com/elixir-lang/elixir/pull/5339) hence this library exists :)\n\n## Installation\n\nAdd `:deep_merge` to your list of dependencies in `mix.exs`:\n\n```elixir\ndef deps do\n  [\n    {:deep_merge, \"~\u003e 1.0\"}\n  ]\nend\n```\n\n## General Usage - deep_merge/2\n\nUsing this library is quite simple (and you might also want to look at the [hexdocs](https://hexdocs.pm/deep_merge/api-reference.html)) - just pass two structures to be deep merged into `DeepMerge.deep_merge/2`:\n\n```elixir\niex\u003e DeepMerge.deep_merge(%{a: 1, b: %{x: 10, y: 9}}, %{b: %{y: 20, z: 30}, c: 4})\n%{a: 1, b: %{x: 10, y: 20, z: 30}, c: 4}\n\niex\u003e DeepMerge.deep_merge([a: 1, b: [x: 10, y: 9]], [b: [y: 20, z: 30], c: 4])\n[a: 1, b: [x: 10, y: 20, z: 30], c: 4]\n```\n\nIt is worth noting that structs are not deeply merged - not with each other and not with normal maps. This is because structs, while internally a map, are more like their own data types and therefore should not be deeply merged... unless you implement the protocol provided by this library for them.\n\n## Customization via protocols\n\nWhat is merged and how is defined by implementing the `DeepMerge.Resolver` protocol. This library implements it for `Map`, `List` and falls back to `Any` (where the right hand side value/override is taken).\n\nIf you want your own struct to be deeply merged you can simply `@derive` the protocol:\n\n```elixir\ndefmodule Derived do\n  @derive [DeepMerge.Resolver]\n  defstruct [:attrs]\nend\n```\n\n\nIf you want to change the deep merge for a custom struct you can do so. An example implementation might look like this if you want to deeply merge your struct but only against non `nil` values (because all keys are always there) if you merge against the same struct (but still merge with maps):\n\n```elixir\ndefimpl DeepMerge.Resolver, for: MyStruct do\n  def resolve(original, override = %MyStruct{}, resolver) do\n    cleaned_override =\n      override\n      |\u003e Map.from_struct()\n      |\u003e Enum.reject(fn {_key, value} -\u003e is_nil(value) end)\n      |\u003e Map.new()\n\n    Map.merge(original, cleaned_override, resolver)\n  end\n\n  def resolve(original, override, resolver) when is_map(override) do\n    Map.merge(original, override, resolver)\n  end\nend\n```\n\nIn this implementation, `MyStruct` structs are merged with other `MyStruct` structs, omitting nil values. The arguments passed to `resolve` are the original value (left hand side) and the override value (right hand side, which would normally replace the original). The third parameter is a `resolver` function which you can pass to `Map.merge/3`/`Keyword.merge/3` to continue the deep merge.\n\n## Customization via deep_merge/3\n\nThere is another deep merge variant that is a bit like `Map.merge/3` as it takes an additional function which you can use to alter the deep merge behavior:\n\n```elixir\niex\u003e resolver = fn\n...\u003e (_, original, override) when is_list(original) and is_list(override) -\u003e\n...\u003e   override\n...\u003e (_, _original, _override) -\u003e\n...\u003e   DeepMerge.continue_deep_merge\n...\u003e end\niex\u003e DeepMerge.deep_merge(%{a: %{b: 1}, c: [d: 1]},\n...\u003e %{a: %{z: 5}, c: [x: 0]}, resolver)\n%{a: %{b: 1, z: 5}, c: [x: 0]}\n```\n\nThis function is called for a given merge conflict with the key where it occurred and the two conflicting values. Whatever value is returned in this function is inserted at that point in the structure - unless `DeepMerge.continue_deep_merge` is returned in which case the deep merge continues as normal.\n\nWhen would you want to use this versus a protocol? The best use case I can think of is when you want to alter behavior for which a protocol is already implemented or if you care about specific keys.\n\nIn the example above the behavior is changed so that keyword lists are not deep_merged (if they were the result would contain `c: [d: 1, x:0]`), but maps still are if that's what you are looking for.\n\n## Do I really need a library for this?\n\nWell not necessarily, no. There are [very simple implementations for maps that use Map.merge/3](http://stackoverflow.com/a/38865647).\n\nThere are subtle things that can be missed there though (and I missed the first time around):\n\n* the most simple implementation also merges structs which is not always what you want\n* for keyword lists on the other hand you gotta be careful that you don't accidentally merge keyword lists with lists as that's [currently possible](https://github.com/elixir-lang/elixir/issues/5395)\n* you might want to further adopt the implementation, in [benchee](https://github.com/bencheeorg/benchee) we have 2 custom implementations of the protocol due to our needs\n\nThis library takes care of those problems and will take care of further problems/edge cases should they appear so you can focus on your business logic.\n\nAt the same time it offers extension mechanisms through protocols and a function in `deep_merge/3`. So, it should be adjustable to your use case and if not please open an issue :)\n\n## Performance\n\nYou can check out [a benchmark and its results](https://github.com/PragTob/deep_merge/blob/main/benches/bench/deep_merge.exs).\n\nThe TLDR; is this: In the sample it is about 30 times slower than `Map.merge/2` - however, less than twice as slow as calling `Map.merge/3` with simple overriding behaviour (same behaviour as `Map.merge/2`). This is because `Map.merge/2` is highly optimized, but we need to do much more than the `Map.merge/3` sample in the benchmark so I think it's a very passable result. We're still talking about a couple of μs.\n\n## Considered feature-complete\n\nUnless you come with great feature ideas of course ;) So if you come here and there are no recent commits, don't worry - there are no known bugs or whatever. It's a small little library that does its job.\n\n## Copyright and License\n\nCopyright (c) 2016 Tobias Pfeiffer\n\nThis library is MIT licensed. See the\n[LICENSE](https://github.com/PragTob/deep_merge/blob/main/LICENSE.txt) for details.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpragtob%2Fdeep_merge","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpragtob%2Fdeep_merge","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpragtob%2Fdeep_merge/lists"}