├── .deepsource.toml ├── .github ├── FUNDING.yml ├── dependabot.yml └── workflows │ ├── codeql-analysis.yml │ ├── linters.yml │ ├── scorecard.yml │ └── tests.yml ├── .gitignore ├── .pre-commit-config.yaml ├── .travis.yml ├── .vscode └── settings.json ├── .well-known └── funding-manifest-urls ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── FUNDING.json ├── LICENSE ├── README.md ├── SECURITY.md ├── benchmark_test.go ├── doc.go ├── go.mod ├── go.sum ├── internal └── ng │ ├── README.md │ ├── merge.go │ └── merge_test.go ├── issue100_test.go ├── issue104_test.go ├── issue121_test.go ├── issue123_test.go ├── issue125_test.go ├── issue129_test.go ├── issue131_test.go ├── issue136_test.go ├── issue138_test.go ├── issue143_test.go ├── issue149_test.go ├── issue174_test.go ├── issue17_test.go ├── issue187_test.go ├── issue202_test.go ├── issue209_test.go ├── issue220_test.go ├── issue230_test.go ├── issue23_test.go ├── issue33_test.go ├── issue38_test.go ├── issue50_test.go ├── issue52_test.go ├── issue61_test.go ├── issue64_test.go ├── issue66_test.go ├── issue83_test.go ├── issue84_test.go ├── issue89_test.go ├── issue90_test.go ├── issueXXX_test.go ├── map.go ├── merge.go ├── merge_test.go ├── mergo.go ├── mergo_test.go ├── pr211_2_test.go ├── pr211_test.go ├── pr80_test.go ├── pr81_test.go ├── testdata ├── license.json └── thing.json └── v039_bugs_test.go /.deepsource.toml: -------------------------------------------------------------------------------- 1 | version = 1 2 | 3 | test_patterns = [ 4 | "*_test.go" 5 | ] 6 | 7 | [[analyzers]] 8 | name = "go" 9 | enabled = true 10 | 11 | [analyzers.meta] 12 | import_path = "dario.cat/mergo" -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: darccio 4 | tidelift: go/dario.cat/mergo 5 | thanks_dev: gh/darccio 6 | liberapay: dario 7 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: gomod 4 | directory: / 5 | schedule: 6 | interval: weekly 7 | - package-ecosystem: github-actions 8 | directory: / 9 | schedule: 10 | interval: daily 11 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | name: "CodeQL" 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | # The branches below must be a subset of the branches above 8 | branches: [ master ] 9 | schedule: 10 | - cron: '43 19 * * 2' 11 | 12 | permissions: 13 | contents: read 14 | 15 | jobs: 16 | analyze: 17 | name: Analyze 18 | runs-on: ubuntu-latest 19 | permissions: 20 | actions: read 21 | contents: read 22 | security-events: write 23 | 24 | strategy: 25 | fail-fast: false 26 | matrix: 27 | language: [ 'go' ] 28 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] 29 | # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support 30 | 31 | steps: 32 | - name: Checkout repository 33 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 34 | 35 | # Initializes the CodeQL tools for scanning. 36 | - name: Initialize CodeQL 37 | uses: github/codeql-action/init@fca7ace96b7d713c7035871441bd52efbe39e27e # v3.28.19 38 | with: 39 | languages: ${{ matrix.language }} 40 | # If you wish to specify custom queries, you can do so here or in a config file. 41 | # By default, queries listed here will override any specified in a config file. 42 | # Prefix the list here with "+" to use these queries and those in the config file. 43 | 44 | # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs 45 | # queries: security-extended,security-and-quality 46 | 47 | 48 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 49 | # If this step fails, then you should remove it and run the build manually (see below) 50 | - name: Autobuild 51 | uses: github/codeql-action/autobuild@fca7ace96b7d713c7035871441bd52efbe39e27e # v3.28.19 52 | 53 | # ℹ️ Command-line programs to run using the OS shell. 54 | # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun 55 | 56 | # If the Autobuild fails above, remove it and uncomment the following three lines. 57 | # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. 58 | 59 | # - run: | 60 | # echo "Run, Build Application using script" 61 | # ./location_of_script_within_repo/buildscript.sh 62 | 63 | - name: Perform CodeQL Analysis 64 | uses: github/codeql-action/analyze@fca7ace96b7d713c7035871441bd52efbe39e27e # v3.28.19 65 | -------------------------------------------------------------------------------- /.github/workflows/linters.yml: -------------------------------------------------------------------------------- 1 | name: 'linters' 2 | on: 3 | push: 4 | branches: 5 | - master 6 | pull_request: 7 | workflow_dispatch: 8 | permissions: 9 | contents: read 10 | jobs: 11 | golangci-lint: 12 | runs-on: ubuntu-latest 13 | permissions: 14 | contents: read # for actions/checkout to fetch code 15 | pull-requests: read # for golangci/golangci-lint-action to fetch pull requests 16 | steps: 17 | - name: Checkout code 18 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 19 | with: 20 | persist-credentials: false 21 | - name: Setup Go 22 | uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 23 | with: 24 | go-version: 'stable' 25 | - name: golangci-lint 26 | uses: golangci/golangci-lint-action@4afd733a84b1f43292c63897423277bb7f4313a9 # v8.0.0 27 | with: 28 | version: v2.1 29 | -------------------------------------------------------------------------------- /.github/workflows/scorecard.yml: -------------------------------------------------------------------------------- 1 | # This workflow uses actions that are not certified by GitHub. They are provided 2 | # by a third-party and are governed by separate terms of service, privacy 3 | # policy, and support documentation. 4 | 5 | name: Scorecard supply-chain security 6 | on: 7 | # For Branch-Protection check. Only the default branch is supported. See 8 | # https://github.com/ossf/scorecard/blob/main/docs/checks.md#branch-protection 9 | branch_protection_rule: 10 | # To guarantee Maintained check is occasionally updated. See 11 | # https://github.com/ossf/scorecard/blob/main/docs/checks.md#maintained 12 | schedule: 13 | - cron: '24 16 * * 6' 14 | push: 15 | branches: [ "master" ] 16 | workflow_dispatch: 17 | 18 | # Declare default permissions as read only. 19 | permissions: read-all 20 | 21 | jobs: 22 | analysis: 23 | name: Scorecard analysis 24 | runs-on: ubuntu-latest 25 | # `publish_results: true` only works when run from the default branch. conditional can be removed if disabled. 26 | if: github.event.repository.default_branch == github.ref_name || github.event_name == 'pull_request' 27 | permissions: 28 | # Needed to upload the results to code-scanning dashboard. 29 | security-events: write 30 | # Needed to publish results and get a badge (see publish_results below). 31 | id-token: write 32 | # Uncomment the permissions below if installing in a private repository. 33 | # contents: read 34 | # actions: read 35 | 36 | steps: 37 | - name: "Checkout code" 38 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 39 | with: 40 | persist-credentials: false 41 | 42 | - name: "Run analysis" 43 | uses: ossf/scorecard-action@05b42c624433fc40578a4040d5cf5e36ddca8cde # v2.4.2 44 | with: 45 | results_file: results.sarif 46 | results_format: sarif 47 | # (Optional) "write" PAT token. Uncomment the `repo_token` line below if: 48 | # - you want to enable the Branch-Protection check on a *public* repository, or 49 | # - you are installing Scorecard on a *private* repository 50 | # To create the PAT, follow the steps in https://github.com/ossf/scorecard-action?tab=readme-ov-file#authentication-with-fine-grained-pat-optional. 51 | # repo_token: ${{ secrets.SCORECARD_TOKEN }} 52 | 53 | # Public repositories: 54 | # - Publish results to OpenSSF REST API for easy access by consumers 55 | # - Allows the repository to include the Scorecard badge. 56 | # - See https://github.com/ossf/scorecard-action#publishing-results. 57 | # For private repositories: 58 | # - `publish_results` will always be set to `false`, regardless 59 | # of the value entered here. 60 | publish_results: true 61 | 62 | # (Optional) Uncomment file_mode if you have a .gitattributes with files marked export-ignore 63 | # file_mode: git 64 | 65 | # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF 66 | # format to the repository Actions tab. 67 | - name: "Upload artifact" 68 | uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 69 | with: 70 | name: SARIF file 71 | path: results.sarif 72 | retention-days: 5 73 | 74 | # Upload the results to GitHub's code scanning dashboard (optional). 75 | # Commenting out will disable upload of results to your repo's Code Scanning dashboard 76 | - name: "Upload to code-scanning" 77 | uses: github/codeql-action/upload-sarif@fca7ace96b7d713c7035871441bd52efbe39e27e # v3.28.19 78 | with: 79 | sarif_file: results.sarif 80 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: 'tests' 2 | on: 3 | push: 4 | branches: 5 | - master 6 | pull_request: 7 | workflow_dispatch: 8 | permissions: 9 | contents: read 10 | jobs: 11 | build: 12 | runs-on: ubuntu-latest 13 | permissions: 14 | checks: write # for coverallsapp/github-action to create new checks 15 | contents: read # for actions/checkout to fetch code 16 | steps: 17 | - name: Checkout code 18 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 19 | with: 20 | persist-credentials: false 21 | - name: Setup Go 22 | uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 23 | with: 24 | go-version: 1.23.9 25 | - name: Build 26 | run: go build -v ./... 27 | - name: Test 28 | run: go test -v -race -covermode atomic -coverprofile=covprofile ./... 29 | - name: Coverage 30 | uses: coverallsapp/github-action@648a8eb78e6d50909eff900e4ec85cab4524a45b # v2.3.6 31 | with: 32 | file: covprofile 33 | format: golang 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | #### joe made this: http://goel.io/joe 2 | 3 | #### go #### 4 | # Binaries for programs and plugins 5 | *.exe 6 | *.dll 7 | *.so 8 | *.dylib 9 | 10 | # Test binary, build with `go test -c` 11 | *.test 12 | 13 | # Output of the go coverage tool, specifically when used with LiteIDE 14 | *.out 15 | 16 | # Golang/Intellij 17 | .idea 18 | 19 | # Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736 20 | .glide/ 21 | 22 | #### vim #### 23 | # Swap 24 | [._]*.s[a-v][a-z] 25 | [._]*.sw[a-p] 26 | [._]s[a-v][a-z] 27 | [._]sw[a-p] 28 | 29 | # Session 30 | Session.vim 31 | 32 | # Temporary 33 | .netrwhist 34 | *~ 35 | # Auto-generated tag files 36 | tags 37 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/gitleaks/gitleaks 3 | rev: v8.16.3 4 | hooks: 5 | - id: gitleaks 6 | - repo: https://github.com/golangci/golangci-lint 7 | rev: v1.52.2 8 | hooks: 9 | - id: golangci-lint 10 | - repo: https://github.com/pre-commit/pre-commit-hooks 11 | rev: v4.4.0 12 | hooks: 13 | - id: end-of-file-fixer 14 | - id: trailing-whitespace 15 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | arch: 3 | - amd64 4 | - ppc64le 5 | install: 6 | - go get -t 7 | - go get golang.org/x/tools/cmd/cover 8 | - go get github.com/mattn/goveralls 9 | script: 10 | - go test -race -v ./... 11 | after_script: 12 | - $HOME/gopath/bin/goveralls -service=travis-ci -repotoken $COVERALLS_TOKEN 13 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "go.lintTool": "golangci-lint", 3 | "go.lintFlags": [ 4 | "--enable-all", 5 | "--disable=gomnd" 6 | ] 7 | } -------------------------------------------------------------------------------- /.well-known/funding-manifest-urls: -------------------------------------------------------------------------------- 1 | https://dario.cat/funding.json -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | ## Our Standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | * Using welcoming and inclusive language 12 | * Being respectful of differing viewpoints and experiences 13 | * Gracefully accepting constructive criticism 14 | * Focusing on what is best for the community 15 | * Showing empathy towards other community members 16 | 17 | Examples of unacceptable behavior by participants include: 18 | 19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances 20 | * Trolling, insulting/derogatory comments, and personal or political attacks 21 | * Public or private harassment 22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission 23 | * Other conduct which could reasonably be considered inappropriate in a professional setting 24 | 25 | ## Our Responsibilities 26 | 27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 28 | 29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 30 | 31 | ## Scope 32 | 33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 34 | 35 | ## Enforcement 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at i@dario.im. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 38 | 39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. 40 | 41 | ## Attribution 42 | 43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] 44 | 45 | [homepage]: http://contributor-covenant.org 46 | [version]: http://contributor-covenant.org/version/1/4/ 47 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | <!-- omit in toc --> 2 | # Contributing to mergo 3 | 4 | First off, thanks for taking the time to contribute! ❤️ 5 | 6 | All types of contributions are encouraged and valued. See the [Table of Contents](#table-of-contents) for different ways to help and details about how this project handles them. Please make sure to read the relevant section before making your contribution. It will make it a lot easier for us maintainers and smooth out the experience for all involved. The community looks forward to your contributions. 🎉 7 | 8 | > And if you like the project, but just don't have time to contribute, that's fine. There are other easy ways to support the project and show your appreciation, which we would also be very happy about: 9 | > - Star the project 10 | > - Tweet about it 11 | > - Refer this project in your project's readme 12 | > - Mention the project at local meetups and tell your friends/colleagues 13 | 14 | <!-- omit in toc --> 15 | ## Table of Contents 16 | 17 | - [Code of Conduct](#code-of-conduct) 18 | - [I Have a Question](#i-have-a-question) 19 | - [I Want To Contribute](#i-want-to-contribute) 20 | - [Reporting Bugs](#reporting-bugs) 21 | - [Suggesting Enhancements](#suggesting-enhancements) 22 | 23 | ## Code of Conduct 24 | 25 | This project and everyone participating in it is governed by the 26 | [mergo Code of Conduct](https://github.com/darccio/mergo/blob/master/CODE_OF_CONDUCT.md). 27 | By participating, you are expected to uphold this code. Please report unacceptable behavior 28 | to <>. 29 | 30 | 31 | ## I Have a Question 32 | 33 | > If you want to ask a question, we assume that you have read the available [Documentation](https://pkg.go.dev/github.com/darccio/mergo). 34 | 35 | Before you ask a question, it is best to search for existing [Issues](https://github.com/darccio/mergo/issues) that might help you. In case you have found a suitable issue and still need clarification, you can write your question in this issue. It is also advisable to search the internet for answers first. 36 | 37 | If you then still feel the need to ask a question and need clarification, we recommend the following: 38 | 39 | - Open an [Issue](https://github.com/darccio/mergo/issues/new). 40 | - Provide as much context as you can about what you're running into. 41 | - Provide project and platform versions (nodejs, npm, etc), depending on what seems relevant. 42 | 43 | We will then take care of the issue as soon as possible. 44 | 45 | ## I Want To Contribute 46 | 47 | > ### Legal Notice <!-- omit in toc --> 48 | > When contributing to this project, you must agree that you have authored 100% of the content, that you have the necessary rights to the content and that the content you contribute may be provided under the project license. 49 | 50 | ### Reporting Bugs 51 | 52 | <!-- omit in toc --> 53 | #### Before Submitting a Bug Report 54 | 55 | A good bug report shouldn't leave others needing to chase you up for more information. Therefore, we ask you to investigate carefully, collect information and describe the issue in detail in your report. Please complete the following steps in advance to help us fix any potential bug as fast as possible. 56 | 57 | - Make sure that you are using the latest version. 58 | - Determine if your bug is really a bug and not an error on your side e.g. using incompatible environment components/versions (Make sure that you have read the [documentation](). If you are looking for support, you might want to check [this section](#i-have-a-question)). 59 | - To see if other users have experienced (and potentially already solved) the same issue you are having, check if there is not already a bug report existing for your bug or error in the [bug tracker](https://github.com/darccio/mergo/issues?q=label%3Abug). 60 | - Also make sure to search the internet (including Stack Overflow) to see if users outside of the GitHub community have discussed the issue. 61 | - Collect information about the bug: 62 | - Stack trace (Traceback) 63 | - OS, Platform and Version (Windows, Linux, macOS, x86, ARM) 64 | - Version of the interpreter, compiler, SDK, runtime environment, package manager, depending on what seems relevant. 65 | - Possibly your input and the output 66 | - Can you reliably reproduce the issue? And can you also reproduce it with older versions? 67 | 68 | <!-- omit in toc --> 69 | #### How Do I Submit a Good Bug Report? 70 | 71 | > You must never report security related issues, vulnerabilities or bugs including sensitive information to the issue tracker, or elsewhere in public. Instead sensitive bugs must be sent by email to . 72 | <!-- You may add a PGP key to allow the messages to be sent encrypted as well. --> 73 | 74 | We use GitHub issues to track bugs and errors. If you run into an issue with the project: 75 | 76 | - Open an [Issue](https://github.com/darccio/mergo/issues/new). (Since we can't be sure at this point whether it is a bug or not, we ask you not to talk about a bug yet and not to label the issue.) 77 | - Explain the behavior you would expect and the actual behavior. 78 | - Please provide as much context as possible and describe the *reproduction steps* that someone else can follow to recreate the issue on their own. This usually includes your code. For good bug reports you should isolate the problem and create a reduced test case. 79 | - Provide the information you collected in the previous section. 80 | 81 | Once it's filed: 82 | 83 | - The project team will label the issue accordingly. 84 | - A team member will try to reproduce the issue with your provided steps. If there are no reproduction steps or no obvious way to reproduce the issue, the team will ask you for those steps and mark the issue as `needs-repro`. Bugs with the `needs-repro` tag will not be addressed until they are reproduced. 85 | - If the team is able to reproduce the issue, it will be marked `needs-fix`, as well as possibly other tags (such as `critical`), and the issue will be left to be implemented by someone. 86 | 87 | ### Suggesting Enhancements 88 | 89 | This section guides you through submitting an enhancement suggestion for mergo, **including completely new features and minor improvements to existing functionality**. Following these guidelines will help maintainers and the community to understand your suggestion and find related suggestions. 90 | 91 | <!-- omit in toc --> 92 | #### Before Submitting an Enhancement 93 | 94 | - Make sure that you are using the latest version. 95 | - Read the [documentation]() carefully and find out if the functionality is already covered, maybe by an individual configuration. 96 | - Perform a [search](https://github.com/darccio/mergo/issues) to see if the enhancement has already been suggested. If it has, add a comment to the existing issue instead of opening a new one. 97 | - Find out whether your idea fits with the scope and aims of the project. It's up to you to make a strong case to convince the project's developers of the merits of this feature. Keep in mind that we want features that will be useful to the majority of our users and not just a small subset. If you're just targeting a minority of users, consider writing an add-on/plugin library. 98 | 99 | <!-- omit in toc --> 100 | #### How Do I Submit a Good Enhancement Suggestion? 101 | 102 | Enhancement suggestions are tracked as [GitHub issues](https://github.com/darccio/mergo/issues). 103 | 104 | - Use a **clear and descriptive title** for the issue to identify the suggestion. 105 | - Provide a **step-by-step description of the suggested enhancement** in as many details as possible. 106 | - **Describe the current behavior** and **explain which behavior you expected to see instead** and why. At this point you can also tell which alternatives do not work for you. 107 | - You may want to **include screenshots and animated GIFs** which help you demonstrate the steps or point out the part which the suggestion is related to. You can use [this tool](https://www.cockos.com/licecap/) to record GIFs on macOS and Windows, and [this tool](https://github.com/colinkeenan/silentcast) or [this tool](https://github.com/GNOME/byzanz) on Linux. <!-- this should only be included if the project has a GUI --> 108 | - **Explain why this enhancement would be useful** to most mergo users. You may also want to point out the other projects that solved it better and which could serve as inspiration. 109 | 110 | <!-- omit in toc --> 111 | ## Attribution 112 | This guide is based on the **contributing-gen**. [Make your own](https://github.com/bttger/contributing-gen)! 113 | -------------------------------------------------------------------------------- /FUNDING.json: -------------------------------------------------------------------------------- 1 | { 2 | "drips": { 3 | "ethereum": { 4 | "ownedBy": "0x6160020e7102237aC41bdb156e94401692D76930" 5 | } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 Dario Castañé. All rights reserved. 2 | Copyright (c) 2012 The Go Authors. All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are 6 | met: 7 | 8 | * Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | * Redistributions in binary form must reproduce the above 11 | copyright notice, this list of conditions and the following disclaimer 12 | in the documentation and/or other materials provided with the 13 | distribution. 14 | * Neither the name of Google Inc. nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Mergo 2 | 3 | [![GitHub release][5]][6] 4 | [![GoCard][7]][8] 5 | [![Test status][1]][2] 6 | [![OpenSSF Scorecard][21]][22] 7 | [![OpenSSF Best Practices][19]][20] 8 | [![Coverage status][9]][10] 9 | [![Sourcegraph][11]][12] 10 | [![FOSSA status][13]][14] 11 | 12 | [![GoDoc][3]][4] 13 | [![Become my sponsor][15]][16] 14 | [![Tidelift][17]][18] 15 | [![Liberapay patrons][23]][24] 16 | [![Support mergo on drips.network][25]][26] 17 | 18 | [1]: https://github.com/darccio/mergo/workflows/tests/badge.svg?branch=master 19 | [2]: https://github.com/darccio/mergo/actions/workflows/tests.yml 20 | [3]: https://pkg.go.dev/badge/dario.cat/mergo 21 | [4]: https://pkg.go.dev/dario.cat/mergo 22 | [5]: https://img.shields.io/github/release/darccio/mergo.svg 23 | [6]: https://github.com/darccio/mergo/releases 24 | [7]: https://goreportcard.com/badge/dario.cat/mergo 25 | [8]: https://goreportcard.com/report/dario.cat/mergo 26 | [9]: https://coveralls.io/repos/github/darccio/mergo/badge.svg?branch=master 27 | [10]: https://coveralls.io/github/darccio/mergo?branch=master 28 | [11]: https://sourcegraph.com/github.com/imdario/mergo/-/badge.svg 29 | [12]: https://sourcegraph.com/github.com/imdario/mergo?badge 30 | [13]: https://app.fossa.io/api/projects/git%2Bgithub.com%2Fimdario%2Fmergo.svg?type=shield 31 | [14]: https://app.fossa.io/projects/git%2Bgithub.com%2Fimdario%2Fmergo?ref=badge_shield 32 | [15]: https://img.shields.io/github/sponsors/darccio 33 | [16]: https://github.com/sponsors/darccio 34 | [17]: https://tidelift.com/badges/package/go/dario.cat%2Fmergo 35 | [18]: https://tidelift.com/subscription/pkg/go-dario.cat-mergo 36 | [19]: https://www.bestpractices.dev/projects/7177/badge 37 | [20]: https://www.bestpractices.dev/en/projects/7177 38 | [21]: https://api.scorecard.dev/projects/github.com/darccio/mergo/badge 39 | [22]: https://scorecard.dev/viewer/?uri=github.com/darccio/mergo 40 | [23]: https://img.shields.io/liberapay/patrons/dario 41 | [24]: https://liberapay.com/dario/donate 42 | [25]: https://www.drips.network/api/embed/project/https%3A%2F%2Fgithub.com%2Fdarccio%2Fmergo/support.png?background=light&style=github&text=project&stat=none 43 | [26]: https://www.drips.network/app/projects/github/darccio/mergo 44 | 45 | A helper to merge structs and maps in Golang. Useful for configuration default values, avoiding messy if-statements. 46 | 47 | Mergo merges same-type structs and maps by setting default values in zero-value fields. Mergo won't merge unexported (private) fields. It will do recursively any exported one. It also won't merge structs inside maps (because they are not addressable using Go reflection). 48 | 49 | Also a lovely [comune](http://en.wikipedia.org/wiki/Mergo) (municipality) in the Province of Ancona in the Italian region of Marche. 50 | 51 | ## Status 52 | 53 | Mergo is stable and frozen, ready for production. Check a short list of the projects using at large scale it [here](https://github.com/darccio/mergo#mergo-in-the-wild). 54 | 55 | No new features are accepted. They will be considered for a future v2 that improves the implementation and fixes bugs for corner cases. 56 | 57 | ### Important notes 58 | 59 | #### 1.0.0 60 | 61 | In [1.0.0](//github.com/darccio/mergo/releases/tag/1.0.0) Mergo moves to a vanity URL `dario.cat/mergo`. No more v1 versions will be released. 62 | 63 | If the vanity URL is causing issues in your project due to a dependency pulling Mergo - it isn't a direct dependency in your project - it is recommended to use [replace](https://github.com/golang/go/wiki/Modules#when-should-i-use-the-replace-directive) to pin the version to the last one with the old import URL: 64 | 65 | ``` 66 | replace github.com/imdario/mergo => github.com/imdario/mergo v0.3.16 67 | ``` 68 | 69 | #### 0.3.9 70 | 71 | Please keep in mind that a problematic PR broke [0.3.9](//github.com/darccio/mergo/releases/tag/0.3.9). I reverted it in [0.3.10](//github.com/darccio/mergo/releases/tag/0.3.10), and I consider it stable but not bug-free. Also, this version adds support for go modules. 72 | 73 | Keep in mind that in [0.3.2](//github.com/darccio/mergo/releases/tag/0.3.2), Mergo changed `Merge()`and `Map()` signatures to support [transformers](#transformers). I added an optional/variadic argument so that it won't break the existing code. 74 | 75 | If you were using Mergo before April 6th, 2015, please check your project works as intended after updating your local copy with ```go get -u dario.cat/mergo```. I apologize for any issue caused by its previous behavior and any future bug that Mergo could cause in existing projects after the change (release 0.2.0). 76 | 77 | ### Donations 78 | 79 | If Mergo is useful to you, consider buying me a coffee, a beer, or making a monthly donation to allow me to keep building great free software. :heart_eyes: 80 | 81 | <a href="https://liberapay.com/dario/donate"><img alt="Donate using Liberapay" src="https://liberapay.com/assets/widgets/donate.svg"></a> 82 | <a href='https://github.com/sponsors/darccio' target='_blank'><img alt="Become my sponsor" src="https://img.shields.io/github/sponsors/darccio?style=for-the-badge" /></a> 83 | 84 | ### Mergo in the wild 85 | 86 | Mergo is used by [thousands](https://deps.dev/go/dario.cat%2Fmergo/v1.0.0/dependents) [of](https://deps.dev/go/github.com%2Fimdario%2Fmergo/v0.3.16/dependents) [projects](https://deps.dev/go/github.com%2Fimdario%2Fmergo/v0.3.12), including: 87 | 88 | * [containerd/containerd](https://github.com/containerd/containerd) 89 | * [datadog/datadog-agent](https://github.com/datadog/datadog-agent) 90 | * [docker/cli/](https://github.com/docker/cli/) 91 | * [goreleaser/goreleaser](https://github.com/goreleaser/goreleaser) 92 | * [go-micro/go-micro](https://github.com/go-micro/go-micro) 93 | * [grafana/loki](https://github.com/grafana/loki) 94 | * [masterminds/sprig](github.com/Masterminds/sprig) 95 | * [moby/moby](https://github.com/moby/moby) 96 | * [slackhq/nebula](https://github.com/slackhq/nebula) 97 | * [volcano-sh/volcano](https://github.com/volcano-sh/volcano) 98 | 99 | ## Install 100 | 101 | go get dario.cat/mergo 102 | 103 | // use in your .go code 104 | import ( 105 | "dario.cat/mergo" 106 | ) 107 | 108 | ## Usage 109 | 110 | You can only merge same-type structs with exported fields initialized as zero value of their type and same-types maps. Mergo won't merge unexported (private) fields but will do recursively any exported one. It won't merge empty structs value as [they are zero values](https://golang.org/ref/spec#The_zero_value) too. Also, maps will be merged recursively except for structs inside maps (because they are not addressable using Go reflection). 111 | 112 | ```go 113 | if err := mergo.Merge(&dst, src); err != nil { 114 | // ... 115 | } 116 | ``` 117 | 118 | Also, you can merge overwriting values using the transformer `WithOverride`. 119 | 120 | ```go 121 | if err := mergo.Merge(&dst, src, mergo.WithOverride); err != nil { 122 | // ... 123 | } 124 | ``` 125 | 126 | If you need to override pointers, so the source pointer's value is assigned to the destination's pointer, you must use `WithoutDereference`: 127 | 128 | ```go 129 | package main 130 | 131 | import ( 132 | "fmt" 133 | 134 | "dario.cat/mergo" 135 | ) 136 | 137 | type Foo struct { 138 | A *string 139 | B int64 140 | } 141 | 142 | func main() { 143 | first := "first" 144 | second := "second" 145 | src := Foo{ 146 | A: &first, 147 | B: 2, 148 | } 149 | 150 | dest := Foo{ 151 | A: &second, 152 | B: 1, 153 | } 154 | 155 | mergo.Merge(&dest, src, mergo.WithOverride, mergo.WithoutDereference) 156 | } 157 | ``` 158 | 159 | Additionally, you can map a `map[string]interface{}` to a struct (and otherwise, from struct to map), following the same restrictions as in `Merge()`. Keys are capitalized to find each corresponding exported field. 160 | 161 | ```go 162 | if err := mergo.Map(&dst, srcMap); err != nil { 163 | // ... 164 | } 165 | ``` 166 | 167 | Warning: if you map a struct to map, it won't do it recursively. Don't expect Mergo to map struct members of your struct as `map[string]interface{}`. They will be just assigned as values. 168 | 169 | Here is a nice example: 170 | 171 | ```go 172 | package main 173 | 174 | import ( 175 | "fmt" 176 | "dario.cat/mergo" 177 | ) 178 | 179 | type Foo struct { 180 | A string 181 | B int64 182 | } 183 | 184 | func main() { 185 | src := Foo{ 186 | A: "one", 187 | B: 2, 188 | } 189 | dest := Foo{ 190 | A: "two", 191 | } 192 | mergo.Merge(&dest, src) 193 | fmt.Println(dest) 194 | // Will print 195 | // {two 2} 196 | } 197 | ``` 198 | 199 | ### Transformers 200 | 201 | Transformers allow to merge specific types differently than in the default behavior. In other words, now you can customize how some types are merged. For example, `time.Time` is a struct; it doesn't have zero value but IsZero can return true because it has fields with zero value. How can we merge a non-zero `time.Time`? 202 | 203 | ```go 204 | package main 205 | 206 | import ( 207 | "fmt" 208 | "dario.cat/mergo" 209 | "reflect" 210 | "time" 211 | ) 212 | 213 | type timeTransformer struct { 214 | } 215 | 216 | func (t timeTransformer) Transformer(typ reflect.Type) func(dst, src reflect.Value) error { 217 | if typ == reflect.TypeOf(time.Time{}) { 218 | return func(dst, src reflect.Value) error { 219 | if dst.CanSet() { 220 | isZero := dst.MethodByName("IsZero") 221 | result := isZero.Call([]reflect.Value{}) 222 | if result[0].Bool() { 223 | dst.Set(src) 224 | } 225 | } 226 | return nil 227 | } 228 | } 229 | return nil 230 | } 231 | 232 | type Snapshot struct { 233 | Time time.Time 234 | // ... 235 | } 236 | 237 | func main() { 238 | src := Snapshot{time.Now()} 239 | dest := Snapshot{} 240 | mergo.Merge(&dest, src, mergo.WithTransformers(timeTransformer{})) 241 | fmt.Println(dest) 242 | // Will print 243 | // { 2018-01-12 01:15:00 +0000 UTC m=+0.000000001 } 244 | } 245 | ``` 246 | 247 | ## Contact me 248 | 249 | If I can help you, you have an idea or you are using Mergo in your projects, don't hesitate to drop me a line (or a pull request): [@im_dario](https://twitter.com/im_dario) 250 | 251 | ## About 252 | 253 | Written by [Dario Castañé](http://dario.cat). 254 | 255 | ## License 256 | 257 | [BSD 3-Clause](http://opensource.org/licenses/BSD-3-Clause) license, as [Go language](http://golang.org/LICENSE). 258 | 259 | [](https://app.fossa.io/projects/git%2Bgithub.com%2Fimdario%2Fmergo?ref=badge_large) 260 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | | Version | Supported | 6 | | ------- | ------------------ | 7 | | 1.x.x | :white_check_mark: | 8 | | < 1.0 | :x: | 9 | 10 | ## Security contact information 11 | 12 | To report a security vulnerability, please use the 13 | [Tidelift security contact](https://tidelift.com/security). 14 | Tidelift will coordinate the fix and disclosure. 15 | -------------------------------------------------------------------------------- /benchmark_test.go: -------------------------------------------------------------------------------- 1 | package mergo_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "dario.cat/mergo" 7 | ) 8 | 9 | func BenchmarkMerge(b *testing.B) { 10 | type ts struct { 11 | Field string 12 | } 13 | 14 | var ( 15 | dst = ts{} 16 | src = ts{ 17 | Field: "src", 18 | } 19 | ) 20 | 21 | b.ResetTimer() 22 | 23 | for i := 0; i < b.N; i++ { 24 | err := mergo.Merge(&dst, src) 25 | if err != nil { 26 | b.Fatal(err) 27 | } 28 | dst.Field = "" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 Dario Castañé. All rights reserved. 2 | // Copyright 2009 The Go Authors. All rights reserved. 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file. 5 | 6 | /* 7 | A helper to merge structs and maps in Golang. Useful for configuration default values, avoiding messy if-statements. 8 | 9 | Mergo merges same-type structs and maps by setting default values in zero-value fields. Mergo won't merge unexported (private) fields. It will do recursively any exported one. It also won't merge structs inside maps (because they are not addressable using Go reflection). 10 | 11 | # Status 12 | 13 | It is ready for production use. It is used in several projects by Docker, Google, The Linux Foundation, VMWare, Shopify, etc. 14 | 15 | # Important notes 16 | 17 | 1.0.0 18 | 19 | In 1.0.0 Mergo moves to a vanity URL `dario.cat/mergo`. 20 | 21 | 0.3.9 22 | 23 | Please keep in mind that a problematic PR broke 0.3.9. We reverted it in 0.3.10. We consider 0.3.10 as stable but not bug-free. . Also, this version adds suppot for go modules. 24 | 25 | Keep in mind that in 0.3.2, Mergo changed Merge() and Map() signatures to support transformers. We added an optional/variadic argument so that it won't break the existing code. 26 | 27 | If you were using Mergo before April 6th, 2015, please check your project works as intended after updating your local copy with go get -u dario.cat/mergo. I apologize for any issue caused by its previous behavior and any future bug that Mergo could cause in existing projects after the change (release 0.2.0). 28 | 29 | # Install 30 | 31 | Do your usual installation procedure: 32 | 33 | go get dario.cat/mergo 34 | 35 | // use in your .go code 36 | import ( 37 | "dario.cat/mergo" 38 | ) 39 | 40 | # Usage 41 | 42 | You can only merge same-type structs with exported fields initialized as zero value of their type and same-types maps. Mergo won't merge unexported (private) fields but will do recursively any exported one. It won't merge empty structs value as they are zero values too. Also, maps will be merged recursively except for structs inside maps (because they are not addressable using Go reflection). 43 | 44 | if err := mergo.Merge(&dst, src); err != nil { 45 | // ... 46 | } 47 | 48 | Also, you can merge overwriting values using the transformer WithOverride. 49 | 50 | if err := mergo.Merge(&dst, src, mergo.WithOverride); err != nil { 51 | // ... 52 | } 53 | 54 | Additionally, you can map a map[string]interface{} to a struct (and otherwise, from struct to map), following the same restrictions as in Merge(). Keys are capitalized to find each corresponding exported field. 55 | 56 | if err := mergo.Map(&dst, srcMap); err != nil { 57 | // ... 58 | } 59 | 60 | Warning: if you map a struct to map, it won't do it recursively. Don't expect Mergo to map struct members of your struct as map[string]interface{}. They will be just assigned as values. 61 | 62 | Here is a nice example: 63 | 64 | package main 65 | 66 | import ( 67 | "fmt" 68 | "dario.cat/mergo" 69 | ) 70 | 71 | type Foo struct { 72 | A string 73 | B int64 74 | } 75 | 76 | func main() { 77 | src := Foo{ 78 | A: "one", 79 | B: 2, 80 | } 81 | dest := Foo{ 82 | A: "two", 83 | } 84 | mergo.Merge(&dest, src) 85 | fmt.Println(dest) 86 | // Will print 87 | // {two 2} 88 | } 89 | 90 | # Transformers 91 | 92 | Transformers allow to merge specific types differently than in the default behavior. In other words, now you can customize how some types are merged. For example, time.Time is a struct; it doesn't have zero value but IsZero can return true because it has fields with zero value. How can we merge a non-zero time.Time? 93 | 94 | package main 95 | 96 | import ( 97 | "fmt" 98 | "dario.cat/mergo" 99 | "reflect" 100 | "time" 101 | ) 102 | 103 | type timeTransformer struct { 104 | } 105 | 106 | func (t timeTransformer) Transformer(typ reflect.Type) func(dst, src reflect.Value) error { 107 | if typ == reflect.TypeOf(time.Time{}) { 108 | return func(dst, src reflect.Value) error { 109 | if dst.CanSet() { 110 | isZero := dst.MethodByName("IsZero") 111 | result := isZero.Call([]reflect.Value{}) 112 | if result[0].Bool() { 113 | dst.Set(src) 114 | } 115 | } 116 | return nil 117 | } 118 | } 119 | return nil 120 | } 121 | 122 | type Snapshot struct { 123 | Time time.Time 124 | // ... 125 | } 126 | 127 | func main() { 128 | src := Snapshot{time.Now()} 129 | dest := Snapshot{} 130 | mergo.Merge(&dest, src, mergo.WithTransformers(timeTransformer{})) 131 | fmt.Println(dest) 132 | // Will print 133 | // { 2018-01-12 01:15:00 +0000 UTC m=+0.000000001 } 134 | } 135 | 136 | # Contact me 137 | 138 | If I can help you, you have an idea or you are using Mergo in your projects, don't hesitate to drop me a line (or a pull request): https://twitter.com/im_dario 139 | 140 | # About 141 | 142 | Written by Dario Castañé: https://da.rio.hn 143 | 144 | # License 145 | 146 | BSD 3-Clause license, as Go language. 147 | */ 148 | package mergo 149 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module dario.cat/mergo 2 | 3 | go 1.18 4 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darccio/mergo/9b031a0e7dd017cda99cf4d5f0ee18aa24252367/go.sum -------------------------------------------------------------------------------- /internal/ng/README.md: -------------------------------------------------------------------------------- 1 | # Mergo 2 | 3 | ## Why next gen? 4 | 5 | 1. Cleaner code. 6 | 2. Reduce `interface{}`/`any` usage in the API. 7 | 3. Allow the compiler to optimize the code through generics. 8 | 4. Reduce allocations: v1 does 4 allocations per merge. 9 | 5. Reduce `reflect` usage. 10 | 6. Migrate from sentinel errors to [concrete error types](https://jub0bs.com/posts/2025-03-31-why-concrete-error-types-are-superior-to-sentinel-errors/). 11 | -------------------------------------------------------------------------------- /internal/ng/merge.go: -------------------------------------------------------------------------------- 1 | package ng 2 | 3 | import "reflect" 4 | 5 | // Copyright 2025 Dario Castañé. All rights reserved. 6 | // Copyright 2009 The Go Authors. All rights reserved. 7 | // Use of this source code is governed by a BSD-style 8 | // license that can be found in the LICENSE file. 9 | 10 | // Based on src/reflect/deepequal.go from official 11 | // golang's stdlib. 12 | 13 | // Don't use this package. It's a work in progress. 14 | // This is the next generation of mergo, and it's not ready for production. 15 | 16 | type NilArgumentsError struct{} 17 | 18 | func (*NilArgumentsError) Error() string { 19 | return "src and dst must not be nil" 20 | } 21 | 22 | type InvalidDestinationError struct{} 23 | 24 | func (*InvalidDestinationError) Error() string { 25 | return "dst must be a pointer" 26 | } 27 | 28 | type DifferentArgumentTypesError struct{} 29 | 30 | func (*DifferentArgumentTypesError) Error() string { 31 | return "dst and src must have the same type" 32 | } 33 | 34 | // Merge sets any [zero-value](https://go.dev/ref/spec#The_zero_value) field 35 | // in dst with the same field's value in src. 36 | // Both dst and src must have the same type, being dst a pointer to the type. 37 | // Merge returns NilArgumentsError if dst is a nil pointer and/or src is a nil 38 | // value. 39 | // If dst and src are values of predefined types, and dst is the type's zero 40 | // value, src is assigned to dst. 41 | // Merge is a convenient wrapper around the more compiler-friendly MergeValue 42 | // and MergePtr functions. 43 | func Merge(dst, src any) error { 44 | if dst == nil { 45 | // As dst pointer is a copy; assigning src to a nil pointer is an 46 | // ineffective assignment. 47 | return new(NilArgumentsError) 48 | } 49 | 50 | if src == nil { 51 | // Nothing to do here. 52 | return new(NilArgumentsError) 53 | } 54 | 55 | dstValue := reflect.ValueOf(dst) 56 | if dstValue.Kind() != reflect.Ptr { 57 | return new(InvalidDestinationError) 58 | } 59 | 60 | dstValue = dstValue.Elem() 61 | srcValue := reflect.ValueOf(src) 62 | 63 | if srcValue.Kind() == reflect.Ptr { 64 | srcValue = srcValue.Elem() 65 | } 66 | 67 | if dstValue.Type() != srcValue.Type() { 68 | return new(DifferentArgumentTypesError) 69 | } 70 | 71 | merge(dstValue, srcValue, dstValue.Type()) 72 | 73 | return nil 74 | } 75 | 76 | func MergeValue[T any](dst *T, src T) error { 77 | if dst == nil { 78 | // As dst pointer is a copy; assigning src to a nil pointer is an 79 | // ineffective assignment. 80 | return new(NilArgumentsError) 81 | } 82 | 83 | dstValue := reflect.ValueOf(dst).Elem() 84 | srcValue := reflect.ValueOf(src) 85 | 86 | merge(dstValue, srcValue, dstValue.Type()) 87 | 88 | return nil 89 | } 90 | 91 | func MergePtr[T any](dst, src *T) error { 92 | if dst == nil { 93 | // As dst pointer is a copy; assigning src to a nil pointer is an 94 | // ineffective assignment. 95 | return new(NilArgumentsError) 96 | } 97 | 98 | if src == nil { 99 | // Nothing to do here. 100 | return new(NilArgumentsError) 101 | } 102 | 103 | dstValue := reflect.ValueOf(dst).Elem() 104 | srcValue := reflect.ValueOf(src).Elem() 105 | 106 | merge(dstValue, srcValue, dstValue.Type()) 107 | 108 | return nil 109 | } 110 | 111 | func merge(dst, src reflect.Value, typ reflect.Type) { 112 | if typ.Kind() == reflect.Struct { 113 | mergeStruct(dst, src, typ) 114 | } 115 | 116 | // TODO: handle maps and slices 117 | // TODO: handle pointers and interfaces 118 | // TODO: cover all potential empty cases (as in isEmptyValue from v1) 119 | if !dst.IsZero() { 120 | return 121 | } 122 | 123 | dst.Set(src) 124 | } 125 | 126 | func mergeStruct(dst, src reflect.Value, typ reflect.Type) { 127 | for i := 0; i < typ.NumField(); i++ { 128 | field := typ.Field(i) 129 | dstField := dst.Field(i) 130 | srcField := src.Field(i) 131 | 132 | if !dstField.CanSet() { 133 | continue 134 | } 135 | 136 | merge(dstField, srcField, field.Type) 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /internal/ng/merge_test.go: -------------------------------------------------------------------------------- 1 | package ng_test 2 | 3 | import ( 4 | "errors" 5 | "testing" 6 | 7 | mergo "dario.cat/mergo/internal/ng" 8 | ) 9 | 10 | func TestMerge(t *testing.T) { 11 | t.Parallel() 12 | 13 | type ts struct { 14 | Field int 15 | } 16 | 17 | t.Run("merge", func(t *testing.T) { 18 | t.Parallel() 19 | 20 | var ( 21 | dst = ts{ 22 | Field: 0, 23 | } 24 | src = ts{ 25 | Field: 1, 26 | } 27 | ) 28 | 29 | if err := mergo.Merge(&dst, src); err != nil { 30 | t.Errorf("Error while merging %s", err) 31 | } 32 | 33 | if dst.Field != src.Field { 34 | t.Errorf("Expected dst.Field to be %d, got %d", src.Field, dst.Field) 35 | } 36 | }) 37 | 38 | t.Run("merge from pointer", func(t *testing.T) { 39 | t.Parallel() 40 | 41 | var ( 42 | dst = &ts{ 43 | Field: 0, 44 | } 45 | src = &ts{ 46 | Field: 1, 47 | } 48 | ) 49 | 50 | if err := mergo.Merge(dst, src); err != nil { 51 | t.Errorf("Error while merging %s", err) 52 | } 53 | 54 | if dst.Field != src.Field { 55 | t.Errorf("Expected dst.Field to be %d, got %d", src.Field, dst.Field) 56 | } 57 | }) 58 | } 59 | 60 | func TestMergePredefinedType(t *testing.T) { 61 | t.Parallel() 62 | 63 | t.Run("merge ints", func(t *testing.T) { 64 | t.Parallel() 65 | 66 | var ( 67 | dst = 0 68 | src = 1 69 | ) 70 | 71 | if err := mergo.Merge(&dst, src); err != nil { 72 | t.Errorf("Error while merging %s", err) 73 | } 74 | 75 | if dst != src { 76 | t.Errorf("Expected dst to be %d, got %d", src, dst) 77 | } 78 | }) 79 | 80 | t.Run("merge strings", func(t *testing.T) { 81 | t.Parallel() 82 | 83 | var ( 84 | dst = "" 85 | src = "src" 86 | ) 87 | 88 | if err := mergo.Merge(&dst, src); err != nil { 89 | t.Errorf("Error while merging %s", err) 90 | } 91 | 92 | if dst != src { 93 | t.Errorf("Expected dst to be %q, got %q", src, dst) 94 | } 95 | }) 96 | } 97 | 98 | func TestMergeNil(t *testing.T) { 99 | t.Parallel() 100 | 101 | t.Run("both nil", func(t *testing.T) { 102 | t.Parallel() 103 | 104 | var naerr *mergo.NilArgumentsError 105 | 106 | if err := mergo.Merge(nil, nil); !errors.As(err, &naerr) { 107 | t.Error(err) 108 | } 109 | }) 110 | 111 | t.Run("dst nil", func(t *testing.T) { 112 | t.Parallel() 113 | 114 | var naerr *mergo.NilArgumentsError 115 | 116 | if err := mergo.Merge(nil, struct{}{}); !errors.As(err, &naerr) { 117 | t.Error(err) 118 | } 119 | }) 120 | 121 | t.Run("src nil", func(t *testing.T) { 122 | t.Parallel() 123 | 124 | var ( 125 | dst = struct{}{} 126 | naerr *mergo.NilArgumentsError 127 | ) 128 | 129 | if err := mergo.Merge(&dst, nil); !errors.As(err, &naerr) { 130 | t.Error(err) 131 | } 132 | }) 133 | } 134 | 135 | func TestMergeNotZero(t *testing.T) { 136 | t.Parallel() 137 | 138 | type ts struct { 139 | Field int 140 | } 141 | 142 | var ( 143 | dst = ts{ 144 | Field: 1, 145 | } 146 | src = ts{ 147 | Field: 2, 148 | } 149 | ) 150 | 151 | if err := mergo.Merge(&dst, src); err != nil { 152 | t.Errorf("Error while merging %s", err) 153 | } 154 | 155 | if dst.Field == src.Field { 156 | t.Errorf("Expected dst.Field to be 1, got %d", dst.Field) 157 | } 158 | } 159 | 160 | func TestMergeOnlyExportedFields(t *testing.T) { 161 | t.Parallel() 162 | 163 | type ts struct { 164 | Field int 165 | unexported int 166 | } 167 | 168 | var ( 169 | dst = ts{ 170 | Field: 0, 171 | unexported: 2, 172 | } 173 | src = ts{ 174 | Field: 3, 175 | unexported: 4, 176 | } 177 | ) 178 | 179 | if err := mergo.Merge(&dst, src); err != nil { 180 | t.Errorf("Error while merging %s", err) 181 | } 182 | 183 | if dst.Field != src.Field { 184 | t.Errorf("Expected dst.Field to be %d, got %d", src.Field, dst.Field) 185 | } 186 | 187 | if dst.unexported == src.unexported { 188 | t.Errorf("Expected dst.unexported to be 2, got %d", dst.unexported) 189 | } 190 | } 191 | 192 | func TestMergeNotPointer(t *testing.T) { 193 | t.Parallel() 194 | 195 | var npeerr *mergo.InvalidDestinationError 196 | if err := mergo.Merge(struct{}{}, struct{}{}); !errors.As(err, &npeerr) { 197 | t.Error(err) 198 | } 199 | } 200 | 201 | func TestMergeDifferentTypes(t *testing.T) { 202 | t.Parallel() 203 | 204 | dst := struct { 205 | Field int 206 | }{ 207 | Field: 1, 208 | } 209 | 210 | src := struct { 211 | Field string 212 | }{ 213 | Field: "src", 214 | } 215 | 216 | var dteerr *mergo.DifferentArgumentTypesError 217 | if err := mergo.Merge(&dst, src); !errors.As(err, &dteerr) { 218 | t.Error(err) 219 | } 220 | } 221 | 222 | func TestMergeValue(t *testing.T) { 223 | t.Parallel() 224 | 225 | type ts struct { 226 | Field int 227 | } 228 | 229 | dst := ts{ 230 | Field: 0, 231 | } 232 | 233 | src := ts{ 234 | Field: 1, 235 | } 236 | 237 | if err := mergo.MergeValue(&dst, src); err != nil { 238 | t.Error(err) 239 | } 240 | 241 | if dst.Field != src.Field { 242 | t.Errorf("Expected dst.Field to be %d, got %d", src.Field, dst.Field) 243 | } 244 | } 245 | 246 | func TestMergeValueNil(t *testing.T) { 247 | t.Parallel() 248 | 249 | var naerr *mergo.NilArgumentsError 250 | 251 | if err := mergo.MergeValue(nil, struct{}{}); !errors.As(err, &naerr) { 252 | t.Error(err) 253 | } 254 | } 255 | 256 | func TestMergePtr(t *testing.T) { 257 | t.Parallel() 258 | 259 | type ts struct { 260 | Field int 261 | } 262 | 263 | var ( 264 | dst = &ts{ 265 | Field: 0, 266 | } 267 | src = &ts{ 268 | Field: 1, 269 | } 270 | ) 271 | 272 | if err := mergo.MergePtr(dst, src); err != nil { 273 | t.Error(err) 274 | } 275 | 276 | if dst.Field != src.Field { 277 | t.Errorf("Expected dst.Field to be %d, got %d", src.Field, dst.Field) 278 | } 279 | } 280 | 281 | func TestMergePtrNil(t *testing.T) { 282 | t.Parallel() 283 | 284 | type ts struct { 285 | Field int 286 | } 287 | 288 | t.Run("dst nil", func(t *testing.T) { 289 | t.Parallel() 290 | 291 | var ( 292 | src = &ts{ 293 | Field: 0, 294 | } 295 | naerr *mergo.NilArgumentsError 296 | ) 297 | 298 | if err := mergo.MergePtr(nil, src); !errors.As(err, &naerr) { 299 | t.Error(err) 300 | } 301 | }) 302 | 303 | t.Run("src nil", func(t *testing.T) { 304 | t.Parallel() 305 | 306 | var ( 307 | dst = &ts{ 308 | Field: 0, 309 | } 310 | naerr *mergo.NilArgumentsError 311 | ) 312 | 313 | if err := mergo.MergePtr(dst, nil); !errors.As(err, &naerr) { 314 | t.Error(err) 315 | } 316 | }) 317 | 318 | t.Run("both nil", func(t *testing.T) { 319 | t.Parallel() 320 | 321 | var ( 322 | dst *ts 323 | src *ts 324 | naerr *mergo.NilArgumentsError 325 | ) 326 | 327 | if err := mergo.MergePtr(dst, src); !errors.As(err, &naerr) { 328 | t.Error(err) 329 | } 330 | }) 331 | } 332 | 333 | func TestErrorMessages(t *testing.T) { 334 | t.Parallel() 335 | 336 | t.Run("NilArgumentsError", func(t *testing.T) { 337 | t.Parallel() 338 | 339 | msg := "src and dst must not be nil" 340 | if err := mergo.Merge(nil, nil); err.Error() != msg { 341 | t.Errorf("Expected error to be %q, got %q", msg, err.Error()) 342 | } 343 | }) 344 | 345 | t.Run("InvalidDestinationError", func(t *testing.T) { 346 | t.Parallel() 347 | 348 | msg := "dst must be a pointer" 349 | if err := mergo.Merge(struct{}{}, struct{}{}); err.Error() != msg { 350 | t.Errorf("Expected error to be %q, got %q", msg, err.Error()) 351 | } 352 | }) 353 | 354 | t.Run("DifferentArgumentTypesError", func(t *testing.T) { 355 | t.Parallel() 356 | 357 | dst := struct { 358 | Field int 359 | }{ 360 | Field: 1, 361 | } 362 | 363 | src := struct { 364 | Field string 365 | }{ 366 | Field: "src", 367 | } 368 | 369 | msg := "dst and src must have the same type" 370 | if err := mergo.Merge(&dst, src); err.Error() != msg { 371 | t.Errorf("Expected error to be %q, got %q", msg, err.Error()) 372 | } 373 | }) 374 | } 375 | 376 | func BenchmarkMerge(b *testing.B) { 377 | b.ReportAllocs() 378 | 379 | type ts struct { 380 | Field int 381 | } 382 | 383 | src := ts{ 384 | Field: 2, 385 | } 386 | 387 | b.Run("Merge", func(b *testing.B) { 388 | dst := ts{ 389 | Field: 0, 390 | } 391 | 392 | b.ResetTimer() 393 | 394 | for i := 0; i < b.N; i++ { 395 | if err := mergo.Merge(&dst, src); err != nil { 396 | b.Fatal(err) 397 | } 398 | 399 | dst.Field = 0 400 | } 401 | }) 402 | 403 | b.Run("MergeValue", func(b *testing.B) { 404 | dst := ts{ 405 | Field: 0, 406 | } 407 | 408 | b.ResetTimer() 409 | 410 | for i := 0; i < b.N; i++ { 411 | if err := mergo.MergeValue(&dst, src); err != nil { 412 | b.Fatal(err) 413 | } 414 | 415 | dst.Field = 0 416 | } 417 | }) 418 | 419 | b.Run("MergePtr", func(b *testing.B) { 420 | dst := &ts{ 421 | Field: 0, 422 | } 423 | 424 | b.ResetTimer() 425 | 426 | for i := 0; i < b.N; i++ { 427 | if err := mergo.MergePtr(dst, &src); err != nil { 428 | b.Fatal(err) 429 | } 430 | 431 | dst.Field = 0 432 | } 433 | }) 434 | } 435 | -------------------------------------------------------------------------------- /issue100_test.go: -------------------------------------------------------------------------------- 1 | package mergo_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "dario.cat/mergo" 7 | ) 8 | 9 | type issue100s struct { 10 | Member interface{} 11 | } 12 | 13 | func TestIssue100(t *testing.T) { 14 | m := make(map[string]interface{}) 15 | m["Member"] = "anything" 16 | 17 | st := &issue100s{} 18 | if err := mergo.Map(st, m); err != nil { 19 | t.Error(err) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /issue104_test.go: -------------------------------------------------------------------------------- 1 | package mergo_test 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | 7 | "dario.cat/mergo" 8 | ) 9 | 10 | type Record struct { 11 | Data map[string]interface{} 12 | Mapping map[string]string 13 | } 14 | 15 | func StructToRecord(in interface{}) *Record { 16 | rec := Record{} 17 | rec.Data = make(map[string]interface{}) 18 | rec.Mapping = make(map[string]string) 19 | typ := reflect.TypeOf(in) 20 | for i := 0; i < typ.NumField(); i++ { 21 | field := typ.Field(i) 22 | dbFieldName := field.Tag.Get("db") 23 | if dbFieldName != "" { 24 | rec.Mapping[field.Name] = dbFieldName 25 | } 26 | } 27 | 28 | if err := mergo.Map(&rec.Data, in); err != nil { 29 | panic(err) 30 | } 31 | return &rec 32 | } 33 | 34 | func TestStructToRecord(t *testing.T) { 35 | type A struct { 36 | Name string `json:"name" db:"name"` 37 | CIDR string `json:"cidr" db:"cidr"` 38 | } 39 | a := A{Name: "David", CIDR: "10.0.0.0/8"} 40 | rec := StructToRecord(a) 41 | if len(rec.Mapping) < 2 { 42 | t.Fatalf("struct to record failed, no mapping, struct missing tags?, rec: %+v, a: %+v ", rec, a) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /issue121_test.go: -------------------------------------------------------------------------------- 1 | package mergo_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "dario.cat/mergo" 7 | ) 8 | 9 | func TestIssue121WithSliceDeepCopy(t *testing.T) { 10 | dst := map[string]interface{}{ 11 | "inter": map[string]interface{}{ 12 | "a": "1", 13 | "b": "2", 14 | }, 15 | } 16 | 17 | src := map[string]interface{}{ 18 | "inter": map[string]interface{}{ 19 | "a": "3", 20 | "c": "4", 21 | }, 22 | } 23 | 24 | if err := mergo.Merge(&dst, src, mergo.WithSliceDeepCopy); err != nil { 25 | t.Errorf("Error during the merge: %v", err) 26 | } 27 | 28 | if dst["inter"].(map[string]interface{})["a"].(string) != "3" { 29 | t.Error("inter.a should equal '3'") 30 | } 31 | 32 | if dst["inter"].(map[string]interface{})["c"].(string) != "4" { 33 | t.Error("inter.c should equal '4'") 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /issue123_test.go: -------------------------------------------------------------------------------- 1 | package mergo_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "dario.cat/mergo" 7 | ) 8 | 9 | func TestIssue123(t *testing.T) { 10 | src := map[string]interface{}{ 11 | "col1": nil, 12 | "col2": 4, 13 | "col3": nil, 14 | } 15 | dst := map[string]interface{}{ 16 | "col1": 2, 17 | "col2": 3, 18 | "col3": 3, 19 | } 20 | 21 | // Expected behavior 22 | if err := mergo.Merge(&dst, src, mergo.WithOverride); err != nil { 23 | t.Fatal(err) 24 | } 25 | testCases := []struct { 26 | expected interface{} 27 | key string 28 | }{ 29 | { 30 | nil, 31 | "col1", 32 | }, 33 | { 34 | 4, 35 | "col2", 36 | }, 37 | { 38 | nil, 39 | "col3", 40 | }, 41 | } 42 | for _, tC := range testCases { 43 | if dst[tC.key] != tC.expected { 44 | t.Fatalf("expected %v in dst[%q], got %v", tC.expected, tC.key, dst[tC.key]) 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /issue125_test.go: -------------------------------------------------------------------------------- 1 | package mergo_test 2 | 3 | import ( 4 | "encoding/json" 5 | "testing" 6 | 7 | "dario.cat/mergo" 8 | ) 9 | 10 | type settings struct { 11 | FirstSlice []string `json:"FirstSlice"` 12 | SecondSlice []string `json:"SecondSlice"` 13 | } 14 | 15 | func TestIssue125MergeWithOverwrite(t *testing.T) { 16 | var ( 17 | defaultSettings = settings{ 18 | FirstSlice: []string{}, 19 | SecondSlice: []string{}, 20 | } 21 | something settings 22 | data = `{"FirstSlice":[], "SecondSlice": null}` 23 | ) 24 | 25 | if err := json.Unmarshal([]byte(data), &something); err != nil { 26 | t.Errorf("Error while Unmarshalling maprequest: %s", err) 27 | } 28 | 29 | if err := mergo.Merge(&something, defaultSettings, mergo.WithOverrideEmptySlice); err != nil { 30 | t.Errorf("Error while merging: %s", err) 31 | } 32 | 33 | if something.FirstSlice == nil { 34 | t.Error("Invalid merging first slice") 35 | } 36 | 37 | if something.SecondSlice == nil { 38 | t.Error("Invalid merging second slice") 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /issue129_test.go: -------------------------------------------------------------------------------- 1 | package mergo_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "dario.cat/mergo" 7 | ) 8 | 9 | func TestIssue129Boolean(t *testing.T) { 10 | type Foo struct { 11 | A bool 12 | B bool 13 | } 14 | 15 | src := Foo{ 16 | A: true, 17 | B: false, 18 | } 19 | dst := Foo{ 20 | A: false, 21 | B: true, 22 | } 23 | 24 | // Standard behavior 25 | if err := mergo.Merge(&dst, src); err != nil { 26 | t.Error(err) 27 | } 28 | if dst.A != true { 29 | t.Errorf("expected true, got false") 30 | } 31 | if dst.B != true { 32 | t.Errorf("expected true, got false") 33 | } 34 | 35 | // Expected behavior 36 | dst = Foo{ 37 | A: false, 38 | B: true, 39 | } 40 | if err := mergo.Merge(&dst, src, mergo.WithOverwriteWithEmptyValue); err != nil { 41 | t.Error(err) 42 | } 43 | if dst.A != true { 44 | t.Errorf("expected true, got false") 45 | } 46 | if dst.B != false { 47 | t.Errorf("expected false, got true") 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /issue131_test.go: -------------------------------------------------------------------------------- 1 | package mergo_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "dario.cat/mergo" 7 | ) 8 | 9 | type foz struct { 10 | A *bool 11 | B string 12 | C *bool 13 | D *bool 14 | E *bool 15 | F *baz 16 | } 17 | 18 | type baz struct { 19 | A *bool 20 | } 21 | 22 | func TestIssue131MergeWithOverwriteWithEmptyValue(t *testing.T) { 23 | src := foz{ 24 | A: func(v bool) *bool { return &v }(false), 25 | B: "src", 26 | } 27 | dest := foz{ 28 | A: func(v bool) *bool { return &v }(true), 29 | B: "dest", 30 | } 31 | if err := mergo.Merge(&dest, src, mergo.WithOverwriteWithEmptyValue); err != nil { 32 | t.Error(err) 33 | } 34 | if *src.A != *dest.A { 35 | t.Errorf("dest.A not merged in properly: %v != %v", *src.A, *dest.A) 36 | } 37 | if src.B != dest.B { 38 | t.Errorf("dest.B not merged in properly: %v != %v", src.B, dest.B) 39 | } 40 | } 41 | 42 | func TestIssue131MergeWithoutDereferenceWithOverride(t *testing.T) { 43 | src := foz{ 44 | A: func(v bool) *bool { return &v }(false), 45 | B: "src", 46 | C: nil, 47 | D: func(v bool) *bool { return &v }(false), 48 | E: func(v bool) *bool { return &v }(true), 49 | } 50 | dest := foz{ 51 | A: func(v bool) *bool { return &v }(true), 52 | B: "dest", 53 | C: func(v bool) *bool { return &v }(false), 54 | D: nil, 55 | E: func(v bool) *bool { return &v }(false), 56 | } 57 | if err := mergo.Merge(&dest, src, mergo.WithoutDereference, mergo.WithOverride); err != nil { 58 | t.Error(err) 59 | } 60 | if *src.A != *dest.A { 61 | t.Errorf("dest.A not merged in properly: %v != %v", *src.A, *dest.A) 62 | } 63 | if src.B != dest.B { 64 | t.Errorf("dest.B not merged in properly: %v != %v", src.B, dest.B) 65 | } 66 | if *dest.C != false { 67 | t.Errorf("dest.C not merged in properly: %v != %v", *src.C, *dest.C) 68 | } 69 | if *dest.D != false { 70 | t.Errorf("dest.D not merged in properly: %v != %v", src.D, *dest.D) 71 | } 72 | if *dest.E != true { 73 | t.Errorf("dest.E not merged in properly: %v != %v", *src.E, *dest.E) 74 | } 75 | } 76 | 77 | func TestIssue131MergeWithoutDereference(t *testing.T) { 78 | src := foz{ 79 | A: func(v bool) *bool { return &v }(false), 80 | B: "src", 81 | C: nil, 82 | D: func(v bool) *bool { return &v }(false), 83 | E: func(v bool) *bool { return &v }(true), 84 | F: &baz{ 85 | A: func(v bool) *bool { return &v }(true), 86 | }, 87 | } 88 | dest := foz{ 89 | A: func(v bool) *bool { return &v }(true), 90 | B: "dest", 91 | C: func(v bool) *bool { return &v }(false), 92 | D: nil, 93 | E: func(v bool) *bool { return &v }(false), 94 | F: nil, 95 | } 96 | if err := mergo.Merge(&dest, src, mergo.WithoutDereference); err != nil { 97 | t.Error(err) 98 | } 99 | if *src.A == *dest.A { 100 | t.Errorf("dest.A should not have been merged: %v == %v", *src.A, *dest.A) 101 | } 102 | if src.B == dest.B { 103 | t.Errorf("dest.B should not have been merged: %v == %v", src.B, dest.B) 104 | } 105 | if *dest.C != false { 106 | t.Errorf("dest.C not merged in properly: %v != %v", *src.C, *dest.C) 107 | } 108 | if *dest.D != false { 109 | t.Errorf("dest.D not merged in properly: %v != %v", src.D, *dest.D) 110 | } 111 | if *dest.E == true { 112 | t.Errorf("dest.E should not have been merged: %v == %v", *src.E, *dest.E) 113 | } 114 | 115 | if dest.F == nil { 116 | t.Errorf("dest.F should not have been overridden with nil: %v == %v", src.F, dest.F) 117 | } 118 | 119 | if *dest.F.A == false { 120 | t.Errorf("dest.F.A not merged in properly: %v != %v", *src.F.A, *dest.F.A) 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /issue136_test.go: -------------------------------------------------------------------------------- 1 | package mergo_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "dario.cat/mergo" 7 | ) 8 | 9 | type embeddedTestA struct { 10 | Name string 11 | Age uint8 12 | } 13 | 14 | type embeddedTestB struct { 15 | Address string 16 | embeddedTestA 17 | } 18 | 19 | func TestMergeEmbedded(t *testing.T) { 20 | var ( 21 | err error 22 | a = &embeddedTestA{ 23 | "Suwon", 16, 24 | } 25 | b = &embeddedTestB{} 26 | ) 27 | 28 | if err := mergo.Merge(&b.embeddedTestA, *a); err != nil { 29 | t.Error(err) 30 | } 31 | 32 | if b.Name != "Suwon" { 33 | t.Errorf("%v %v", b.Name, err) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /issue138_test.go: -------------------------------------------------------------------------------- 1 | package mergo_test 2 | 3 | import ( 4 | "encoding/json" 5 | "testing" 6 | 7 | "dario.cat/mergo" 8 | ) 9 | 10 | const issue138configuration string = ` 11 | { 12 | "Port": 80 13 | } 14 | ` 15 | 16 | func TestIssue138(t *testing.T) { 17 | type config struct { 18 | Port uint16 19 | } 20 | type compatibleConfig struct { 21 | Port float64 22 | } 23 | 24 | foo := make(map[string]interface{}) 25 | // encoding/json unmarshals numbers as float64 26 | // https://golang.org/pkg/encoding/json/#Unmarshal 27 | _ = json.Unmarshal([]byte(issue138configuration), &foo) 28 | 29 | err := mergo.Map(&config{}, foo) 30 | if err == nil { 31 | t.Error("expected type mismatch error, got nil") 32 | } else { 33 | if err.Error() != "type mismatch on Port field: found float64, expected uint16" { 34 | t.Errorf("expected type mismatch error, got %q", err) 35 | } 36 | } 37 | 38 | c := compatibleConfig{} 39 | if err := mergo.Map(&c, foo); err != nil { 40 | t.Error(err) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /issue143_test.go: -------------------------------------------------------------------------------- 1 | package mergo_test 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "dario.cat/mergo" 8 | ) 9 | 10 | func TestIssue143(t *testing.T) { 11 | testCases := []struct { 12 | expected func(map[string]interface{}) error 13 | options []func(*mergo.Config) 14 | }{ 15 | { 16 | options: []func(*mergo.Config){mergo.WithOverride}, 17 | expected: func(m map[string]interface{}) error { 18 | properties := m["properties"].(map[string]interface{}) 19 | if properties["field1"] != "wrong" { 20 | return fmt.Errorf("expected %q, got %v", "wrong", properties["field1"]) 21 | } 22 | return nil 23 | }, 24 | }, 25 | { 26 | options: []func(*mergo.Config){}, 27 | expected: func(m map[string]interface{}) error { 28 | properties := m["properties"].(map[string]interface{}) 29 | if properties["field1"] == "wrong" { 30 | return fmt.Errorf("expected a map, got %v", "wrong") 31 | } 32 | return nil 33 | }, 34 | }, 35 | } 36 | for _, tC := range testCases { 37 | base := map[string]interface{}{ 38 | "properties": map[string]interface{}{ 39 | "field1": map[string]interface{}{ 40 | "type": "text", 41 | }, 42 | }, 43 | } 44 | 45 | err := mergo.Map( 46 | &base, 47 | map[string]interface{}{ 48 | "properties": map[string]interface{}{ 49 | "field1": "wrong", 50 | }, 51 | }, 52 | tC.options..., 53 | ) 54 | if err != nil { 55 | t.Error(err) 56 | } 57 | if err := tC.expected(base); err != nil { 58 | t.Error(err) 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /issue149_test.go: -------------------------------------------------------------------------------- 1 | package mergo_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "dario.cat/mergo" 7 | ) 8 | 9 | type user struct { 10 | Name string 11 | } 12 | 13 | type token struct { 14 | User *user 15 | Token *string 16 | } 17 | 18 | func TestIssue149(t *testing.T) { 19 | dest := &token{ 20 | User: &user{ 21 | Name: "destination", 22 | }, 23 | Token: nil, 24 | } 25 | tokenValue := "Issue149" 26 | src := &token{ 27 | User: nil, 28 | Token: &tokenValue, 29 | } 30 | if err := mergo.Merge(dest, src, mergo.WithOverwriteWithEmptyValue); err != nil { 31 | t.Error(err) 32 | } 33 | if dest.User != nil { 34 | t.Errorf("expected nil User, got %q", dest.User) 35 | } 36 | if dest.Token == nil { 37 | t.Errorf("expected not nil Token, got %q", *dest.Token) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /issue174_test.go: -------------------------------------------------------------------------------- 1 | package mergo_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "dario.cat/mergo" 7 | ) 8 | 9 | type structWithBlankField struct { 10 | _ struct{} 11 | A struct{} 12 | } 13 | 14 | func TestIssue174(t *testing.T) { 15 | dst := structWithBlankField{} 16 | src := structWithBlankField{} 17 | 18 | if err := mergo.Merge(&dst, src, mergo.WithOverride); err != nil { 19 | t.Error(err) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /issue17_test.go: -------------------------------------------------------------------------------- 1 | package mergo_test 2 | 3 | import ( 4 | "encoding/json" 5 | "testing" 6 | 7 | "dario.cat/mergo" 8 | ) 9 | 10 | func TestIssue17MergeWithOverwrite(t *testing.T) { 11 | var ( 12 | request = `{"timestamp":null, "name": "foo"}` 13 | maprequest = map[string]interface{}{ 14 | "timestamp": nil, 15 | "name": "foo", 16 | "newStuff": "foo", 17 | } 18 | ) 19 | 20 | var something map[string]interface{} 21 | if err := json.Unmarshal([]byte(request), &something); err != nil { 22 | t.Errorf("Error while Unmarshalling maprequest: %s", err) 23 | } 24 | 25 | if err := mergo.MergeWithOverwrite(&something, maprequest); err != nil { 26 | t.Errorf("Error while merging: %s", err) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /issue187_test.go: -------------------------------------------------------------------------------- 1 | package mergo_test 2 | 3 | import ( 4 | "dario.cat/mergo" 5 | "testing" 6 | ) 7 | 8 | func TestIssue187MergeStructToMap(t *testing.T) { 9 | dst := map[string]interface{}{ 10 | "empty": "data", 11 | } 12 | 13 | src := struct { 14 | Foo string 15 | Bar int 16 | Empty string 17 | }{ 18 | Foo: "hello", 19 | Bar: 42, 20 | } 21 | if err := mergo.Map(&dst, src); err != nil { 22 | t.Error(err) 23 | } 24 | if dst["foo"] != "hello" || dst["bar"] != 42 || dst["empty"] != "data" { 25 | t.Errorf("expected dst to be {foo: hello, bar: 42, empty: data}, got {foo: %v, bar: %v, empty: %v}", dst["foo"], dst["bar"], dst["empty"]) 26 | } 27 | } 28 | 29 | func TestIssue187MergeStructToMapWithOverwrite(t *testing.T) { 30 | dst := map[string]interface{}{ 31 | "foo": "initial", 32 | "bar": 1, 33 | "empty": "data", 34 | } 35 | src := struct { 36 | Foo string 37 | Bar int 38 | Empty string 39 | }{ 40 | Foo: "hello", 41 | Bar: 42, 42 | } 43 | if err := mergo.Map(&dst, src, mergo.WithOverride); err != nil { 44 | t.Error(err) 45 | } 46 | if dst["foo"] != "hello" || dst["bar"] != 42 || dst["empty"] != "data" { 47 | t.Errorf("expected dst to be {foo: hello, bar: 42, empty: data}, got {foo: %v, bar: %v, empty: %v}", dst["foo"], dst["bar"], dst["empty"]) 48 | } 49 | } 50 | 51 | func TestIssue187MergeStructToMapWithOverwriteWithEmptyValue(t *testing.T) { 52 | dst := map[string]interface{}{ 53 | "foo": "initial", 54 | "bar": 1, 55 | "empty": "data", 56 | } 57 | src := struct { 58 | Foo string 59 | Bar int 60 | Empty string 61 | }{ 62 | Foo: "hello", 63 | Bar: 42, 64 | } 65 | if err := mergo.Map(&dst, src, mergo.WithOverwriteWithEmptyValue); err != nil { 66 | t.Error(err) 67 | } 68 | if dst["foo"] != "hello" || dst["bar"] != 42 || dst["empty"] != "" { 69 | t.Errorf("expected dst to be {foo: hello, bar: 42, empty: }, got {foo: %v, bar: %v, empty: %v}", dst["foo"], dst["bar"], dst["empty"]) 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /issue202_test.go: -------------------------------------------------------------------------------- 1 | package mergo_test 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | 7 | "dario.cat/mergo" 8 | ) 9 | 10 | func TestIssue202(t *testing.T) { 11 | tests := []struct { 12 | name string 13 | dst, src, want map[string]interface{} 14 | }{ 15 | { 16 | name: "slice override string", 17 | dst: map[string]interface{}{ 18 | "x": 456, 19 | "y": "foo", 20 | }, 21 | src: map[string]interface{}{ 22 | "x": "123", 23 | "y": []int{1, 2, 3}, 24 | }, 25 | want: map[string]interface{}{ 26 | "x": "123", 27 | "y": []int{1, 2, 3}, 28 | }, 29 | }, 30 | { 31 | name: "string override slice", 32 | dst: map[string]interface{}{ 33 | "x": 456, 34 | "y": []int{1, 2, 3}, 35 | }, 36 | src: map[string]interface{}{ 37 | "x": "123", 38 | "y": "foo", 39 | }, 40 | want: map[string]interface{}{ 41 | "x": "123", 42 | "y": "foo", 43 | }, 44 | }, 45 | { 46 | name: "map override string", 47 | dst: map[string]interface{}{ 48 | "x": 456, 49 | "y": "foo", 50 | }, 51 | src: map[string]interface{}{ 52 | "x": "123", 53 | "y": map[string]interface{}{ 54 | "a": true, 55 | }, 56 | }, 57 | want: map[string]interface{}{ 58 | "x": "123", 59 | "y": map[string]interface{}{ 60 | "a": true, 61 | }, 62 | }, 63 | }, 64 | { 65 | name: "string override map", 66 | dst: map[string]interface{}{ 67 | "x": 456, 68 | "y": map[string]interface{}{ 69 | "a": true, 70 | }, 71 | }, 72 | src: map[string]interface{}{ 73 | "x": "123", 74 | "y": "foo", 75 | }, 76 | want: map[string]interface{}{ 77 | "x": "123", 78 | "y": "foo", 79 | }, 80 | }, 81 | { 82 | name: "map override map", 83 | dst: map[string]interface{}{ 84 | "x": 456, 85 | "y": map[string]interface{}{ 86 | "a": 10, 87 | }, 88 | }, 89 | src: map[string]interface{}{ 90 | "x": "123", 91 | "y": map[string]interface{}{ 92 | "a": true, 93 | }, 94 | }, 95 | want: map[string]interface{}{ 96 | "x": "123", 97 | "y": map[string]interface{}{ 98 | "a": true, 99 | }, 100 | }, 101 | }, 102 | { 103 | name: "map override map with merge", 104 | dst: map[string]interface{}{ 105 | "x": 456, 106 | "y": map[string]interface{}{ 107 | "a": 10, 108 | "b": 100, 109 | }, 110 | }, 111 | src: map[string]interface{}{ 112 | "x": "123", 113 | "y": map[string]interface{}{ 114 | "a": true, 115 | }, 116 | }, 117 | want: map[string]interface{}{ 118 | "x": "123", 119 | "y": map[string]interface{}{ 120 | "a": true, 121 | "b": 100, 122 | }, 123 | }, 124 | }, 125 | } 126 | 127 | for _, tt := range tests { 128 | t.Run(tt.name, func(t *testing.T) { 129 | if err := mergo.Merge(&tt.dst, tt.src, mergo.WithOverride); err != nil { 130 | t.Error(err) 131 | } 132 | 133 | if !reflect.DeepEqual(tt.dst, tt.want) { 134 | t.Errorf("maps not equal.\nwant:\n%v\ngot:\n%v\n", tt.want, tt.dst) 135 | } 136 | }) 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /issue209_test.go: -------------------------------------------------------------------------------- 1 | package mergo_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "dario.cat/mergo" 7 | ) 8 | 9 | func TestIssue209(t *testing.T) { 10 | dst := []string{"a", "b"} 11 | src := []string{"c", "d"} 12 | 13 | if err := mergo.Merge(&dst, src, mergo.WithAppendSlice); err != nil { 14 | t.Error(err) 15 | } 16 | 17 | expected := []string{"a", "b", "c", "d"} 18 | if len(dst) != len(expected) { 19 | t.Errorf("arrays not equal length") 20 | } 21 | for i := range expected { 22 | if dst[i] != expected[i] { 23 | t.Errorf("array elements at %d are not equal", i) 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /issue220_test.go: -------------------------------------------------------------------------------- 1 | package mergo_test 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | 7 | "dario.cat/mergo" 8 | ) 9 | 10 | func TestIssue220(t *testing.T) { 11 | dst := []interface{}{ 12 | map[string]int{ 13 | "a": 1, 14 | }, 15 | } 16 | src := []interface{}{ 17 | "nil", 18 | } 19 | expected := []interface{}{ 20 | map[string]int{ 21 | "a": 1, 22 | }, 23 | } 24 | 25 | err := mergo.Merge(&dst, src, mergo.WithSliceDeepCopy) 26 | if err != nil { 27 | t.Errorf("unexpected error %v", err) 28 | } 29 | 30 | if !reflect.DeepEqual(dst, expected) { 31 | t.Errorf("expected: %#v\ngot: %#v", expected, dst) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /issue230_test.go: -------------------------------------------------------------------------------- 1 | package mergo_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "dario.cat/mergo" 7 | ) 8 | 9 | var testDataM = []struct { 10 | M1 mapTest 11 | M2 mapTest 12 | WithOverrideEmptyValue bool 13 | ExpectedMap map[int]int 14 | }{ 15 | { 16 | M1: mapTest{ 17 | M: map[int]int{1: 1, 3: 3}, 18 | }, 19 | M2: mapTest{ 20 | M: map[int]int{1: 2, 2: 2}, 21 | }, 22 | WithOverrideEmptyValue: true, 23 | ExpectedMap: map[int]int{1: 1, 3: 3}, 24 | }, 25 | { 26 | M1: mapTest{ 27 | M: map[int]int{1: 1, 3: 3}, 28 | }, 29 | M2: mapTest{ 30 | M: map[int]int{1: 2, 2: 2}, 31 | }, 32 | WithOverrideEmptyValue: false, 33 | ExpectedMap: map[int]int{1: 1, 2: 2, 3: 3}, 34 | }, 35 | { 36 | M1: mapTest{ 37 | M: map[int]int{}, 38 | }, 39 | M2: mapTest{ 40 | M: map[int]int{1: 2, 2: 2}, 41 | }, 42 | WithOverrideEmptyValue: true, 43 | ExpectedMap: map[int]int{}, 44 | }, 45 | { 46 | M1: mapTest{ 47 | M: map[int]int{}, 48 | }, 49 | M2: mapTest{ 50 | M: map[int]int{1: 2, 2: 2}, 51 | }, 52 | WithOverrideEmptyValue: false, 53 | ExpectedMap: map[int]int{1: 2, 2: 2}, 54 | }, 55 | } 56 | 57 | func withOverrideEmptyValue(enable bool) func(*mergo.Config) { 58 | if enable { 59 | return mergo.WithOverwriteWithEmptyValue 60 | } 61 | 62 | return mergo.WithOverride 63 | } 64 | 65 | func TestMergeMapWithOverride(t *testing.T) { 66 | t.Parallel() 67 | 68 | for _, data := range testDataM { 69 | err := mergo.Merge(&data.M2, data.M1, withOverrideEmptyValue(data.WithOverrideEmptyValue)) 70 | if err != nil { 71 | t.Errorf("Error while merging %s", err) 72 | } 73 | 74 | if len(data.M2.M) != len(data.ExpectedMap) { 75 | t.Errorf("Got %d elements in map, but expected %d", len(data.M2.M), len(data.ExpectedMap)) 76 | return 77 | } 78 | 79 | for i, val := range data.M2.M { 80 | if val != data.ExpectedMap[i] { 81 | t.Errorf("Expected value: %d, but got %d while merging map", data.ExpectedMap[i], val) 82 | } 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /issue23_test.go: -------------------------------------------------------------------------------- 1 | package mergo_test 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | 7 | "dario.cat/mergo" 8 | ) 9 | 10 | type document struct { 11 | Created *time.Time 12 | } 13 | 14 | func TestIssue23MergeWithOverwrite(t *testing.T) { 15 | now := time.Now() 16 | dst := document{ 17 | &now, 18 | } 19 | expected := time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC) 20 | src := document{ 21 | &expected, 22 | } 23 | 24 | if err := mergo.MergeWithOverwrite(&dst, src); err != nil { 25 | t.Errorf("Error while merging %s", err) 26 | } 27 | 28 | if !dst.Created.Equal(*src.Created) { //--> https://golang.org/pkg/time/#pkg-overview 29 | t.Errorf("Created not merged in properly: dst.Created(%v) != src.Created(%v)", dst.Created, src.Created) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /issue33_test.go: -------------------------------------------------------------------------------- 1 | package mergo_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "dario.cat/mergo" 7 | ) 8 | 9 | type Foo struct { 10 | Str string 11 | Bslice []byte 12 | } 13 | 14 | func TestIssue33Merge(t *testing.T) { 15 | dest := Foo{Str: "a"} 16 | toMerge := Foo{ 17 | Str: "b", 18 | Bslice: []byte{1, 2}, 19 | } 20 | 21 | if err := mergo.Merge(&dest, toMerge); err != nil { 22 | t.Errorf("Error while merging: %s", err) 23 | } 24 | // Merge doesn't overwrite an attribute if in destination it doesn't have a zero value. 25 | // In this case, Str isn't a zero value string. 26 | if dest.Str != "a" { 27 | t.Errorf("dest.Str should have not been override as it has a non-zero value: dest.Str(%v) != 'a'", dest.Str) 28 | } 29 | // If we want to override, we must use MergeWithOverwrite or Merge using WithOverride. 30 | if err := mergo.Merge(&dest, toMerge, mergo.WithOverride); err != nil { 31 | t.Errorf("Error while merging: %s", err) 32 | } 33 | 34 | if dest.Str != toMerge.Str { 35 | t.Errorf("dest.Str should have been override: dest.Str(%v) != toMerge.Str(%v)", dest.Str, toMerge.Str) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /issue38_test.go: -------------------------------------------------------------------------------- 1 | package mergo_test 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | 7 | "dario.cat/mergo" 8 | ) 9 | 10 | type structWithoutTimePointer struct { 11 | Created time.Time 12 | } 13 | 14 | func TestIssue38Merge(t *testing.T) { 15 | dst := structWithoutTimePointer{ 16 | time.Now(), 17 | } 18 | 19 | expected := time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC) 20 | src := structWithoutTimePointer{ 21 | expected, 22 | } 23 | 24 | if err := mergo.Merge(&dst, src); err != nil { 25 | t.Errorf("Error while merging %s", err) 26 | } 27 | 28 | if dst.Created.Equal(src.Created) { 29 | t.Errorf("Created merged unexpectedly: dst.Created(%v) == src.Created(%v)", dst.Created, src.Created) 30 | } 31 | } 32 | 33 | func TestIssue38MergeEmptyStruct(t *testing.T) { 34 | dst := structWithoutTimePointer{} 35 | 36 | expected := time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC) 37 | src := structWithoutTimePointer{ 38 | expected, 39 | } 40 | 41 | if err := mergo.Merge(&dst, src); err != nil { 42 | t.Errorf("Error while merging %s", err) 43 | } 44 | 45 | if dst.Created.Equal(src.Created) { 46 | t.Errorf("Created merged unexpectedly: dst.Created(%v) == src.Created(%v)", dst.Created, src.Created) 47 | } 48 | } 49 | 50 | func TestIssue38MergeWithOverwrite(t *testing.T) { 51 | dst := structWithoutTimePointer{ 52 | time.Now(), 53 | } 54 | 55 | expected := time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC) 56 | src := structWithoutTimePointer{ 57 | expected, 58 | } 59 | 60 | if err := mergo.MergeWithOverwrite(&dst, src); err != nil { 61 | t.Errorf("Error while merging %s", err) 62 | } 63 | 64 | if !dst.Created.Equal(src.Created) { 65 | t.Errorf("Created not merged in properly: dst.Created(%v) != src.Created(%v)", dst.Created, src.Created) 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /issue50_test.go: -------------------------------------------------------------------------------- 1 | package mergo_test 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | 7 | "dario.cat/mergo" 8 | ) 9 | 10 | type testStruct struct { 11 | time.Duration 12 | } 13 | 14 | func TestIssue50Merge(t *testing.T) { 15 | to := testStruct{} 16 | from := testStruct{} 17 | 18 | if err := mergo.Merge(&to, from); err != nil { 19 | t.Fail() 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /issue52_test.go: -------------------------------------------------------------------------------- 1 | package mergo_test 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | "time" 7 | 8 | "dario.cat/mergo" 9 | ) 10 | 11 | type structWithTime struct { 12 | Birth time.Time 13 | } 14 | 15 | type timeTransfomer struct { 16 | overwrite bool 17 | } 18 | 19 | func (t timeTransfomer) Transformer(typ reflect.Type) func(dst, src reflect.Value) error { 20 | if typ == reflect.TypeOf(time.Time{}) { 21 | return func(dst, src reflect.Value) error { 22 | if dst.CanSet() { 23 | if t.overwrite { 24 | isZero := src.MethodByName("IsZero") 25 | 26 | result := isZero.Call([]reflect.Value{}) 27 | if !result[0].Bool() { 28 | dst.Set(src) 29 | } 30 | } else { 31 | isZero := dst.MethodByName("IsZero") 32 | 33 | result := isZero.Call([]reflect.Value{}) 34 | if result[0].Bool() { 35 | dst.Set(src) 36 | } 37 | } 38 | } 39 | return nil 40 | } 41 | } 42 | return nil 43 | } 44 | 45 | func TestOverwriteZeroSrcTime(t *testing.T) { 46 | now := time.Now() 47 | dst := structWithTime{now} 48 | src := structWithTime{} 49 | 50 | if err := mergo.MergeWithOverwrite(&dst, src); err != nil { 51 | t.FailNow() 52 | } 53 | 54 | if !dst.Birth.IsZero() { 55 | t.Errorf("dst should have been overwritten: dst.Birth(%v) != now(%v)", dst.Birth, now) 56 | } 57 | } 58 | 59 | func TestOverwriteZeroSrcTimeWithTransformer(t *testing.T) { 60 | now := time.Now() 61 | dst := structWithTime{now} 62 | src := structWithTime{} 63 | 64 | if err := mergo.MergeWithOverwrite(&dst, src, mergo.WithTransformers(timeTransfomer{true})); err != nil { 65 | t.FailNow() 66 | } 67 | 68 | if dst.Birth.IsZero() { 69 | t.Errorf("dst should not have been overwritten: dst.Birth(%v) != now(%v)", dst.Birth, now) 70 | } 71 | } 72 | 73 | func TestOverwriteZeroDstTime(t *testing.T) { 74 | now := time.Now() 75 | dst := structWithTime{} 76 | src := structWithTime{now} 77 | 78 | if err := mergo.MergeWithOverwrite(&dst, src); err != nil { 79 | t.FailNow() 80 | } 81 | 82 | if dst.Birth.IsZero() { 83 | t.Errorf("dst should have been overwritten: dst.Birth(%v) != zero(%v)", dst.Birth, time.Time{}) 84 | } 85 | } 86 | 87 | func TestZeroDstTime(t *testing.T) { 88 | now := time.Now() 89 | dst := structWithTime{} 90 | src := structWithTime{now} 91 | 92 | if err := mergo.Merge(&dst, src); err != nil { 93 | t.FailNow() 94 | } 95 | 96 | if !dst.Birth.IsZero() { 97 | t.Errorf("dst should not have been overwritten: dst.Birth(%v) != zero(%v)", dst.Birth, time.Time{}) 98 | } 99 | } 100 | 101 | func TestZeroDstTimeWithTransformer(t *testing.T) { 102 | now := time.Now() 103 | dst := structWithTime{} 104 | src := structWithTime{now} 105 | 106 | if err := mergo.Merge(&dst, src, mergo.WithTransformers(timeTransfomer{})); err != nil { 107 | t.FailNow() 108 | } 109 | 110 | if dst.Birth.IsZero() { 111 | t.Errorf("dst should have been overwritten: dst.Birth(%v) != now(%v)", dst.Birth, now) 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /issue61_test.go: -------------------------------------------------------------------------------- 1 | package mergo_test 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | 7 | "dario.cat/mergo" 8 | ) 9 | 10 | func TestIssue61MergeNilMap(t *testing.T) { 11 | type T struct { 12 | I map[string][]string 13 | } 14 | t1 := T{} 15 | t2 := T{I: map[string][]string{"hi": {"there"}}} 16 | 17 | if err := mergo.Merge(&t1, t2); err != nil { 18 | t.Fail() 19 | } 20 | 21 | if !reflect.DeepEqual(t2, T{I: map[string][]string{"hi": {"there"}}}) { 22 | t.FailNow() 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /issue64_test.go: -------------------------------------------------------------------------------- 1 | package mergo_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "dario.cat/mergo" 7 | ) 8 | 9 | type Student struct { 10 | Name string 11 | Books []string 12 | } 13 | 14 | type issue64TestData struct { 15 | S1 Student 16 | S2 Student 17 | ExpectedSlice []string 18 | } 19 | 20 | func issue64Data() []issue64TestData { 21 | return []issue64TestData{ 22 | {Student{"Jack", []string{"a", "B"}}, Student{"Tom", []string{"1"}}, []string{"a", "B"}}, 23 | {Student{"Jack", []string{"a", "B"}}, Student{"Tom", []string{}}, []string{"a", "B"}}, 24 | {Student{"Jack", []string{}}, Student{"Tom", []string{"1"}}, []string{"1"}}, 25 | {Student{"Jack", []string{}}, Student{"Tom", []string{}}, []string{}}, 26 | } 27 | } 28 | 29 | func TestIssue64MergeSliceWithOverride(t *testing.T) { 30 | for _, data := range issue64Data() { 31 | err := mergo.Merge(&data.S2, data.S1, mergo.WithOverride) 32 | if err != nil { 33 | t.Errorf("Error while merging %s", err) 34 | } 35 | 36 | if len(data.S2.Books) != len(data.ExpectedSlice) { 37 | t.Errorf("Got %d elements in slice, but expected %d", len(data.S2.Books), len(data.ExpectedSlice)) 38 | } 39 | 40 | for i, val := range data.S2.Books { 41 | if val != data.ExpectedSlice[i] { 42 | t.Errorf("Expected %s, but got %s while merging slice with override", data.ExpectedSlice[i], val) 43 | } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /issue66_test.go: -------------------------------------------------------------------------------- 1 | package mergo_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "dario.cat/mergo" 7 | ) 8 | 9 | type PrivateSliceTest66 struct { 10 | PublicStrings []string 11 | privateStrings []string 12 | } 13 | 14 | func TestPrivateSlice(t *testing.T) { 15 | p1 := PrivateSliceTest66{ 16 | PublicStrings: []string{"one", "two", "three"}, 17 | privateStrings: []string{"four", "five"}, 18 | } 19 | p2 := PrivateSliceTest66{ 20 | PublicStrings: []string{"six", "seven"}, 21 | } 22 | 23 | if err := mergo.Merge(&p1, p2); err != nil { 24 | t.Errorf("Error during the merge: %v", err) 25 | } 26 | 27 | if len(p1.PublicStrings) != 3 { 28 | t.Error("3 elements should be in 'PublicStrings' field, when no append") 29 | } 30 | 31 | if len(p1.privateStrings) != 2 { 32 | t.Error("2 elements should be in 'privateStrings' field") 33 | } 34 | } 35 | 36 | func TestPrivateSliceWithAppendSlice(t *testing.T) { 37 | p1 := PrivateSliceTest66{ 38 | PublicStrings: []string{"one", "two", "three"}, 39 | privateStrings: []string{"four", "five"}, 40 | } 41 | p2 := PrivateSliceTest66{ 42 | PublicStrings: []string{"six", "seven"}, 43 | } 44 | 45 | if err := mergo.Merge(&p1, p2, mergo.WithAppendSlice); err != nil { 46 | t.Errorf("Error during the merge: %v", err) 47 | } 48 | 49 | if len(p1.PublicStrings) != 5 { 50 | t.Error("5 elements should be in 'PublicStrings' field") 51 | } 52 | 53 | if len(p1.privateStrings) != 2 { 54 | t.Error("2 elements should be in 'privateStrings' field") 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /issue83_test.go: -------------------------------------------------------------------------------- 1 | package mergo_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "dario.cat/mergo" 7 | ) 8 | 9 | type issue83My struct { 10 | Data []int 11 | } 12 | 13 | func TestIssue83(t *testing.T) { 14 | dst := issue83My{Data: []int{1, 2, 3}} 15 | new := issue83My{} 16 | if err := mergo.Merge(&dst, new, mergo.WithOverwriteWithEmptyValue); err != nil { 17 | t.Error(err) 18 | } 19 | if len(dst.Data) > 0 { 20 | t.Errorf("expected empty slice, got %v", dst.Data) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /issue84_test.go: -------------------------------------------------------------------------------- 1 | package mergo_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "dario.cat/mergo" 7 | ) 8 | 9 | type DstStructIssue84 struct { 10 | A int 11 | B int 12 | C int 13 | } 14 | 15 | type DstNestedStructIssue84 struct { 16 | A struct { 17 | A int 18 | B int 19 | C int 20 | } 21 | B int 22 | C int 23 | } 24 | 25 | func TestIssue84MergeMapWithNilValueToStructWithOverride(t *testing.T) { 26 | p1 := DstStructIssue84{ 27 | A: 0, B: 1, C: 2, 28 | } 29 | p2 := map[string]interface{}{ 30 | "A": 3, "B": 4, "C": 0, 31 | } 32 | 33 | if err := mergo.Map(&p1, p2, mergo.WithOverride); err != nil { 34 | t.Errorf("Error during the merge: %v", err) 35 | } 36 | 37 | if p1.C != 0 { 38 | t.Error("C field should become '0'") 39 | } 40 | } 41 | 42 | func TestIssue84MergeMapWithoutKeyExistsToStructWithOverride(t *testing.T) { 43 | p1 := DstStructIssue84{ 44 | A: 0, B: 1, C: 2, 45 | } 46 | p2 := map[string]interface{}{ 47 | "A": 3, "B": 4, 48 | } 49 | 50 | if err := mergo.Map(&p1, p2, mergo.WithOverride); err != nil { 51 | t.Errorf("Error during the merge: %v", err) 52 | } 53 | 54 | if p1.C != 2 { 55 | t.Error("C field should be '2'") 56 | } 57 | } 58 | 59 | func TestIssue84MergeNestedMapWithNilValueToStructWithOverride(t *testing.T) { 60 | p1 := DstNestedStructIssue84{ 61 | A: struct { 62 | A int 63 | B int 64 | C int 65 | }{A: 1, B: 2, C: 0}, 66 | B: 0, 67 | C: 2, 68 | } 69 | p2 := map[string]interface{}{ 70 | "A": map[string]interface{}{ 71 | "A": 0, "B": 0, "C": 5, 72 | }, "B": 4, "C": 0, 73 | } 74 | 75 | if err := mergo.Map(&p1, p2, mergo.WithOverride); err != nil { 76 | t.Errorf("Error during the merge: %v", err) 77 | } 78 | 79 | if p1.B != 4 { 80 | t.Error("A.C field should become '4'") 81 | } 82 | 83 | if p1.A.C != 5 { 84 | t.Error("A.C field should become '5'") 85 | } 86 | 87 | if p1.A.B != 0 || p1.A.A != 0 { 88 | t.Error("A.A and A.B field should become '0'") 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /issue89_test.go: -------------------------------------------------------------------------------- 1 | package mergo_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "dario.cat/mergo" 7 | ) 8 | 9 | func TestIssue89Boolean(t *testing.T) { 10 | type Foo struct { 11 | Bar bool `json:"bar"` 12 | } 13 | 14 | src := Foo{Bar: true} 15 | dst := Foo{Bar: false} 16 | 17 | if err := mergo.Merge(&dst, src); err != nil { 18 | t.Error(err) 19 | } 20 | if dst.Bar == false { 21 | t.Errorf("expected true, got false") 22 | } 23 | } 24 | 25 | func TestIssue89MergeWithEmptyValue(t *testing.T) { 26 | p1 := map[string]interface{}{ 27 | "A": 3, "B": "note", "C": true, 28 | } 29 | p2 := map[string]interface{}{ 30 | "B": "", "C": false, 31 | } 32 | if err := mergo.Merge(&p1, p2, mergo.WithOverwriteWithEmptyValue); err != nil { 33 | t.Error(err) 34 | } 35 | testCases := []struct { 36 | expected interface{} 37 | key string 38 | }{ 39 | { 40 | "", 41 | "B", 42 | }, 43 | { 44 | false, 45 | "C", 46 | }, 47 | } 48 | for _, tC := range testCases { 49 | if p1[tC.key] != tC.expected { 50 | t.Errorf("expected %v in p1[%q], got %v", tC.expected, tC.key, p1[tC.key]) 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /issue90_test.go: -------------------------------------------------------------------------------- 1 | package mergo_test 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | 7 | "dario.cat/mergo" 8 | ) 9 | 10 | type structWithStringMap struct { 11 | Data map[string]string 12 | } 13 | 14 | func TestIssue90(t *testing.T) { 15 | dst := map[string]structWithStringMap{ 16 | "struct": { 17 | Data: nil, 18 | }, 19 | } 20 | src := map[string]structWithStringMap{ 21 | "struct": { 22 | Data: map[string]string{ 23 | "foo": "bar", 24 | }, 25 | }, 26 | } 27 | expected := map[string]structWithStringMap{ 28 | "struct": { 29 | Data: map[string]string{ 30 | "foo": "bar", 31 | }, 32 | }, 33 | } 34 | 35 | err := mergo.Merge(&dst, src, mergo.WithOverride) 36 | if err != nil { 37 | t.Errorf("unexpected error %v", err) 38 | } 39 | 40 | if !reflect.DeepEqual(dst, expected) { 41 | t.Errorf("expected: %#v\ngot: %#v", expected, dst) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /issueXXX_test.go: -------------------------------------------------------------------------------- 1 | package mergo_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "dario.cat/mergo" 7 | ) 8 | 9 | var testDataS = []struct { 10 | S1 Student 11 | S2 Student 12 | ExpectedSlice []string 13 | }{ 14 | {Student{"Jack", []string{"a", "B"}}, Student{"Tom", []string{"1"}}, []string{"1", "a", "B"}}, 15 | {Student{"Jack", []string{"a", "B"}}, Student{"Tom", []string{}}, []string{"a", "B"}}, 16 | {Student{"Jack", []string{}}, Student{"Tom", []string{"1"}}, []string{"1"}}, 17 | {Student{"Jack", []string{}}, Student{"Tom", []string{}}, []string{}}, 18 | } 19 | 20 | func TestMergeSliceWithOverrideWithAppendSlice(t *testing.T) { 21 | for _, data := range testDataS { 22 | err := mergo.Merge(&data.S2, data.S1, mergo.WithOverride, mergo.WithAppendSlice) 23 | if err != nil { 24 | t.Errorf("Error while merging %s", err) 25 | } 26 | 27 | if len(data.S2.Books) != len(data.ExpectedSlice) { 28 | t.Errorf("Got %d elements in slice, but expected %d", len(data.S2.Books), len(data.ExpectedSlice)) 29 | } 30 | 31 | for i, val := range data.S2.Books { 32 | if val != data.ExpectedSlice[i] { 33 | t.Errorf("Expected %s, but got %s while merging slice with override", data.ExpectedSlice[i], val) 34 | } 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /map.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 Dario Castañé. All rights reserved. 2 | // Copyright 2009 The Go Authors. All rights reserved. 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file. 5 | 6 | // Based on src/pkg/reflect/deepequal.go from official 7 | // golang's stdlib. 8 | 9 | package mergo 10 | 11 | import ( 12 | "fmt" 13 | "reflect" 14 | "unicode" 15 | "unicode/utf8" 16 | ) 17 | 18 | func changeInitialCase(s string, mapper func(rune) rune) string { 19 | if s == "" { 20 | return s 21 | } 22 | r, n := utf8.DecodeRuneInString(s) 23 | return string(mapper(r)) + s[n:] 24 | } 25 | 26 | func isExported(field reflect.StructField) bool { 27 | r, _ := utf8.DecodeRuneInString(field.Name) 28 | return r >= 'A' && r <= 'Z' 29 | } 30 | 31 | // Traverses recursively both values, assigning src's fields values to dst. 32 | // The map argument tracks comparisons that have already been seen, which allows 33 | // short circuiting on recursive types. 34 | func deepMap(dst, src reflect.Value, visited map[uintptr]*visit, depth int, config *Config) (err error) { 35 | overwrite := config.Overwrite 36 | if dst.CanAddr() { 37 | addr := dst.UnsafeAddr() 38 | h := 17 * addr 39 | seen := visited[h] 40 | typ := dst.Type() 41 | for p := seen; p != nil; p = p.next { 42 | if p.ptr == addr && p.typ == typ { 43 | return nil 44 | } 45 | } 46 | // Remember, remember... 47 | visited[h] = &visit{typ, seen, addr} 48 | } 49 | zeroValue := reflect.Value{} 50 | switch dst.Kind() { 51 | case reflect.Map: 52 | dstMap := dst.Interface().(map[string]interface{}) 53 | for i, n := 0, src.NumField(); i < n; i++ { 54 | srcType := src.Type() 55 | field := srcType.Field(i) 56 | if !isExported(field) { 57 | continue 58 | } 59 | fieldName := field.Name 60 | fieldName = changeInitialCase(fieldName, unicode.ToLower) 61 | if _, ok := dstMap[fieldName]; !ok || (!isEmptyValue(reflect.ValueOf(src.Field(i).Interface()), !config.ShouldNotDereference) && overwrite) || config.overwriteWithEmptyValue { 62 | dstMap[fieldName] = src.Field(i).Interface() 63 | } 64 | } 65 | case reflect.Ptr: 66 | if dst.IsNil() { 67 | v := reflect.New(dst.Type().Elem()) 68 | dst.Set(v) 69 | } 70 | dst = dst.Elem() 71 | fallthrough 72 | case reflect.Struct: 73 | srcMap := src.Interface().(map[string]interface{}) 74 | for key := range srcMap { 75 | config.overwriteWithEmptyValue = true 76 | srcValue := srcMap[key] 77 | fieldName := changeInitialCase(key, unicode.ToUpper) 78 | dstElement := dst.FieldByName(fieldName) 79 | if dstElement == zeroValue { 80 | // We discard it because the field doesn't exist. 81 | continue 82 | } 83 | srcElement := reflect.ValueOf(srcValue) 84 | dstKind := dstElement.Kind() 85 | srcKind := srcElement.Kind() 86 | if srcKind == reflect.Ptr && dstKind != reflect.Ptr { 87 | srcElement = srcElement.Elem() 88 | srcKind = reflect.TypeOf(srcElement.Interface()).Kind() 89 | } else if dstKind == reflect.Ptr { 90 | // Can this work? I guess it can't. 91 | if srcKind != reflect.Ptr && srcElement.CanAddr() { 92 | srcPtr := srcElement.Addr() 93 | srcElement = reflect.ValueOf(srcPtr) 94 | srcKind = reflect.Ptr 95 | } 96 | } 97 | 98 | if !srcElement.IsValid() { 99 | continue 100 | } 101 | if srcKind == dstKind { 102 | if err = deepMerge(dstElement, srcElement, visited, depth+1, config); err != nil { 103 | return 104 | } 105 | } else if dstKind == reflect.Interface && dstElement.Kind() == reflect.Interface { 106 | if err = deepMerge(dstElement, srcElement, visited, depth+1, config); err != nil { 107 | return 108 | } 109 | } else if srcKind == reflect.Map { 110 | if err = deepMap(dstElement, srcElement, visited, depth+1, config); err != nil { 111 | return 112 | } 113 | } else { 114 | return fmt.Errorf("type mismatch on %s field: found %v, expected %v", fieldName, srcKind, dstKind) 115 | } 116 | } 117 | } 118 | return 119 | } 120 | 121 | // Map sets fields' values in dst from src. 122 | // src can be a map with string keys or a struct. dst must be the opposite: 123 | // if src is a map, dst must be a valid pointer to struct. If src is a struct, 124 | // dst must be map[string]interface{}. 125 | // It won't merge unexported (private) fields and will do recursively 126 | // any exported field. 127 | // If dst is a map, keys will be src fields' names in lower camel case. 128 | // Missing key in src that doesn't match a field in dst will be skipped. This 129 | // doesn't apply if dst is a map. 130 | // This is separated method from Merge because it is cleaner and it keeps sane 131 | // semantics: merging equal types, mapping different (restricted) types. 132 | func Map(dst, src interface{}, opts ...func(*Config)) error { 133 | return _map(dst, src, opts...) 134 | } 135 | 136 | // MapWithOverwrite will do the same as Map except that non-empty dst attributes will be overridden by 137 | // non-empty src attribute values. 138 | // Deprecated: Use Map(…) with WithOverride 139 | func MapWithOverwrite(dst, src interface{}, opts ...func(*Config)) error { 140 | return _map(dst, src, append(opts, WithOverride)...) 141 | } 142 | 143 | func _map(dst, src interface{}, opts ...func(*Config)) error { 144 | if dst != nil && reflect.ValueOf(dst).Kind() != reflect.Ptr { 145 | return ErrNonPointerArgument 146 | } 147 | var ( 148 | vDst, vSrc reflect.Value 149 | err error 150 | ) 151 | config := &Config{} 152 | 153 | for _, opt := range opts { 154 | opt(config) 155 | } 156 | 157 | if vDst, vSrc, err = resolveValues(dst, src); err != nil { 158 | return err 159 | } 160 | // To be friction-less, we redirect equal-type arguments 161 | // to deepMerge. Only because arguments can be anything. 162 | if vSrc.Kind() == vDst.Kind() { 163 | return deepMerge(vDst, vSrc, make(map[uintptr]*visit), 0, config) 164 | } 165 | switch vSrc.Kind() { 166 | case reflect.Struct: 167 | if vDst.Kind() != reflect.Map { 168 | return ErrExpectedMapAsDestination 169 | } 170 | case reflect.Map: 171 | if vDst.Kind() != reflect.Struct { 172 | return ErrExpectedStructAsDestination 173 | } 174 | default: 175 | return ErrNotSupported 176 | } 177 | return deepMap(vDst, vSrc, make(map[uintptr]*visit), 0, config) 178 | } 179 | -------------------------------------------------------------------------------- /merge.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 Dario Castañé. All rights reserved. 2 | // Copyright 2009 The Go Authors. All rights reserved. 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file. 5 | 6 | // Based on src/pkg/reflect/deepequal.go from official 7 | // golang's stdlib. 8 | 9 | package mergo 10 | 11 | import ( 12 | "fmt" 13 | "reflect" 14 | ) 15 | 16 | func hasMergeableFields(dst reflect.Value) (exported bool) { 17 | for i, n := 0, dst.NumField(); i < n; i++ { 18 | field := dst.Type().Field(i) 19 | if field.Anonymous && dst.Field(i).Kind() == reflect.Struct { 20 | exported = exported || hasMergeableFields(dst.Field(i)) 21 | } else if isExportedComponent(&field) { 22 | exported = exported || len(field.PkgPath) == 0 23 | } 24 | } 25 | return 26 | } 27 | 28 | func isExportedComponent(field *reflect.StructField) bool { 29 | pkgPath := field.PkgPath 30 | if len(pkgPath) > 0 { 31 | return false 32 | } 33 | c := field.Name[0] 34 | if 'a' <= c && c <= 'z' || c == '_' { 35 | return false 36 | } 37 | return true 38 | } 39 | 40 | type Config struct { 41 | Transformers Transformers 42 | Overwrite bool 43 | ShouldNotDereference bool 44 | AppendSlice bool 45 | TypeCheck bool 46 | overwriteWithEmptyValue bool 47 | overwriteSliceWithEmptyValue bool 48 | sliceDeepCopy bool 49 | } 50 | 51 | type Transformers interface { 52 | Transformer(reflect.Type) func(dst, src reflect.Value) error 53 | } 54 | 55 | // Traverses recursively both values, assigning src's fields values to dst. 56 | // The map argument tracks comparisons that have already been seen, which allows 57 | // short circuiting on recursive types. 58 | func deepMerge(dst, src reflect.Value, visited map[uintptr]*visit, depth int, config *Config) (err error) { 59 | overwrite := config.Overwrite 60 | typeCheck := config.TypeCheck 61 | overwriteWithEmptySrc := config.overwriteWithEmptyValue 62 | overwriteSliceWithEmptySrc := config.overwriteSliceWithEmptyValue 63 | sliceDeepCopy := config.sliceDeepCopy 64 | 65 | if !src.IsValid() { 66 | return 67 | } 68 | if dst.CanAddr() { 69 | addr := dst.UnsafeAddr() 70 | h := 17 * addr 71 | seen := visited[h] 72 | typ := dst.Type() 73 | for p := seen; p != nil; p = p.next { 74 | if p.ptr == addr && p.typ == typ { 75 | return nil 76 | } 77 | } 78 | // Remember, remember... 79 | visited[h] = &visit{typ, seen, addr} 80 | } 81 | 82 | if config.Transformers != nil && !isReflectNil(dst) && dst.IsValid() { 83 | if fn := config.Transformers.Transformer(dst.Type()); fn != nil { 84 | err = fn(dst, src) 85 | return 86 | } 87 | } 88 | 89 | switch dst.Kind() { 90 | case reflect.Struct: 91 | if hasMergeableFields(dst) { 92 | for i, n := 0, dst.NumField(); i < n; i++ { 93 | if err = deepMerge(dst.Field(i), src.Field(i), visited, depth+1, config); err != nil { 94 | return 95 | } 96 | } 97 | } else { 98 | if dst.CanSet() && (isReflectNil(dst) || overwrite) && (!isEmptyValue(src, !config.ShouldNotDereference) || overwriteWithEmptySrc) { 99 | dst.Set(src) 100 | } 101 | } 102 | case reflect.Map: 103 | if dst.IsNil() && !src.IsNil() { 104 | if dst.CanSet() { 105 | dst.Set(reflect.MakeMap(dst.Type())) 106 | } else { 107 | return 108 | } 109 | } 110 | 111 | if src.Kind() != reflect.Map { 112 | if overwrite && dst.CanSet() { 113 | dst.Set(src) 114 | } 115 | return 116 | } 117 | 118 | for _, key := range src.MapKeys() { 119 | srcElement := src.MapIndex(key) 120 | if !srcElement.IsValid() { 121 | continue 122 | } 123 | dstElement := dst.MapIndex(key) 124 | switch srcElement.Kind() { 125 | case reflect.Chan, reflect.Func, reflect.Map, reflect.Interface, reflect.Slice: 126 | if srcElement.IsNil() { 127 | if overwrite { 128 | dst.SetMapIndex(key, srcElement) 129 | } 130 | continue 131 | } 132 | fallthrough 133 | default: 134 | if !srcElement.CanInterface() { 135 | continue 136 | } 137 | switch reflect.TypeOf(srcElement.Interface()).Kind() { 138 | case reflect.Struct: 139 | fallthrough 140 | case reflect.Ptr: 141 | fallthrough 142 | case reflect.Map: 143 | srcMapElm := srcElement 144 | dstMapElm := dstElement 145 | if srcMapElm.CanInterface() { 146 | srcMapElm = reflect.ValueOf(srcMapElm.Interface()) 147 | if dstMapElm.IsValid() { 148 | dstMapElm = reflect.ValueOf(dstMapElm.Interface()) 149 | } 150 | } 151 | if err = deepMerge(dstMapElm, srcMapElm, visited, depth+1, config); err != nil { 152 | return 153 | } 154 | case reflect.Slice: 155 | srcSlice := reflect.ValueOf(srcElement.Interface()) 156 | 157 | var dstSlice reflect.Value 158 | if !dstElement.IsValid() || dstElement.IsNil() { 159 | dstSlice = reflect.MakeSlice(srcSlice.Type(), 0, srcSlice.Len()) 160 | } else { 161 | dstSlice = reflect.ValueOf(dstElement.Interface()) 162 | } 163 | 164 | if (!isEmptyValue(src, !config.ShouldNotDereference) || overwriteWithEmptySrc || overwriteSliceWithEmptySrc) && (overwrite || isEmptyValue(dst, !config.ShouldNotDereference)) && !config.AppendSlice && !sliceDeepCopy { 165 | if typeCheck && srcSlice.Type() != dstSlice.Type() { 166 | return fmt.Errorf("cannot override two slices with different type (%s, %s)", srcSlice.Type(), dstSlice.Type()) 167 | } 168 | dstSlice = srcSlice 169 | } else if config.AppendSlice { 170 | if srcSlice.Type() != dstSlice.Type() { 171 | return fmt.Errorf("cannot append two slices with different type (%s, %s)", srcSlice.Type(), dstSlice.Type()) 172 | } 173 | dstSlice = reflect.AppendSlice(dstSlice, srcSlice) 174 | } else if sliceDeepCopy { 175 | i := 0 176 | for ; i < srcSlice.Len() && i < dstSlice.Len(); i++ { 177 | srcElement := srcSlice.Index(i) 178 | dstElement := dstSlice.Index(i) 179 | 180 | if srcElement.CanInterface() { 181 | srcElement = reflect.ValueOf(srcElement.Interface()) 182 | } 183 | if dstElement.CanInterface() { 184 | dstElement = reflect.ValueOf(dstElement.Interface()) 185 | } 186 | 187 | if err = deepMerge(dstElement, srcElement, visited, depth+1, config); err != nil { 188 | return 189 | } 190 | } 191 | 192 | } 193 | dst.SetMapIndex(key, dstSlice) 194 | } 195 | } 196 | 197 | if dstElement.IsValid() && !isEmptyValue(dstElement, !config.ShouldNotDereference) { 198 | if reflect.TypeOf(srcElement.Interface()).Kind() == reflect.Slice { 199 | continue 200 | } 201 | if reflect.TypeOf(srcElement.Interface()).Kind() == reflect.Map && reflect.TypeOf(dstElement.Interface()).Kind() == reflect.Map { 202 | continue 203 | } 204 | } 205 | 206 | if srcElement.IsValid() && ((srcElement.Kind() != reflect.Ptr && overwrite) || !dstElement.IsValid() || isEmptyValue(dstElement, !config.ShouldNotDereference)) { 207 | if dst.IsNil() { 208 | dst.Set(reflect.MakeMap(dst.Type())) 209 | } 210 | dst.SetMapIndex(key, srcElement) 211 | } 212 | } 213 | 214 | // Ensure that all keys in dst are deleted if they are not in src. 215 | if overwriteWithEmptySrc { 216 | for _, key := range dst.MapKeys() { 217 | srcElement := src.MapIndex(key) 218 | if !srcElement.IsValid() { 219 | dst.SetMapIndex(key, reflect.Value{}) 220 | } 221 | } 222 | } 223 | case reflect.Slice: 224 | if !dst.CanSet() { 225 | break 226 | } 227 | if (!isEmptyValue(src, !config.ShouldNotDereference) || overwriteWithEmptySrc || overwriteSliceWithEmptySrc) && (overwrite || isEmptyValue(dst, !config.ShouldNotDereference)) && !config.AppendSlice && !sliceDeepCopy { 228 | dst.Set(src) 229 | } else if config.AppendSlice { 230 | if src.Type() != dst.Type() { 231 | return fmt.Errorf("cannot append two slice with different type (%s, %s)", src.Type(), dst.Type()) 232 | } 233 | dst.Set(reflect.AppendSlice(dst, src)) 234 | } else if sliceDeepCopy { 235 | for i := 0; i < src.Len() && i < dst.Len(); i++ { 236 | srcElement := src.Index(i) 237 | dstElement := dst.Index(i) 238 | if srcElement.CanInterface() { 239 | srcElement = reflect.ValueOf(srcElement.Interface()) 240 | } 241 | if dstElement.CanInterface() { 242 | dstElement = reflect.ValueOf(dstElement.Interface()) 243 | } 244 | 245 | if err = deepMerge(dstElement, srcElement, visited, depth+1, config); err != nil { 246 | return 247 | } 248 | } 249 | } 250 | case reflect.Ptr: 251 | fallthrough 252 | case reflect.Interface: 253 | if isReflectNil(src) { 254 | if overwriteWithEmptySrc && dst.CanSet() && src.Type().AssignableTo(dst.Type()) { 255 | dst.Set(src) 256 | } 257 | break 258 | } 259 | 260 | if src.Kind() != reflect.Interface { 261 | if dst.IsNil() || (src.Kind() != reflect.Ptr && overwrite) { 262 | if dst.CanSet() && (overwrite || isEmptyValue(dst, !config.ShouldNotDereference)) { 263 | dst.Set(src) 264 | } 265 | } else if src.Kind() == reflect.Ptr { 266 | if !config.ShouldNotDereference { 267 | if err = deepMerge(dst.Elem(), src.Elem(), visited, depth+1, config); err != nil { 268 | return 269 | } 270 | } else if src.Elem().Kind() != reflect.Struct { 271 | if overwriteWithEmptySrc || (overwrite && !src.IsNil()) || dst.IsNil() { 272 | dst.Set(src) 273 | } 274 | } 275 | } else if dst.Elem().Type() == src.Type() { 276 | if err = deepMerge(dst.Elem(), src, visited, depth+1, config); err != nil { 277 | return 278 | } 279 | } else { 280 | return ErrDifferentArgumentsTypes 281 | } 282 | break 283 | } 284 | 285 | if dst.IsNil() || overwrite { 286 | if dst.CanSet() && (overwrite || isEmptyValue(dst, !config.ShouldNotDereference)) { 287 | dst.Set(src) 288 | } 289 | break 290 | } 291 | 292 | if dst.Elem().Kind() == src.Elem().Kind() { 293 | if err = deepMerge(dst.Elem(), src.Elem(), visited, depth+1, config); err != nil { 294 | return 295 | } 296 | break 297 | } 298 | default: 299 | mustSet := (isEmptyValue(dst, !config.ShouldNotDereference) || overwrite) && (!isEmptyValue(src, !config.ShouldNotDereference) || overwriteWithEmptySrc) 300 | if mustSet { 301 | if dst.CanSet() { 302 | dst.Set(src) 303 | } 304 | } 305 | } 306 | 307 | return 308 | } 309 | 310 | // Merge will fill any empty for value type attributes on the dst struct using corresponding 311 | // src attributes if they themselves are not empty. dst and src must be valid same-type structs 312 | // and dst must be a pointer to struct. 313 | // It won't merge unexported (private) fields and will do recursively any exported field. 314 | func Merge(dst, src interface{}, opts ...func(*Config)) error { 315 | return merge(dst, src, opts...) 316 | } 317 | 318 | // MergeWithOverwrite will do the same as Merge except that non-empty dst attributes will be overridden by 319 | // non-empty src attribute values. 320 | // Deprecated: use Merge(…) with WithOverride 321 | func MergeWithOverwrite(dst, src interface{}, opts ...func(*Config)) error { 322 | return merge(dst, src, append(opts, WithOverride)...) 323 | } 324 | 325 | // WithTransformers adds transformers to merge, allowing to customize the merging of some types. 326 | func WithTransformers(transformers Transformers) func(*Config) { 327 | return func(config *Config) { 328 | config.Transformers = transformers 329 | } 330 | } 331 | 332 | // WithOverride will make merge override non-empty dst attributes with non-empty src attributes values. 333 | func WithOverride(config *Config) { 334 | config.Overwrite = true 335 | } 336 | 337 | // WithOverwriteWithEmptyValue will make merge override non empty dst attributes with empty src attributes values. 338 | func WithOverwriteWithEmptyValue(config *Config) { 339 | config.Overwrite = true 340 | config.overwriteWithEmptyValue = true 341 | } 342 | 343 | // WithOverrideEmptySlice will make merge override empty dst slice with empty src slice. 344 | func WithOverrideEmptySlice(config *Config) { 345 | config.overwriteSliceWithEmptyValue = true 346 | } 347 | 348 | // WithoutDereference prevents dereferencing pointers when evaluating whether they are empty 349 | // (i.e. a non-nil pointer is never considered empty). 350 | func WithoutDereference(config *Config) { 351 | config.ShouldNotDereference = true 352 | } 353 | 354 | // WithAppendSlice will make merge append slices instead of overwriting it. 355 | func WithAppendSlice(config *Config) { 356 | config.AppendSlice = true 357 | } 358 | 359 | // WithTypeCheck will make merge check types while overwriting it (must be used with WithOverride). 360 | func WithTypeCheck(config *Config) { 361 | config.TypeCheck = true 362 | } 363 | 364 | // WithSliceDeepCopy will merge slice element one by one with Overwrite flag. 365 | func WithSliceDeepCopy(config *Config) { 366 | config.sliceDeepCopy = true 367 | config.Overwrite = true 368 | } 369 | 370 | func merge(dst, src interface{}, opts ...func(*Config)) error { 371 | if dst != nil && reflect.ValueOf(dst).Kind() != reflect.Ptr { 372 | return ErrNonPointerArgument 373 | } 374 | var ( 375 | vDst, vSrc reflect.Value 376 | err error 377 | ) 378 | 379 | config := &Config{} // First allocation 380 | 381 | for _, opt := range opts { 382 | opt(config) 383 | } 384 | 385 | if vDst, vSrc, err = resolveValues(dst, src); err != nil { 386 | return err 387 | } 388 | if vDst.Type() != vSrc.Type() { 389 | return ErrDifferentArgumentsTypes 390 | } 391 | return deepMerge(vDst, vSrc, make(map[uintptr]*visit), 0, config) 392 | } 393 | 394 | // IsReflectNil is the reflect value provided nil 395 | func isReflectNil(v reflect.Value) bool { 396 | k := v.Kind() 397 | switch k { 398 | case reflect.Interface, reflect.Slice, reflect.Chan, reflect.Func, reflect.Map, reflect.Ptr: 399 | // Both interface and slice are nil if first word is 0. 400 | // Both are always bigger than a word; assume flagIndir. 401 | return v.IsNil() 402 | default: 403 | return false 404 | } 405 | } 406 | -------------------------------------------------------------------------------- /merge_test.go: -------------------------------------------------------------------------------- 1 | package mergo_test 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | 7 | "dario.cat/mergo" 8 | ) 9 | 10 | type transformer struct { 11 | m map[reflect.Type]func(dst, src reflect.Value) error 12 | } 13 | 14 | func (s *transformer) Transformer(t reflect.Type) func(dst, src reflect.Value) error { 15 | if fn, ok := s.m[t]; ok { 16 | return fn 17 | } 18 | return nil 19 | } 20 | 21 | type foo struct { 22 | Bar *bar 23 | s string 24 | } 25 | 26 | type bar struct { 27 | s map[string]string 28 | i int 29 | } 30 | 31 | func TestMergeWithTransformerNilStruct(t *testing.T) { 32 | a := foo{s: "foo"} 33 | b := foo{Bar: &bar{i: 2, s: map[string]string{"foo": "bar"}}} 34 | 35 | if err := mergo.Merge(&a, &b, mergo.WithOverride, mergo.WithTransformers(&transformer{ 36 | m: map[reflect.Type]func(dst, src reflect.Value) error{ 37 | reflect.TypeOf(&bar{}): func(dst, src reflect.Value) error { 38 | // Do sthg with Elem 39 | t.Log(dst.Elem().FieldByName("i")) 40 | t.Log(src.Elem()) 41 | return nil 42 | }, 43 | }, 44 | })); err != nil { 45 | t.Error(err) 46 | } 47 | 48 | if a.s != "foo" { 49 | t.Errorf("b not merged in properly: a.s.Value(%s) != expected(%s)", a.s, "foo") 50 | } 51 | 52 | if a.Bar == nil { 53 | t.Errorf("b not merged in properly: a.Bar shouldn't be nil") 54 | } 55 | } 56 | 57 | func TestMergeNonPointer(t *testing.T) { 58 | dst := bar{ 59 | i: 1, 60 | } 61 | src := bar{ 62 | i: 2, 63 | s: map[string]string{ 64 | "a": "1", 65 | }, 66 | } 67 | want := mergo.ErrNonPointerArgument 68 | 69 | if got := mergo.Merge(dst, src); got != want { 70 | t.Errorf("want: %s, got: %s", want, got) 71 | } 72 | } 73 | 74 | func TestMapNonPointer(t *testing.T) { 75 | dst := make(map[string]bar) 76 | src := map[string]bar{ 77 | "a": { 78 | i: 2, 79 | s: map[string]string{ 80 | "a": "1", 81 | }, 82 | }, 83 | } 84 | want := mergo.ErrNonPointerArgument 85 | if got := mergo.Merge(dst, src); got != want { 86 | t.Errorf("want: %s, got: %s", want, got) 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /mergo.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 Dario Castañé. All rights reserved. 2 | // Copyright 2009 The Go Authors. All rights reserved. 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file. 5 | 6 | // Based on src/pkg/reflect/deepequal.go from official 7 | // golang's stdlib. 8 | 9 | package mergo 10 | 11 | import ( 12 | "errors" 13 | "reflect" 14 | ) 15 | 16 | // Errors reported by Mergo when it finds invalid arguments. 17 | var ( 18 | ErrNilArguments = errors.New("src and dst must not be nil") 19 | ErrDifferentArgumentsTypes = errors.New("src and dst must be of same type") 20 | ErrNotSupported = errors.New("only structs, maps, and slices are supported") 21 | ErrExpectedMapAsDestination = errors.New("dst was expected to be a map") 22 | ErrExpectedStructAsDestination = errors.New("dst was expected to be a struct") 23 | ErrNonPointerArgument = errors.New("dst must be a pointer") 24 | ) 25 | 26 | // During deepMerge, must keep track of checks that are 27 | // in progress. The comparison algorithm assumes that all 28 | // checks in progress are true when it reencounters them. 29 | // Visited are stored in a map indexed by 17 * a1 + a2; 30 | type visit struct { 31 | typ reflect.Type 32 | next *visit 33 | ptr uintptr 34 | } 35 | 36 | // From src/pkg/encoding/json/encode.go. 37 | func isEmptyValue(v reflect.Value, shouldDereference bool) bool { 38 | switch v.Kind() { 39 | case reflect.Array, reflect.Map, reflect.Slice, reflect.String: 40 | return v.Len() == 0 41 | case reflect.Bool: 42 | return !v.Bool() 43 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 44 | return v.Int() == 0 45 | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: 46 | return v.Uint() == 0 47 | case reflect.Float32, reflect.Float64: 48 | return v.Float() == 0 49 | case reflect.Interface, reflect.Ptr: 50 | if v.IsNil() { 51 | return true 52 | } 53 | if shouldDereference { 54 | return isEmptyValue(v.Elem(), shouldDereference) 55 | } 56 | return false 57 | case reflect.Func: 58 | return v.IsNil() 59 | case reflect.Invalid: 60 | return true 61 | } 62 | return false 63 | } 64 | 65 | func resolveValues(dst, src interface{}) (vDst, vSrc reflect.Value, err error) { 66 | if dst == nil || src == nil { 67 | err = ErrNilArguments 68 | return 69 | } 70 | vDst = reflect.ValueOf(dst).Elem() 71 | if vDst.Kind() != reflect.Struct && vDst.Kind() != reflect.Map && vDst.Kind() != reflect.Slice { 72 | err = ErrNotSupported 73 | return 74 | } 75 | vSrc = reflect.ValueOf(src) 76 | // We check if vSrc is a pointer to dereference it. 77 | if vSrc.Kind() == reflect.Ptr { 78 | vSrc = vSrc.Elem() 79 | } 80 | return 81 | } 82 | -------------------------------------------------------------------------------- /mergo_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 Dario Castañé. All rights reserved. 2 | // Copyright 2009 The Go Authors. All rights reserved. 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file. 5 | 6 | package mergo_test 7 | 8 | import ( 9 | "encoding/json" 10 | "os" 11 | "reflect" 12 | "strings" 13 | "testing" 14 | "time" 15 | 16 | "dario.cat/mergo" 17 | ) 18 | 19 | type simpleTest struct { 20 | Value int 21 | } 22 | 23 | type complexTest struct { 24 | ID string 25 | St simpleTest 26 | sz int 27 | } 28 | 29 | type mapTest struct { 30 | M map[int]int 31 | } 32 | 33 | type ifcTest struct { 34 | I interface{} 35 | } 36 | 37 | type moreComplextText struct { 38 | Ct complexTest 39 | St simpleTest 40 | Nt simpleTest 41 | } 42 | 43 | type pointerTest struct { 44 | C *simpleTest 45 | } 46 | 47 | type sliceTest struct { 48 | S []int 49 | } 50 | 51 | func TestKb(t *testing.T) { 52 | type testStruct struct { 53 | KeyValue map[string]interface{} 54 | Name string 55 | } 56 | 57 | akv := make(map[string]interface{}) 58 | akv["Key1"] = "not value 1" 59 | akv["Key2"] = "value2" 60 | a := testStruct{} 61 | a.Name = "A" 62 | a.KeyValue = akv 63 | 64 | bkv := make(map[string]interface{}) 65 | bkv["Key1"] = "value1" 66 | bkv["Key3"] = "value3" 67 | b := testStruct{} 68 | b.Name = "B" 69 | b.KeyValue = bkv 70 | 71 | ekv := make(map[string]interface{}) 72 | ekv["Key1"] = "value1" 73 | ekv["Key2"] = "value2" 74 | ekv["Key3"] = "value3" 75 | expected := testStruct{} 76 | expected.Name = "B" 77 | expected.KeyValue = ekv 78 | 79 | if err := mergo.Merge(&b, a); err != nil { 80 | t.Error(err) 81 | } 82 | 83 | if !reflect.DeepEqual(b, expected) { 84 | t.Errorf("Actual: %#v did not match \nExpected: %#v", b, expected) 85 | } 86 | } 87 | 88 | func TestNil(t *testing.T) { 89 | t.Run("both nil", func(t *testing.T) { 90 | if err := mergo.Merge(nil, nil); err != mergo.ErrNilArguments { 91 | t.Fail() 92 | } 93 | }) 94 | t.Run("dst nil", func(t *testing.T) { 95 | if err := mergo.Merge(nil, struct{}{}); err != mergo.ErrNilArguments { 96 | t.Fail() 97 | } 98 | }) 99 | t.Run("src nil", func(t *testing.T) { 100 | dst := struct{}{} 101 | if err := mergo.Merge(&dst, nil); err != mergo.ErrNilArguments { 102 | t.Error(err) 103 | } 104 | }) 105 | } 106 | 107 | func TestDifferentTypes(t *testing.T) { 108 | a := simpleTest{42} 109 | b := 42 110 | if err := mergo.Merge(&a, b); err != mergo.ErrDifferentArgumentsTypes { 111 | t.Fail() 112 | } 113 | } 114 | 115 | func TestSimpleStruct(t *testing.T) { 116 | a := simpleTest{} 117 | b := simpleTest{42} 118 | if err := mergo.Merge(&a, b); err != nil { 119 | t.FailNow() 120 | } 121 | if a.Value != 42 { 122 | t.Errorf("b not merged in properly: a.Value(%d) != b.Value(%d)", a.Value, b.Value) 123 | } 124 | if !reflect.DeepEqual(a, b) { 125 | t.FailNow() 126 | } 127 | } 128 | 129 | func TestComplexStruct(t *testing.T) { 130 | a := complexTest{} 131 | a.ID = "athing" 132 | b := complexTest{"bthing", simpleTest{42}, 1} 133 | if err := mergo.Merge(&a, b); err != nil { 134 | t.FailNow() 135 | } 136 | if a.St.Value != 42 { 137 | t.Errorf("b not merged in properly: a.St.Value(%d) != b.St.Value(%d)", a.St.Value, b.St.Value) 138 | } 139 | if a.sz == 1 { 140 | t.Errorf("a's private field sz not preserved from merge: a.sz(%d) == b.sz(%d)", a.sz, b.sz) 141 | } 142 | if a.ID == b.ID { 143 | t.Errorf("a's field ID merged unexpectedly: a.ID(%s) == b.ID(%s)", a.ID, b.ID) 144 | } 145 | } 146 | 147 | func TestComplexStructWithOverwrite(t *testing.T) { 148 | a := complexTest{"do-not-overwrite-with-empty-value", simpleTest{1}, 1} 149 | b := complexTest{"", simpleTest{42}, 2} 150 | 151 | expect := complexTest{"do-not-overwrite-with-empty-value", simpleTest{42}, 1} 152 | if err := mergo.MergeWithOverwrite(&a, b); err != nil { 153 | t.FailNow() 154 | } 155 | 156 | if !reflect.DeepEqual(a, expect) { 157 | t.Errorf("Test failed:\ngot :\n%#v\n\nwant :\n%#v\n\n", a, expect) 158 | } 159 | } 160 | 161 | func TestPointerStruct(t *testing.T) { 162 | s1 := simpleTest{} 163 | s2 := simpleTest{19} 164 | a := pointerTest{&s1} 165 | b := pointerTest{&s2} 166 | if err := mergo.Merge(&a, b); err != nil { 167 | t.FailNow() 168 | } 169 | if a.C.Value != b.C.Value { 170 | t.Errorf("b not merged in properly: a.C.Value(%d) != b.C.Value(%d)", a.C.Value, b.C.Value) 171 | } 172 | } 173 | 174 | type embeddingStruct struct { 175 | embeddedStruct 176 | } 177 | 178 | type embeddedStruct struct { 179 | A string 180 | } 181 | 182 | func TestEmbeddedStruct(t *testing.T) { 183 | tests := []struct { 184 | src embeddingStruct 185 | dst embeddingStruct 186 | expected embeddingStruct 187 | }{ 188 | { 189 | src: embeddingStruct{ 190 | embeddedStruct{"foo"}, 191 | }, 192 | dst: embeddingStruct{ 193 | embeddedStruct{""}, 194 | }, 195 | expected: embeddingStruct{ 196 | embeddedStruct{"foo"}, 197 | }, 198 | }, 199 | { 200 | src: embeddingStruct{ 201 | embeddedStruct{""}, 202 | }, 203 | dst: embeddingStruct{ 204 | embeddedStruct{"bar"}, 205 | }, 206 | expected: embeddingStruct{ 207 | embeddedStruct{"bar"}, 208 | }, 209 | }, 210 | { 211 | src: embeddingStruct{ 212 | embeddedStruct{"foo"}, 213 | }, 214 | dst: embeddingStruct{ 215 | embeddedStruct{"bar"}, 216 | }, 217 | expected: embeddingStruct{ 218 | embeddedStruct{"bar"}, 219 | }, 220 | }, 221 | } 222 | 223 | for _, test := range tests { 224 | err := mergo.Merge(&test.dst, test.src) 225 | if err != nil { 226 | t.Errorf("unexpected error: %v", err) 227 | continue 228 | } 229 | if !reflect.DeepEqual(test.dst, test.expected) { 230 | t.Errorf("unexpected output\nexpected:\n%+v\nsaw:\n%+v\n", test.expected, test.dst) 231 | } 232 | } 233 | } 234 | 235 | func TestPointerStructNil(t *testing.T) { 236 | a := pointerTest{nil} 237 | b := pointerTest{&simpleTest{19}} 238 | if err := mergo.Merge(&a, b); err != nil { 239 | t.FailNow() 240 | } 241 | if a.C.Value != b.C.Value { 242 | t.Errorf("b not merged in a properly: a.C.Value(%d) != b.C.Value(%d)", a.C.Value, b.C.Value) 243 | } 244 | } 245 | 246 | func testSlice(t *testing.T, a []int, b []int, e []int, opts ...func(*mergo.Config)) { 247 | t.Helper() 248 | bc := b 249 | 250 | sa := sliceTest{a} 251 | sb := sliceTest{b} 252 | if err := mergo.Merge(&sa, sb, opts...); err != nil { 253 | t.FailNow() 254 | } 255 | if !reflect.DeepEqual(sb.S, bc) { 256 | t.Errorf("Source slice was modified %d != %d", sb.S, bc) 257 | } 258 | if !reflect.DeepEqual(sa.S, e) { 259 | t.Errorf("b not merged in a proper way %d != %d", sa.S, e) 260 | } 261 | 262 | ma := map[string][]int{"S": a} 263 | mb := map[string][]int{"S": b} 264 | if err := mergo.Merge(&ma, mb, opts...); err != nil { 265 | t.FailNow() 266 | } 267 | if !reflect.DeepEqual(mb["S"], bc) { 268 | t.Errorf("map value: Source slice was modified %d != %d", mb["S"], bc) 269 | } 270 | if !reflect.DeepEqual(ma["S"], e) { 271 | t.Errorf("map value: b not merged in a proper way %d != %d", ma["S"], e) 272 | } 273 | 274 | if a == nil { 275 | // test case with missing dst key 276 | ma := map[string][]int{} 277 | mb := map[string][]int{"S": b} 278 | if err := mergo.Merge(&ma, mb); err != nil { 279 | t.FailNow() 280 | } 281 | if !reflect.DeepEqual(mb["S"], bc) { 282 | t.Errorf("missing dst key: Source slice was modified %d != %d", mb["S"], bc) 283 | } 284 | if !reflect.DeepEqual(ma["S"], e) { 285 | t.Errorf("missing dst key: b not merged in a proper way %d != %d", ma["S"], e) 286 | } 287 | } 288 | 289 | if b == nil { 290 | // test case with missing src key 291 | ma := map[string][]int{"S": a} 292 | mb := map[string][]int{} 293 | if err := mergo.Merge(&ma, mb); err != nil { 294 | t.FailNow() 295 | } 296 | if !reflect.DeepEqual(mb["S"], bc) { 297 | t.Errorf("missing src key: Source slice was modified %d != %d", mb["S"], bc) 298 | } 299 | if !reflect.DeepEqual(ma["S"], e) { 300 | t.Errorf("missing src key: b not merged in a proper way %d != %d", ma["S"], e) 301 | } 302 | } 303 | } 304 | 305 | func TestSlice(t *testing.T) { 306 | testSlice(t, nil, []int{1, 2, 3}, []int{1, 2, 3}) 307 | testSlice(t, []int{}, []int{1, 2, 3}, []int{1, 2, 3}) 308 | testSlice(t, []int{1}, []int{2, 3}, []int{1}) 309 | testSlice(t, []int{1}, []int{}, []int{1}) 310 | testSlice(t, []int{1}, nil, []int{1}) 311 | testSlice(t, nil, []int{1, 2, 3}, []int{1, 2, 3}, mergo.WithAppendSlice) 312 | testSlice(t, []int{}, []int{1, 2, 3}, []int{1, 2, 3}, mergo.WithAppendSlice) 313 | testSlice(t, []int{1}, []int{2, 3}, []int{1, 2, 3}, mergo.WithAppendSlice) 314 | testSlice(t, []int{1}, []int{2, 3}, []int{1, 2, 3}, mergo.WithAppendSlice, mergo.WithOverride) 315 | testSlice(t, []int{1}, []int{}, []int{1}, mergo.WithAppendSlice) 316 | testSlice(t, []int{1}, nil, []int{1}, mergo.WithAppendSlice) 317 | } 318 | 319 | func TestEmptyMaps(t *testing.T) { 320 | a := mapTest{} 321 | b := mapTest{ 322 | map[int]int{}, 323 | } 324 | if err := mergo.Merge(&a, b); err != nil { 325 | t.Fail() 326 | } 327 | if !reflect.DeepEqual(a, b) { 328 | t.FailNow() 329 | } 330 | } 331 | 332 | func TestEmptyToEmptyMaps(t *testing.T) { 333 | a := mapTest{} 334 | b := mapTest{} 335 | if err := mergo.Merge(&a, b); err != nil { 336 | t.Fail() 337 | } 338 | if !reflect.DeepEqual(a, b) { 339 | t.FailNow() 340 | } 341 | } 342 | 343 | func TestEmptyToNotEmptyMaps(t *testing.T) { 344 | a := mapTest{map[int]int{ 345 | 1: 2, 346 | 3: 4, 347 | }} 348 | aa := mapTest{map[int]int{ 349 | 1: 2, 350 | 3: 4, 351 | }} 352 | b := mapTest{ 353 | map[int]int{}, 354 | } 355 | if err := mergo.Merge(&a, b); err != nil { 356 | t.Fail() 357 | } 358 | if !reflect.DeepEqual(a, aa) { 359 | t.FailNow() 360 | } 361 | } 362 | 363 | func TestMapsWithOverwrite(t *testing.T) { 364 | m := map[string]simpleTest{ 365 | "a": {}, // overwritten by 16 366 | "b": {42}, // overwritten by 0, as map Value is not addressable and it doesn't check for b is set or not set in `n` 367 | "c": {13}, // overwritten by 12 368 | "d": {61}, 369 | } 370 | n := map[string]simpleTest{ 371 | "a": {16}, 372 | "b": {}, 373 | "c": {12}, 374 | "e": {14}, 375 | } 376 | expect := map[string]simpleTest{ 377 | "a": {16}, 378 | "b": {}, 379 | "c": {12}, 380 | "d": {61}, 381 | "e": {14}, 382 | } 383 | 384 | if err := mergo.MergeWithOverwrite(&m, n); err != nil { 385 | t.Error(err.Error()) 386 | } 387 | 388 | if !reflect.DeepEqual(m, expect) { 389 | t.Errorf("Test failed:\ngot :\n%#v\n\nwant :\n%#v\n\n", m, expect) 390 | } 391 | } 392 | 393 | func TestMapWithEmbeddedStructPointer(t *testing.T) { 394 | m := map[string]*simpleTest{ 395 | "a": {}, // overwritten by 16 396 | "b": {42}, // not overwritten by empty value 397 | "c": {13}, // overwritten by 12 398 | "d": {61}, 399 | } 400 | n := map[string]*simpleTest{ 401 | "a": {16}, 402 | "b": {}, 403 | "c": {12}, 404 | "e": {14}, 405 | } 406 | expect := map[string]*simpleTest{ 407 | "a": {16}, 408 | "b": {42}, 409 | "c": {12}, 410 | "d": {61}, 411 | "e": {14}, 412 | } 413 | 414 | if err := mergo.Merge(&m, n, mergo.WithOverride); err != nil { 415 | t.Error(err.Error()) 416 | } 417 | 418 | if !reflect.DeepEqual(m, expect) { 419 | t.Errorf("Test failed:\ngot :\n%#v\n\nwant :\n%#v\n\n", m, expect) 420 | } 421 | } 422 | 423 | func TestMergeUsingStructAndMap(t *testing.T) { 424 | type multiPtr struct { 425 | Text string 426 | Number int 427 | } 428 | type final struct { 429 | Msg1 string 430 | Msg2 string 431 | } 432 | type params struct { 433 | Multi *multiPtr 434 | Final *final 435 | Name string 436 | } 437 | type config struct { 438 | Params *params 439 | Foo string 440 | Bar string 441 | } 442 | 443 | cases := []struct { 444 | changes *config 445 | target *config 446 | output *config 447 | name string 448 | overwrite bool 449 | }{ 450 | { 451 | name: "Should overwrite values in target for non-nil values in source", 452 | overwrite: true, 453 | changes: &config{ 454 | Bar: "from changes", 455 | Params: ¶ms{ 456 | Final: &final{ 457 | Msg1: "from changes", 458 | Msg2: "from changes", 459 | }, 460 | }, 461 | }, 462 | target: &config{ 463 | Foo: "from target", 464 | Params: ¶ms{ 465 | Name: "from target", 466 | Multi: &multiPtr{ 467 | Text: "from target", 468 | Number: 5, 469 | }, 470 | Final: &final{ 471 | Msg1: "from target", 472 | Msg2: "", 473 | }, 474 | }, 475 | }, 476 | output: &config{ 477 | Foo: "from target", 478 | Bar: "from changes", 479 | Params: ¶ms{ 480 | Name: "from target", 481 | Multi: &multiPtr{ 482 | Text: "from target", 483 | Number: 5, 484 | }, 485 | Final: &final{ 486 | Msg1: "from changes", 487 | Msg2: "from changes", 488 | }, 489 | }, 490 | }, 491 | }, 492 | { 493 | name: "Should not overwrite values in target for non-nil values in source", 494 | overwrite: false, 495 | changes: &config{ 496 | Bar: "from changes", 497 | Params: ¶ms{ 498 | Final: &final{ 499 | Msg1: "from changes", 500 | Msg2: "from changes", 501 | }, 502 | }, 503 | }, 504 | target: &config{ 505 | Foo: "from target", 506 | Params: ¶ms{ 507 | Name: "from target", 508 | Multi: &multiPtr{ 509 | Text: "from target", 510 | Number: 5, 511 | }, 512 | Final: &final{ 513 | Msg1: "from target", 514 | Msg2: "", 515 | }, 516 | }, 517 | }, 518 | output: &config{ 519 | Foo: "from target", 520 | Bar: "from changes", 521 | Params: ¶ms{ 522 | Name: "from target", 523 | Multi: &multiPtr{ 524 | Text: "from target", 525 | Number: 5, 526 | }, 527 | Final: &final{ 528 | Msg1: "from target", 529 | Msg2: "from changes", 530 | }, 531 | }, 532 | }, 533 | }, 534 | } 535 | 536 | for _, tc := range cases { 537 | t.Run(tc.name, func(t *testing.T) { 538 | var err error 539 | if tc.overwrite { 540 | err = mergo.Merge(tc.target, *tc.changes, mergo.WithOverride) 541 | } else { 542 | err = mergo.Merge(tc.target, *tc.changes) 543 | } 544 | if err != nil { 545 | t.Error(err) 546 | } 547 | if !reflect.DeepEqual(tc.target, tc.output) { 548 | t.Errorf("Test failed:\ngot :\n%+v\n\nwant :\n%+v\n\n", tc.target.Params, tc.output.Params) 549 | } 550 | }) 551 | } 552 | } 553 | func TestMaps(t *testing.T) { 554 | m := map[string]simpleTest{ 555 | "a": {}, 556 | "b": {42}, 557 | "c": {13}, 558 | "d": {61}, 559 | } 560 | n := map[string]simpleTest{ 561 | "a": {16}, 562 | "b": {}, 563 | "c": {12}, 564 | "e": {14}, 565 | } 566 | expect := map[string]simpleTest{ 567 | "a": {0}, 568 | "b": {42}, 569 | "c": {13}, 570 | "d": {61}, 571 | "e": {14}, 572 | } 573 | 574 | if err := mergo.Merge(&m, n); err != nil { 575 | t.Error(err.Error()) 576 | } 577 | 578 | if !reflect.DeepEqual(m, expect) { 579 | t.Errorf("Test failed:\ngot :\n%#v\n\nwant :\n%#v\n\n", m, expect) 580 | } 581 | if m["a"].Value != 0 { 582 | t.Errorf(`n merged in m because I solved non-addressable map values TODO: m["a"].Value(%d) != n["a"].Value(%d)`, m["a"].Value, n["a"].Value) 583 | } 584 | if m["b"].Value != 42 { 585 | t.Errorf(`n wrongly merged in m: m["b"].Value(%d) != n["b"].Value(%d)`, m["b"].Value, n["b"].Value) 586 | } 587 | if m["c"].Value != 13 { 588 | t.Errorf(`n overwritten in m: m["c"].Value(%d) != n["c"].Value(%d)`, m["c"].Value, n["c"].Value) 589 | } 590 | } 591 | 592 | func TestMapsWithNilPointer(t *testing.T) { 593 | m := map[string]*simpleTest{ 594 | "a": nil, 595 | "b": nil, 596 | } 597 | n := map[string]*simpleTest{ 598 | "b": nil, 599 | "c": nil, 600 | } 601 | expect := map[string]*simpleTest{ 602 | "a": nil, 603 | "b": nil, 604 | "c": nil, 605 | } 606 | 607 | if err := mergo.Merge(&m, n, mergo.WithOverride); err != nil { 608 | t.Error(err.Error()) 609 | } 610 | 611 | if !reflect.DeepEqual(m, expect) { 612 | t.Errorf("Test failed:\ngot :\n%#v\n\nwant :\n%#v\n\n", m, expect) 613 | } 614 | } 615 | 616 | func TestJSONMaps(t *testing.T) { 617 | thing := loadFixture("testdata/thing.json") 618 | license := loadFixture("testdata/license.json") 619 | ft := thing["fields"].(map[string]interface{}) 620 | fl := license["fields"].(map[string]interface{}) 621 | // license has one extra field (site) and another already existing in thing (author) that Mergo won't override. 622 | expectedLength := len(ft) + len(fl) - 1 623 | if err := mergo.Merge(&license, thing); err != nil { 624 | t.Error(err.Error()) 625 | } 626 | currentLength := len(license["fields"].(map[string]interface{})) 627 | if currentLength != expectedLength { 628 | t.Errorf(`thing not merged in license properly, license must have %d elements instead of %d`, expectedLength, currentLength) 629 | } 630 | fields := license["fields"].(map[string]interface{}) 631 | if _, ok := fields["id"]; !ok { 632 | t.Errorf(`thing not merged in license properly, license must have a new id field from thing`) 633 | } 634 | } 635 | 636 | func TestTwoPointerValues(t *testing.T) { 637 | a := &simpleTest{} 638 | b := &simpleTest{42} 639 | if err := mergo.Merge(a, b); err != nil { 640 | t.Errorf(`Boom. You crossed the streams: %s`, err) 641 | } 642 | } 643 | 644 | func TestMap(t *testing.T) { 645 | a := complexTest{} 646 | a.ID = "athing" 647 | c := moreComplextText{a, simpleTest{}, simpleTest{}} 648 | b := map[string]interface{}{ 649 | "ct": map[string]interface{}{ 650 | "st": map[string]interface{}{ 651 | "value": 42, 652 | }, 653 | "sz": 1, 654 | "id": "bthing", 655 | }, 656 | "st": &simpleTest{144}, // Mapping a reference 657 | "zt": simpleTest{299}, // Mapping a missing field (zt doesn't exist) 658 | "nt": simpleTest{3}, 659 | } 660 | if err := mergo.Map(&c, b); err != nil { 661 | t.FailNow() 662 | } 663 | m := b["ct"].(map[string]interface{}) 664 | n := m["st"].(map[string]interface{}) 665 | o := b["st"].(*simpleTest) 666 | p := b["nt"].(simpleTest) 667 | if c.Ct.St.Value != 42 { 668 | t.Errorf("b not merged in properly: c.Ct.St.Value(%d) != b.Ct.St.Value(%d)", c.Ct.St.Value, n["value"]) 669 | } 670 | if c.St.Value != 144 { 671 | t.Errorf("b not merged in properly: c.St.Value(%d) != b.St.Value(%d)", c.St.Value, o.Value) 672 | } 673 | if c.Nt.Value != 3 { 674 | t.Errorf("b not merged in properly: c.Nt.Value(%d) != b.Nt.Value(%d)", c.St.Value, p.Value) 675 | } 676 | if c.Ct.sz == 1 { 677 | t.Errorf("a's private field sz not preserved from merge: c.Ct.sz(%d) == b.Ct.sz(%d)", c.Ct.sz, m["sz"]) 678 | } 679 | if c.Ct.ID == m["id"] { 680 | t.Errorf("a's field ID merged unexpectedly: c.Ct.ID(%s) == b.Ct.ID(%s)", c.Ct.ID, m["id"]) 681 | } 682 | } 683 | 684 | func TestSimpleMap(t *testing.T) { 685 | a := simpleTest{} 686 | b := map[string]interface{}{ 687 | "value": 42, 688 | } 689 | if err := mergo.Map(&a, b); err != nil { 690 | t.FailNow() 691 | } 692 | if a.Value != 42 { 693 | t.Errorf("b not merged in properly: a.Value(%d) != b.Value(%v)", a.Value, b["value"]) 694 | } 695 | } 696 | 697 | func TestIfcMap(t *testing.T) { 698 | a := ifcTest{} 699 | b := ifcTest{42} 700 | if err := mergo.Map(&a, b); err != nil { 701 | t.FailNow() 702 | } 703 | if a.I != 42 { 704 | t.Errorf("b not merged in properly: a.I(%d) != b.I(%d)", a.I, b.I) 705 | } 706 | if !reflect.DeepEqual(a, b) { 707 | t.FailNow() 708 | } 709 | } 710 | 711 | func TestIfcMapNoOverwrite(t *testing.T) { 712 | a := ifcTest{13} 713 | b := ifcTest{42} 714 | if err := mergo.Map(&a, b); err != nil { 715 | t.FailNow() 716 | } 717 | if a.I != 13 { 718 | t.Errorf("a not left alone: a.I(%d) == b.I(%d)", a.I, b.I) 719 | } 720 | } 721 | 722 | func TestIfcMapWithOverwrite(t *testing.T) { 723 | a := ifcTest{13} 724 | b := ifcTest{42} 725 | if err := mergo.MapWithOverwrite(&a, b); err != nil { 726 | t.FailNow() 727 | } 728 | if a.I != 42 { 729 | t.Errorf("b not merged in properly: a.I(%d) != b.I(%d)", a.I, b.I) 730 | } 731 | if !reflect.DeepEqual(a, b) { 732 | t.FailNow() 733 | } 734 | } 735 | 736 | type pointerMapTest struct { 737 | B *simpleTest 738 | A int 739 | hidden int 740 | } 741 | 742 | func TestBackAndForth(t *testing.T) { 743 | pt := pointerMapTest{&simpleTest{66}, 42, 1} 744 | m := make(map[string]interface{}) 745 | if err := mergo.Map(&m, pt); err != nil { 746 | t.FailNow() 747 | } 748 | var ( 749 | v interface{} 750 | ok bool 751 | ) 752 | if v, ok = m["a"]; v.(int) != pt.A || !ok { 753 | t.Errorf("pt not merged in properly: m[`a`](%d) != pt.A(%d)", v, pt.A) 754 | } 755 | if v, ok = m["b"]; !ok { 756 | t.Errorf("pt not merged in properly: B is missing in m") 757 | } 758 | var st *simpleTest 759 | if st = v.(*simpleTest); st.Value != 66 { 760 | t.Errorf("something went wrong while mapping pt on m, B wasn't copied") 761 | } 762 | bpt := pointerMapTest{} 763 | if err := mergo.Map(&bpt, m); err != nil { 764 | t.Error(err) 765 | } 766 | if bpt.A != pt.A { 767 | t.Errorf("pt not merged in properly: bpt.A(%d) != pt.A(%d)", bpt.A, pt.A) 768 | } 769 | if bpt.hidden == pt.hidden { 770 | t.Errorf("pt unexpectedly merged: bpt.hidden(%d) == pt.hidden(%d)", bpt.hidden, pt.hidden) 771 | } 772 | if bpt.B.Value != pt.B.Value { 773 | t.Errorf("pt not merged in properly: bpt.B.Value(%d) != pt.B.Value(%d)", bpt.B.Value, pt.B.Value) 774 | } 775 | } 776 | 777 | func TestEmbeddedPointerUnpacking(t *testing.T) { 778 | tests := []struct{ input pointerMapTest }{ 779 | {pointerMapTest{nil, 42, 1}}, 780 | {pointerMapTest{&simpleTest{66}, 42, 1}}, 781 | } 782 | newValue := 77 783 | m := map[string]interface{}{ 784 | "b": map[string]interface{}{ 785 | "value": newValue, 786 | }, 787 | } 788 | for _, test := range tests { 789 | pt := test.input 790 | if err := mergo.MapWithOverwrite(&pt, m); err != nil { 791 | t.FailNow() 792 | } 793 | if pt.B.Value != newValue { 794 | t.Errorf("pt not mapped properly: pt.A.Value(%d) != m[`b`][`value`](%d)", pt.B.Value, newValue) 795 | } 796 | 797 | } 798 | } 799 | 800 | type structWithTimePointer struct { 801 | Birth *time.Time 802 | } 803 | 804 | func TestTime(t *testing.T) { 805 | now := time.Now() 806 | dataStruct := structWithTimePointer{ 807 | Birth: &now, 808 | } 809 | dataMap := map[string]interface{}{ 810 | "Birth": &now, 811 | } 812 | b := structWithTimePointer{} 813 | if err := mergo.Merge(&b, dataStruct); err != nil { 814 | t.FailNow() 815 | } 816 | if b.Birth.IsZero() { 817 | t.Errorf("time.Time not merged in properly: b.Birth(%v) != dataStruct['Birth'](%v)", b.Birth, dataStruct.Birth) 818 | } 819 | if b.Birth != dataStruct.Birth { 820 | t.Errorf("time.Time not merged in properly: b.Birth(%v) != dataStruct['Birth'](%v)", b.Birth, dataStruct.Birth) 821 | } 822 | b = structWithTimePointer{} 823 | if err := mergo.Map(&b, dataMap); err != nil { 824 | t.FailNow() 825 | } 826 | if b.Birth.IsZero() { 827 | t.Errorf("time.Time not merged in properly: b.Birth(%v) != dataMap['Birth'](%v)", b.Birth, dataMap["Birth"]) 828 | } 829 | } 830 | 831 | type simpleNested struct { 832 | A int 833 | } 834 | 835 | type structWithNestedPtrValueMap struct { 836 | NestedPtrValue map[string]*simpleNested 837 | } 838 | 839 | func TestNestedPtrValueInMap(t *testing.T) { 840 | src := &structWithNestedPtrValueMap{ 841 | NestedPtrValue: map[string]*simpleNested{ 842 | "x": { 843 | A: 1, 844 | }, 845 | }, 846 | } 847 | dst := &structWithNestedPtrValueMap{ 848 | NestedPtrValue: map[string]*simpleNested{ 849 | "x": {}, 850 | }, 851 | } 852 | if err := mergo.Map(dst, src); err != nil { 853 | t.FailNow() 854 | } 855 | if dst.NestedPtrValue["x"].A == 0 { 856 | t.Errorf("Nested Ptr value not merged in properly: dst.NestedPtrValue[\"x\"].A(%v) != src.NestedPtrValue[\"x\"].A(%v)", dst.NestedPtrValue["x"].A, src.NestedPtrValue["x"].A) 857 | } 858 | } 859 | 860 | func loadFixture(path string) (m map[string]interface{}) { 861 | m = make(map[string]interface{}) 862 | raw, _ := os.ReadFile(path) 863 | _ = json.Unmarshal(raw, &m) 864 | return 865 | } 866 | 867 | type structWithMap struct { 868 | m map[string]structWithUnexportedProperty 869 | } 870 | 871 | type structWithUnexportedProperty struct { 872 | s string 873 | } 874 | 875 | func TestUnexportedProperty(t *testing.T) { 876 | a := structWithMap{map[string]structWithUnexportedProperty{ 877 | "key": {"hello"}, 878 | }} 879 | b := structWithMap{map[string]structWithUnexportedProperty{ 880 | "key": {"hi"}, 881 | }} 882 | defer func() { 883 | if r := recover(); r != nil { 884 | t.Errorf("Should not have panicked") 885 | } 886 | }() 887 | if err := mergo.Merge(&a, b); err != nil { 888 | t.Errorf("Error while merging %s", err) 889 | } 890 | } 891 | 892 | type structWithBoolPointer struct { 893 | C *bool 894 | } 895 | 896 | func TestBooleanPointer(t *testing.T) { 897 | bt, bf := true, false 898 | src := structWithBoolPointer{ 899 | &bt, 900 | } 901 | dst := structWithBoolPointer{ 902 | &bf, 903 | } 904 | if err := mergo.Merge(&dst, src); err != nil { 905 | t.FailNow() 906 | } 907 | if dst.C == src.C { 908 | t.Errorf("dst.C should be a different pointer than src.C") 909 | } 910 | if *dst.C != *src.C { 911 | t.Errorf("dst.C should be true") 912 | } 913 | } 914 | 915 | func TestMergeMapWithInnerSliceOfDifferentType(t *testing.T) { 916 | testCases := []struct { 917 | name string 918 | err string 919 | options []func(*mergo.Config) 920 | }{ 921 | { 922 | "With override and append slice", 923 | "cannot append two slices with different type", 924 | []func(*mergo.Config){mergo.WithOverride, mergo.WithAppendSlice}, 925 | }, 926 | { 927 | "With override and type check", 928 | "cannot override two slices with different type", 929 | []func(*mergo.Config){mergo.WithOverride, mergo.WithTypeCheck}, 930 | }, 931 | } 932 | for _, tc := range testCases { 933 | t.Run(tc.name, func(t *testing.T) { 934 | src := map[string]interface{}{ 935 | "foo": []string{"a", "b"}, 936 | } 937 | dst := map[string]interface{}{ 938 | "foo": []int{1, 2}, 939 | } 940 | 941 | if err := mergo.Merge(&src, &dst, tc.options...); err == nil || !strings.Contains(err.Error(), tc.err) { 942 | t.Errorf("expected %q, got %q", tc.err, err) 943 | } 944 | }) 945 | } 946 | } 947 | 948 | func TestMergeDifferentSlicesIsNotSupported(t *testing.T) { 949 | src := []string{"a", "b"} 950 | dst := []int{1, 2} 951 | 952 | if err := mergo.Merge(&src, &dst, mergo.WithOverride, mergo.WithAppendSlice); err != mergo.ErrDifferentArgumentsTypes { 953 | t.Errorf("expected %q, got %q", mergo.ErrNotSupported, err) 954 | } 955 | } 956 | -------------------------------------------------------------------------------- /pr211_2_test.go: -------------------------------------------------------------------------------- 1 | package mergo 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | "time" 7 | ) 8 | 9 | type transformer struct { 10 | } 11 | 12 | func (s *transformer) Transformer(t reflect.Type) func(dst, src reflect.Value) error { 13 | return nil 14 | } 15 | 16 | func Test_deepMergeTransformerInvalidDestination(t *testing.T) { 17 | foo := time.Time{} 18 | src := reflect.ValueOf(foo) 19 | _ = deepMerge(reflect.Value{}, src, make(map[uintptr]*visit), 0, &Config{ 20 | Transformers: &transformer{}, 21 | }) 22 | // this test is intentionally not asserting on anything, it's sole 23 | // purpose to verify deepMerge doesn't panic when a transformer is 24 | // passed and the destination is invalid. 25 | } 26 | -------------------------------------------------------------------------------- /pr211_test.go: -------------------------------------------------------------------------------- 1 | package mergo_test 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | 7 | "dario.cat/mergo" 8 | ) 9 | 10 | func TestMergeWithTransformerZeroValue(t *testing.T) { 11 | // This test specifically tests that a transformer can be used to 12 | // prevent overwriting a zero value (in this case a bool). This would fail prior to #211 13 | type fooWithBoolPtr struct { 14 | b *bool 15 | } 16 | var Bool = func(b bool) *bool { return &b } 17 | a := fooWithBoolPtr{b: Bool(false)} 18 | b := fooWithBoolPtr{b: Bool(true)} 19 | 20 | if err := mergo.Merge(&a, &b, mergo.WithTransformers(&transformer{ 21 | m: map[reflect.Type]func(dst, src reflect.Value) error{ 22 | reflect.TypeOf(Bool(false)): func(dst, src reflect.Value) error { 23 | if dst.CanSet() && dst.IsNil() { 24 | dst.Set(src) 25 | } 26 | return nil 27 | }, 28 | }, 29 | })); err != nil { 30 | t.Error(err) 31 | } 32 | 33 | if *a.b != false { 34 | t.Errorf("b not merged in properly: a.b(%v) != expected(%v)", a.b, false) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /pr80_test.go: -------------------------------------------------------------------------------- 1 | package mergo_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "dario.cat/mergo" 7 | ) 8 | 9 | type mapInterface map[string]interface{} 10 | 11 | func TestMergeMapsEmptyString(t *testing.T) { 12 | a := mapInterface{"s": ""} 13 | b := mapInterface{"s": "foo"} 14 | if err := mergo.Merge(&a, b); err != nil { 15 | t.Error(err) 16 | } 17 | if a["s"] != "foo" { 18 | t.Errorf("b not merged in properly: a.s.Value(%s) != expected(%s)", a["s"], "foo") 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /pr81_test.go: -------------------------------------------------------------------------------- 1 | package mergo_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "dario.cat/mergo" 7 | ) 8 | 9 | func TestMapInterfaceWithMultipleLayer(t *testing.T) { 10 | m1 := map[string]interface{}{ 11 | "k1": map[string]interface{}{ 12 | "k1.1": "v1", 13 | }, 14 | } 15 | 16 | m2 := map[string]interface{}{ 17 | "k1": map[string]interface{}{ 18 | "k1.1": "v2", 19 | "k1.2": "v3", 20 | }, 21 | } 22 | 23 | if err := mergo.Map(&m1, m2, mergo.WithOverride); err != nil { 24 | t.Errorf("Error merging: %v", err) 25 | } 26 | 27 | // Check overwrite of sub map works 28 | expected := "v2" 29 | actual := m1["k1"].(map[string]interface{})["k1.1"].(string) 30 | if actual != expected { 31 | t.Errorf("Expected %v but got %v", 32 | expected, 33 | actual) 34 | } 35 | 36 | // Check new key is merged 37 | expected = "v3" 38 | actual = m1["k1"].(map[string]interface{})["k1.2"].(string) 39 | if actual != expected { 40 | t.Errorf("Expected %v but got %v", 41 | expected, 42 | actual) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /testdata/license.json: -------------------------------------------------------------------------------- 1 | { 2 | "import": "./thing.json", 3 | "fields": { 4 | "site": "string", 5 | "author": "updater" 6 | } 7 | } -------------------------------------------------------------------------------- /testdata/thing.json: -------------------------------------------------------------------------------- 1 | { 2 | "fields": { 3 | "id": "int", 4 | "name": "string", 5 | "parent": "ref datu:thing", 6 | "status": "enum(draft, public, private)", 7 | "author": "updater" 8 | } 9 | } -------------------------------------------------------------------------------- /v039_bugs_test.go: -------------------------------------------------------------------------------- 1 | package mergo_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "dario.cat/mergo" 7 | ) 8 | 9 | type inner struct { 10 | A int 11 | } 12 | 13 | type outer struct { 14 | inner 15 | B int 16 | } 17 | 18 | func TestV039Issue139(t *testing.T) { 19 | dst := outer{ 20 | inner: inner{A: 1}, 21 | B: 2, 22 | } 23 | src := outer{ 24 | inner: inner{A: 10}, 25 | B: 20, 26 | } 27 | err := mergo.MergeWithOverwrite(&dst, src) 28 | if err != nil { 29 | panic(err.Error()) 30 | } 31 | if dst.A == 1 { 32 | t.Errorf("expected %d, got %d", src.A, dst.A) 33 | } 34 | } 35 | 36 | func TestV039Issue152(t *testing.T) { 37 | dst := map[string]interface{}{ 38 | "properties": map[string]interface{}{ 39 | "field1": map[string]interface{}{ 40 | "type": "text", 41 | }, 42 | "field2": "ohai", 43 | }, 44 | } 45 | src := map[string]interface{}{ 46 | "properties": map[string]interface{}{ 47 | "field1": "wrong", 48 | }, 49 | } 50 | if err := mergo.Map(&dst, src, mergo.WithOverride); err != nil { 51 | t.Error(err) 52 | } 53 | } 54 | 55 | type issue146Foo struct { 56 | B map[string]issue146Bar 57 | A string 58 | } 59 | 60 | type issue146Bar struct { 61 | C *string 62 | D *string 63 | } 64 | 65 | func TestV039Issue146(t *testing.T) { 66 | var ( 67 | s1 = "asd" 68 | s2 = "sdf" 69 | ) 70 | dst := issue146Foo{ 71 | A: "two", 72 | B: map[string]issue146Bar{ 73 | "foo": { 74 | C: &s1, 75 | }, 76 | }, 77 | } 78 | src := issue146Foo{ 79 | A: "one", 80 | B: map[string]issue146Bar{ 81 | "foo": { 82 | D: &s2, 83 | }, 84 | }, 85 | } 86 | if err := mergo.Merge(&dst, src, mergo.WithOverride); err != nil { 87 | t.Error(err) 88 | } 89 | if dst.B["foo"].D == nil { 90 | t.Errorf("expected %v, got nil", &s2) 91 | } 92 | } 93 | --------------------------------------------------------------------------------