diff --git a/.github/ISSUE_TEMPLATE/bug_report.yaml b/.github/ISSUE_TEMPLATE/bug_report.yaml
index a893b01..db00ae6 100644
--- a/.github/ISSUE_TEMPLATE/bug_report.yaml
+++ b/.github/ISSUE_TEMPLATE/bug_report.yaml
@@ -19,6 +19,8 @@ body:
         required: true
       - label: I am not looking for support or already pursued the available [support channels](https://github.com/spf13/viper/issues/new/choose) without success.
         required: true
+      - label: I have checked the [troubleshooting guide](https://github.com/spf13/viper/blob/master/TROUBLESHOOTING.md) for my problem, without success.
+        required: true
 - type: input
   attributes:
     label: Viper Version
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 53ba804..bf7422c 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -17,6 +17,7 @@ jobs:
       matrix:
         os: [ubuntu-latest, macos-latest, windows-latest]
         go: ['1.14', '1.15', '1.16', '1.17']
+        tags: ['', 'viper_yaml3', 'viper_toml2']
     env:
       GOFLAGS: -mod=readonly
 
@@ -27,10 +28,15 @@ jobs:
           go-version: ${{ matrix.go }}
 
       - name: Checkout code
-        uses: actions/checkout@v2
+        uses: actions/checkout@v3
 
       - name: Test
-        run: go test -race -v ./...
+        run: go test -race -tags '${{ matrix.tags }}' -v ./...
+        if: runner.os != 'Windows'
+
+      - name: Test (without race detector)
+        run: go test -tags '${{ matrix.tags }}' -v ./...
+        if: runner.os == 'Windows'
 
   lint:
     name: Lint
@@ -45,7 +51,7 @@ jobs:
           go-version: 1.17
 
       - name: Checkout code
-        uses: actions/checkout@v2
+        uses: actions/checkout@v3
 
       - name: Lint
         uses: golangci/golangci-lint-action@v2
diff --git a/.github/workflows/codeql-analysis.yaml b/.github/workflows/codeql-analysis.yaml
index 60bf45c..89b7737 100644
--- a/.github/workflows/codeql-analysis.yaml
+++ b/.github/workflows/codeql-analysis.yaml
@@ -39,7 +39,7 @@ jobs:
 
     steps:
     - name: Checkout repository
-      uses: actions/checkout@v2
+      uses: actions/checkout@v3
 
     # Initializes the CodeQL tools for scanning.
     - name: Initialize CodeQL
diff --git a/.github/workflows/feedback_issue.yml b/.github/workflows/feedback_issue.yml
index 87453cf..3859624 100644
--- a/.github/workflows/feedback_issue.yml
+++ b/.github/workflows/feedback_issue.yml
@@ -6,7 +6,7 @@ jobs:
   comment:
     runs-on: ubuntu-latest
     steps:
-      - uses: actions/github-script@v5
+      - uses: actions/github-script@v6
         with:
           github-token: ${{secrets.GITHUB_TOKEN}}
           script: |
diff --git a/.github/workflows/feedback_pull_request.yml b/.github/workflows/feedback_pull_request.yml
index d3e5d22..f5f6ab4 100644
--- a/.github/workflows/feedback_pull_request.yml
+++ b/.github/workflows/feedback_pull_request.yml
@@ -6,7 +6,7 @@ jobs:
   comment:
     runs-on: ubuntu-latest
     steps:
-      - uses: actions/github-script@v5
+      - uses: actions/github-script@v6
         with:
           github-token: ${{secrets.GITHUB_TOKEN}}
           script: |
diff --git a/.github/workflows/wasm.yml b/.github/workflows/wasm.yml
index 20e04b8..55634de 100644
--- a/.github/workflows/wasm.yml
+++ b/.github/workflows/wasm.yml
@@ -20,7 +20,7 @@ jobs:
           go-version: '1.17'
 
       - name: Checkout code
-        uses: actions/checkout@v2
+        uses: actions/checkout@v3
 
       - name: Ensure Viper compiles for WASM
         run: GOOS=js GOARCH=wasm go build .
diff --git a/TROUBLESHOOTING.md b/TROUBLESHOOTING.md
index 096277a..c4e36c6 100644
--- a/TROUBLESHOOTING.md
+++ b/TROUBLESHOOTING.md
@@ -21,3 +21,12 @@ The solution is easy: switch to using Go Modules.
 Please refer to the [wiki](https://github.com/golang/go/wiki/Modules) on how to do that.
 
 **tl;dr* `export GO111MODULE=on`
+
+## Unquoted 'y' and 'n' characters get replaced with _true_ and _false_ when reading a YAML file
+
+This is a YAML 1.1 feature according to [go-yaml/yaml#740](https://github.com/go-yaml/yaml/issues/740).
+
+Potential solutions are:
+
+1. Quoting values resolved as boolean
+1. Upgrading to YAML v3 (for the time being this is possible by passing the `viper_yaml3` tag to your build)
diff --git a/debian/changelog b/debian/changelog
index 02cffc8..28b6907 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,9 @@
+golang-github-spf13-viper (1.10.1+git20220312.1.dc76f3c-1) UNRELEASED; urgency=low
+
+  * New upstream snapshot.
+
+ -- Debian Janitor <janitor@jelmer.uk>  Mon, 28 Mar 2022 06:19:41 -0000
+
 golang-github-spf13-viper (1.10.1-1) unstable; urgency=medium
 
   * New upstream version 1.10.1
diff --git a/experimental_logger.go b/experimental_logger.go
new file mode 100644
index 0000000..206dad6
--- /dev/null
+++ b/experimental_logger.go
@@ -0,0 +1,11 @@
+//go:build viper_logger
+// +build viper_logger
+
+package viper
+
+// WithLogger sets a custom logger.
+func WithLogger(l Logger) Option {
+	return optionFunc(func(v *Viper) {
+		v.logger = l
+	})
+}
diff --git a/go.mod b/go.mod
index 91f39a3..3db6196 100644
--- a/go.mod
+++ b/go.mod
@@ -5,18 +5,20 @@ go 1.17
 require (
 	github.com/fsnotify/fsnotify v1.5.1
 	github.com/hashicorp/hcl v1.0.0
-	github.com/magiconair/properties v1.8.5
+	github.com/magiconair/properties v1.8.6
 	github.com/mitchellh/mapstructure v1.4.3
 	github.com/pelletier/go-toml v1.9.4
+	github.com/pelletier/go-toml/v2 v2.0.0-beta.6
 	github.com/sagikazarmark/crypt v0.4.0
-	github.com/spf13/afero v1.6.0
+	github.com/spf13/afero v1.8.2
 	github.com/spf13/cast v1.4.1
 	github.com/spf13/jwalterweatherman v1.1.0
 	github.com/spf13/pflag v1.0.5
-	github.com/stretchr/testify v1.7.0
+	github.com/stretchr/testify v1.7.1-0.20210427113832-6241f9ab9942
 	github.com/subosito/gotenv v1.2.0
-	gopkg.in/ini.v1 v1.66.2
+	gopkg.in/ini.v1 v1.66.4
 	gopkg.in/yaml.v2 v2.4.0
+	gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
 )
 
 require (
@@ -54,7 +56,7 @@ require (
 	go.etcd.io/etcd/client/pkg/v3 v3.5.1 // indirect
 	go.etcd.io/etcd/client/v2 v2.305.1 // indirect
 	go.opencensus.io v0.23.0 // indirect
-	golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 // indirect
+	golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa // indirect
 	golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d // indirect
 	golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect
 	golang.org/x/sys v0.0.0-20211210111614-af8b64212486 // indirect
@@ -65,5 +67,4 @@ require (
 	google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa // indirect
 	google.golang.org/grpc v1.43.0 // indirect
 	google.golang.org/protobuf v1.27.1 // indirect
-	gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
 )
diff --git a/go.sum b/go.sum
index ceb4d98..bf5216b 100644
--- a/go.sum
+++ b/go.sum
@@ -3,6 +3,7 @@ cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT
 cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
 cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
 cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
+cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
 cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
 cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
 cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
@@ -15,6 +16,7 @@ cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOY
 cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
 cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI=
 cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk=
+cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY=
 cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg=
 cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8=
 cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0=
@@ -46,6 +48,7 @@ cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0Zeo
 cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
 cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
 cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
+cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo=
 dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
 github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
@@ -193,6 +196,7 @@ github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hf
 github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
 github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
 github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
+github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
 github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
 github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
 github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
@@ -206,6 +210,7 @@ github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5m
 github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0=
 github.com/googleapis/gax-go/v2 v2.1.1 h1:dp3bWCh+PPO1zjRRiCSczJav13sBvG4UhNyVTa1KqdU=
 github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM=
+github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g=
 github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
 github.com/hashicorp/consul/api v1.12.0 h1:k3y1FYv6nuKyNTqj6w9gXOx5r5CfLj/k/euUeBXj1OY=
 github.com/hashicorp/consul/api v1.12.0/go.mod h1:6pVBMo0ebnYdt2S3H87XhekM/HHrUoTD2XXb/VrZVy0=
@@ -272,8 +277,8 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
 github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
 github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
 github.com/lyft/protoc-gen-star v0.5.3/go.mod h1:V0xaHgaf5oCCqmcxYcWiDfTiKsZsRc87/1qhoTACD8w=
-github.com/magiconair/properties v1.8.5 h1:b6kJs+EmPFMYGkow9GiUyCyOvIwYetYJ3fSaWak/Gls=
-github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
+github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo=
+github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
 github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
 github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
 github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
@@ -313,10 +318,14 @@ github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0Mw
 github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
 github.com/pelletier/go-toml v1.9.4 h1:tjENF6MfZAg8e4ZmZTeWaWiT2vXtsoO6+iuOjFhECwM=
 github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
+github.com/pelletier/go-toml/v2 v2.0.0-beta.6 h1:JFNqj2afbbhCqTiyN16D7Tudc/aaDzE2FBDk+VlBQnE=
+github.com/pelletier/go-toml/v2 v2.0.0-beta.6/go.mod h1:ke6xncR3W76Ba8xnVxkrZG0js6Rd2BsQEAYrfgJ6eQA=
 github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
-github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
 github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
+github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI=
+github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
@@ -344,8 +353,9 @@ github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPx
 github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
 github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
 github.com/spf13/afero v1.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4=
-github.com/spf13/afero v1.6.0 h1:xoax2sJ2DT8S8xA2paPFjDCScCNeWsg75VG0DLRreiY=
 github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=
+github.com/spf13/afero v1.8.2 h1:xehSyVa0YnHWsJ49JFljMpg1HX19V6NDZ1fkm1Xznbo=
+github.com/spf13/afero v1.8.2/go.mod h1:CtAatgMJh6bJEIs48Ay/FOnkljP3WeGUG0MC1RfAqwo=
 github.com/spf13/cast v1.4.1 h1:s0hze+J0196ZfEMTs80N7UlFt0BDuQ7Q+JDnHiMWKdA=
 github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
 github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
@@ -360,8 +370,9 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV
 github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
 github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
 github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
 github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.7.1-0.20210427113832-6241f9ab9942 h1:t0lM6y/M5IiUZyvbBTcngso8SZEZICH7is9B6g/obVU=
+github.com/stretchr/testify v1.7.1-0.20210427113832-6241f9ab9942/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
 github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
 github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
@@ -396,8 +407,10 @@ golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8U
 golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY=
 golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
-golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 h1:HWj/xjIHfjYU5nVXpTM0s39J9CbLn7Cc5a7IC5rwsMQ=
+golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
 golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
+golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa h1:idItI2DDfCokpg0N51B2VtiLdJ4vAuXC9fnCb2gACo4=
+golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
 golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@@ -467,6 +480,7 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY
 golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
 golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
 golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
+golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
 golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
 golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
 golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
@@ -547,6 +561,7 @@ golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -554,6 +569,7 @@ golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -631,6 +647,7 @@ golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4f
 golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
 golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
 golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
 golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
 golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
 golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
@@ -718,7 +735,9 @@ google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6D
 google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
 google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
 google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
 google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
 google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
 google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
 google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
@@ -794,8 +813,8 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8
 gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
 gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
-gopkg.in/ini.v1 v1.66.2 h1:XfR1dOYubytKy4Shzc2LHrrGhU0lDCfDGG1yLPmpgsI=
-gopkg.in/ini.v1 v1.66.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
+gopkg.in/ini.v1 v1.66.4 h1:SsAcf+mM7mRZo2nJNGt8mZCjG8ZRaNGMURJw7BsIST4=
+gopkg.in/ini.v1 v1.66.4/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
 gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
diff --git a/internal/encoding/decoder.go b/internal/encoding/decoder.go
index 08b1bb6..f472e9f 100644
--- a/internal/encoding/decoder.go
+++ b/internal/encoding/decoder.go
@@ -4,10 +4,10 @@ import (
 	"sync"
 )
 
-// Decoder decodes the contents of b into a v representation.
+// Decoder decodes the contents of b into v.
 // It's primarily used for decoding contents of a file into a map[string]interface{}.
 type Decoder interface {
-	Decode(b []byte, v interface{}) error
+	Decode(b []byte, v map[string]interface{}) error
 }
 
 const (
@@ -48,7 +48,7 @@ func (e *DecoderRegistry) RegisterDecoder(format string, enc Decoder) error {
 }
 
 // Decode calls the underlying Decoder based on the format.
-func (e *DecoderRegistry) Decode(format string, b []byte, v interface{}) error {
+func (e *DecoderRegistry) Decode(format string, b []byte, v map[string]interface{}) error {
 	e.mu.RLock()
 	decoder, ok := e.decoders[format]
 	e.mu.RUnlock()
diff --git a/internal/encoding/decoder_test.go b/internal/encoding/decoder_test.go
index 80e6688..6cb56d0 100644
--- a/internal/encoding/decoder_test.go
+++ b/internal/encoding/decoder_test.go
@@ -1,16 +1,18 @@
 package encoding
 
 import (
+	"reflect"
 	"testing"
 )
 
 type decoder struct {
-	v interface{}
+	v map[string]interface{}
 }
 
-func (d decoder) Decode(_ []byte, v interface{}) error {
-	rv := v.(*string)
-	*rv = d.v.(string)
+func (d decoder) Decode(_ []byte, v map[string]interface{}) error {
+	for key, value := range d.v {
+		v[key] = value
+	}
 
 	return nil
 }
@@ -44,7 +46,9 @@ func TestDecoderRegistry_Decode(t *testing.T) {
 	t.Run("OK", func(t *testing.T) {
 		registry := NewDecoderRegistry()
 		decoder := decoder{
-			v: "decoded value",
+			v: map[string]interface{}{
+				"key": "value",
+			},
 		}
 
 		err := registry.RegisterDecoder("myformat", decoder)
@@ -52,24 +56,24 @@ func TestDecoderRegistry_Decode(t *testing.T) {
 			t.Fatal(err)
 		}
 
-		var v string
+		v := map[string]interface{}{}
 
-		err = registry.Decode("myformat", []byte("some value"), &v)
+		err = registry.Decode("myformat", []byte("key: value"), v)
 		if err != nil {
 			t.Fatal(err)
 		}
 
-		if v != "decoded value" {
-			t.Fatalf("expected 'decoded value', got: %#v", v)
+		if !reflect.DeepEqual(decoder.v, v) {
+			t.Fatalf("decoded value does not match the expected one\nactual:   %+v\nexpected: %+v", v, decoder.v)
 		}
 	})
 
 	t.Run("DecoderNotFound", func(t *testing.T) {
 		registry := NewDecoderRegistry()
 
-		var v string
+		v := map[string]interface{}{}
 
-		err := registry.Decode("myformat", []byte("some value"), &v)
+		err := registry.Decode("myformat", nil, v)
 		if err != ErrDecoderNotFound {
 			t.Fatalf("expected ErrDecoderNotFound, got: %v", err)
 		}
diff --git a/internal/encoding/dotenv/codec.go b/internal/encoding/dotenv/codec.go
new file mode 100644
index 0000000..4485063
--- /dev/null
+++ b/internal/encoding/dotenv/codec.go
@@ -0,0 +1,61 @@
+package dotenv
+
+import (
+	"bytes"
+	"fmt"
+	"sort"
+	"strings"
+
+	"github.com/subosito/gotenv"
+)
+
+const keyDelimiter = "_"
+
+// Codec implements the encoding.Encoder and encoding.Decoder interfaces for encoding data containing environment variables
+// (commonly called as dotenv format).
+type Codec struct{}
+
+func (Codec) Encode(v map[string]interface{}) ([]byte, error) {
+	flattened := map[string]interface{}{}
+
+	flattened = flattenAndMergeMap(flattened, v, "", keyDelimiter)
+
+	keys := make([]string, 0, len(flattened))
+
+	for key := range flattened {
+		keys = append(keys, key)
+	}
+
+	sort.Strings(keys)
+
+	var buf bytes.Buffer
+
+	for _, key := range keys {
+		_, err := buf.WriteString(fmt.Sprintf("%v=%v\n", strings.ToUpper(key), flattened[key]))
+		if err != nil {
+			return nil, err
+		}
+	}
+
+	return buf.Bytes(), nil
+}
+
+func (Codec) Decode(b []byte, v map[string]interface{}) error {
+	var buf bytes.Buffer
+
+	_, err := buf.Write(b)
+	if err != nil {
+		return err
+	}
+
+	env, err := gotenv.StrictParse(&buf)
+	if err != nil {
+		return err
+	}
+
+	for key, value := range env {
+		v[key] = value
+	}
+
+	return nil
+}
diff --git a/internal/encoding/dotenv/codec_test.go b/internal/encoding/dotenv/codec_test.go
new file mode 100644
index 0000000..d297071
--- /dev/null
+++ b/internal/encoding/dotenv/codec_test.go
@@ -0,0 +1,63 @@
+package dotenv
+
+import (
+	"reflect"
+	"testing"
+)
+
+// original form of the data
+const original = `# key-value pair
+KEY=value
+`
+
+// encoded form of the data
+const encoded = `KEY=value
+`
+
+// Viper's internal representation
+var data = map[string]interface{}{
+	"KEY": "value",
+}
+
+func TestCodec_Encode(t *testing.T) {
+	codec := Codec{}
+
+	b, err := codec.Encode(data)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if encoded != string(b) {
+		t.Fatalf("decoded value does not match the expected one\nactual:   %#v\nexpected: %#v", string(b), encoded)
+	}
+}
+
+func TestCodec_Decode(t *testing.T) {
+	t.Run("OK", func(t *testing.T) {
+		codec := Codec{}
+
+		v := map[string]interface{}{}
+
+		err := codec.Decode([]byte(original), v)
+		if err != nil {
+			t.Fatal(err)
+		}
+
+		if !reflect.DeepEqual(data, v) {
+			t.Fatalf("decoded value does not match the expected one\nactual:   %#v\nexpected: %#v", v, data)
+		}
+	})
+
+	t.Run("InvalidData", func(t *testing.T) {
+		codec := Codec{}
+
+		v := map[string]interface{}{}
+
+		err := codec.Decode([]byte(`invalid data`), v)
+		if err == nil {
+			t.Fatal("expected decoding to fail")
+		}
+
+		t.Logf("decoding failed as expected: %s", err)
+	})
+}
diff --git a/internal/encoding/dotenv/map_utils.go b/internal/encoding/dotenv/map_utils.go
new file mode 100644
index 0000000..ce6e6ef
--- /dev/null
+++ b/internal/encoding/dotenv/map_utils.go
@@ -0,0 +1,41 @@
+package dotenv
+
+import (
+	"strings"
+
+	"github.com/spf13/cast"
+)
+
+// flattenAndMergeMap recursively flattens the given map into a new map
+// Code is based on the function with the same name in tha main package.
+// TODO: move it to a common place
+func flattenAndMergeMap(shadow map[string]interface{}, m map[string]interface{}, prefix string, delimiter string) map[string]interface{} {
+	if shadow != nil && prefix != "" && shadow[prefix] != nil {
+		// prefix is shadowed => nothing more to flatten
+		return shadow
+	}
+	if shadow == nil {
+		shadow = make(map[string]interface{})
+	}
+
+	var m2 map[string]interface{}
+	if prefix != "" {
+		prefix += delimiter
+	}
+	for k, val := range m {
+		fullKey := prefix + k
+		switch val.(type) {
+		case map[string]interface{}:
+			m2 = val.(map[string]interface{})
+		case map[interface{}]interface{}:
+			m2 = cast.ToStringMap(val)
+		default:
+			// immediate value
+			shadow[strings.ToLower(fullKey)] = val
+			continue
+		}
+		// recursively merge to shadow map
+		shadow = flattenAndMergeMap(shadow, m2, fullKey, delimiter)
+	}
+	return shadow
+}
diff --git a/internal/encoding/encoder.go b/internal/encoding/encoder.go
index 82c7996..2341bf2 100644
--- a/internal/encoding/encoder.go
+++ b/internal/encoding/encoder.go
@@ -7,7 +7,7 @@ import (
 // Encoder encodes the contents of v into a byte representation.
 // It's primarily used for encoding a map[string]interface{} into a file format.
 type Encoder interface {
-	Encode(v interface{}) ([]byte, error)
+	Encode(v map[string]interface{}) ([]byte, error)
 }
 
 const (
@@ -47,7 +47,7 @@ func (e *EncoderRegistry) RegisterEncoder(format string, enc Encoder) error {
 	return nil
 }
 
-func (e *EncoderRegistry) Encode(format string, v interface{}) ([]byte, error) {
+func (e *EncoderRegistry) Encode(format string, v map[string]interface{}) ([]byte, error) {
 	e.mu.RLock()
 	encoder, ok := e.encoders[format]
 	e.mu.RUnlock()
diff --git a/internal/encoding/encoder_test.go b/internal/encoding/encoder_test.go
index e2472ad..adee6d0 100644
--- a/internal/encoding/encoder_test.go
+++ b/internal/encoding/encoder_test.go
@@ -8,7 +8,7 @@ type encoder struct {
 	b []byte
 }
 
-func (e encoder) Encode(_ interface{}) ([]byte, error) {
+func (e encoder) Encode(_ map[string]interface{}) ([]byte, error) {
 	return e.b, nil
 }
 
@@ -41,7 +41,7 @@ func TestEncoderRegistry_Decode(t *testing.T) {
 	t.Run("OK", func(t *testing.T) {
 		registry := NewEncoderRegistry()
 		encoder := encoder{
-			b: []byte("encoded value"),
+			b: []byte("key: value"),
 		}
 
 		err := registry.RegisterEncoder("myformat", encoder)
@@ -49,20 +49,20 @@ func TestEncoderRegistry_Decode(t *testing.T) {
 			t.Fatal(err)
 		}
 
-		b, err := registry.Encode("myformat", "some value")
+		b, err := registry.Encode("myformat", map[string]interface{}{"key": "value"})
 		if err != nil {
 			t.Fatal(err)
 		}
 
-		if string(b) != "encoded value" {
-			t.Fatalf("expected 'encoded value', got: %#v", string(b))
+		if string(b) != "key: value" {
+			t.Fatalf("expected 'key: value', got: %#v", string(b))
 		}
 	})
 
 	t.Run("EncoderNotFound", func(t *testing.T) {
 		registry := NewEncoderRegistry()
 
-		_, err := registry.Encode("myformat", "some value")
+		_, err := registry.Encode("myformat", map[string]interface{}{"key": "value"})
 		if err != ErrEncoderNotFound {
 			t.Fatalf("expected ErrEncoderNotFound, got: %v", err)
 		}
diff --git a/internal/encoding/hcl/codec.go b/internal/encoding/hcl/codec.go
index f3e4ab1..7fde8e4 100644
--- a/internal/encoding/hcl/codec.go
+++ b/internal/encoding/hcl/codec.go
@@ -12,7 +12,7 @@ import (
 // TODO: add printer config to the codec?
 type Codec struct{}
 
-func (Codec) Encode(v interface{}) ([]byte, error) {
+func (Codec) Encode(v map[string]interface{}) ([]byte, error) {
 	b, err := json.Marshal(v)
 	if err != nil {
 		return nil, err
@@ -35,6 +35,6 @@ func (Codec) Encode(v interface{}) ([]byte, error) {
 	return buf.Bytes(), nil
 }
 
-func (Codec) Decode(b []byte, v interface{}) error {
-	return hcl.Unmarshal(b, v)
+func (Codec) Decode(b []byte, v map[string]interface{}) error {
+	return hcl.Unmarshal(b, &v)
 }
diff --git a/internal/encoding/hcl/codec_test.go b/internal/encoding/hcl/codec_test.go
new file mode 100644
index 0000000..7e48057
--- /dev/null
+++ b/internal/encoding/hcl/codec_test.go
@@ -0,0 +1,140 @@
+package hcl
+
+import (
+	"reflect"
+	"testing"
+)
+
+// original form of the data
+const original = `# key-value pair
+"key" = "value"
+
+// list
+"list" = ["item1", "item2", "item3"]
+
+/* map */
+"map" = {
+  "key" = "value"
+}
+
+/*
+nested map
+*/
+"nested_map" "map" {
+  "key" = "value"
+
+  "list" = ["item1", "item2", "item3"]
+}`
+
+// encoded form of the data
+const encoded = `"key" = "value"
+
+"list" = ["item1", "item2", "item3"]
+
+"map" = {
+  "key" = "value"
+}
+
+"nested_map" "map" {
+  "key" = "value"
+
+  "list" = ["item1", "item2", "item3"]
+}`
+
+// decoded form of the data
+//
+// in case of HCL it's slightly different from Viper's internal representation
+// (eg. map is decoded into a list of maps)
+var decoded = map[string]interface{}{
+	"key": "value",
+	"list": []interface{}{
+		"item1",
+		"item2",
+		"item3",
+	},
+	"map": []map[string]interface{}{
+		{
+			"key": "value",
+		},
+	},
+	"nested_map": []map[string]interface{}{
+		{
+			"map": []map[string]interface{}{
+				{
+					"key": "value",
+					"list": []interface{}{
+						"item1",
+						"item2",
+						"item3",
+					},
+				},
+			},
+		},
+	},
+}
+
+// Viper's internal representation
+var data = map[string]interface{}{
+	"key": "value",
+	"list": []interface{}{
+		"item1",
+		"item2",
+		"item3",
+	},
+	"map": map[string]interface{}{
+		"key": "value",
+	},
+	"nested_map": map[string]interface{}{
+		"map": map[string]interface{}{
+			"key": "value",
+			"list": []interface{}{
+				"item1",
+				"item2",
+				"item3",
+			},
+		},
+	},
+}
+
+func TestCodec_Encode(t *testing.T) {
+	codec := Codec{}
+
+	b, err := codec.Encode(data)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if encoded != string(b) {
+		t.Fatalf("decoded value does not match the expected one\nactual:   %#v\nexpected: %#v", string(b), encoded)
+	}
+}
+
+func TestCodec_Decode(t *testing.T) {
+	t.Run("OK", func(t *testing.T) {
+		codec := Codec{}
+
+		v := map[string]interface{}{}
+
+		err := codec.Decode([]byte(original), v)
+		if err != nil {
+			t.Fatal(err)
+		}
+
+		if !reflect.DeepEqual(decoded, v) {
+			t.Fatalf("decoded value does not match the expected one\nactual:   %#v\nexpected: %#v", v, decoded)
+		}
+	})
+
+	t.Run("InvalidData", func(t *testing.T) {
+		codec := Codec{}
+
+		v := map[string]interface{}{}
+
+		err := codec.Decode([]byte(`invalid data`), v)
+		if err == nil {
+			t.Fatal("expected decoding to fail")
+		}
+
+		t.Logf("decoding failed as expected: %s", err)
+	})
+}
diff --git a/internal/encoding/ini/codec.go b/internal/encoding/ini/codec.go
new file mode 100644
index 0000000..9acd87f
--- /dev/null
+++ b/internal/encoding/ini/codec.go
@@ -0,0 +1,99 @@
+package ini
+
+import (
+	"bytes"
+	"sort"
+	"strings"
+
+	"github.com/spf13/cast"
+	"gopkg.in/ini.v1"
+)
+
+// LoadOptions contains all customized options used for load data source(s).
+// This type is added here for convenience: this way consumers can import a single package called "ini".
+type LoadOptions = ini.LoadOptions
+
+// Codec implements the encoding.Encoder and encoding.Decoder interfaces for INI encoding.
+type Codec struct {
+	KeyDelimiter string
+	LoadOptions  LoadOptions
+}
+
+func (c Codec) Encode(v map[string]interface{}) ([]byte, error) {
+	cfg := ini.Empty()
+	ini.PrettyFormat = false
+
+	flattened := map[string]interface{}{}
+
+	flattened = flattenAndMergeMap(flattened, v, "", c.keyDelimiter())
+
+	keys := make([]string, 0, len(flattened))
+
+	for key := range flattened {
+		keys = append(keys, key)
+	}
+
+	sort.Strings(keys)
+
+	for _, key := range keys {
+		sectionName, keyName := "", key
+
+		lastSep := strings.LastIndex(key, ".")
+		if lastSep != -1 {
+			sectionName = key[:(lastSep)]
+			keyName = key[(lastSep + 1):]
+		}
+
+		// TODO: is this a good idea?
+		if sectionName == "default" {
+			sectionName = ""
+		}
+
+		cfg.Section(sectionName).Key(keyName).SetValue(cast.ToString(flattened[key]))
+	}
+
+	var buf bytes.Buffer
+
+	_, err := cfg.WriteTo(&buf)
+	if err != nil {
+		return nil, err
+	}
+
+	return buf.Bytes(), nil
+}
+
+func (c Codec) Decode(b []byte, v map[string]interface{}) error {
+	cfg := ini.Empty(c.LoadOptions)
+
+	err := cfg.Append(b)
+	if err != nil {
+		return err
+	}
+
+	sections := cfg.Sections()
+
+	for i := 0; i < len(sections); i++ {
+		section := sections[i]
+		keys := section.Keys()
+
+		for j := 0; j < len(keys); j++ {
+			key := keys[j]
+			value := cfg.Section(section.Name()).Key(key.Name()).String()
+
+			deepestMap := deepSearch(v, strings.Split(section.Name(), c.keyDelimiter()))
+
+			// set innermost value
+			deepestMap[key.Name()] = value
+		}
+	}
+
+	return nil
+}
+
+func (c Codec) keyDelimiter() string {
+	if c.KeyDelimiter == "" {
+		return "."
+	}
+
+	return c.KeyDelimiter
+}
diff --git a/internal/encoding/ini/codec_test.go b/internal/encoding/ini/codec_test.go
new file mode 100644
index 0000000..ca48617
--- /dev/null
+++ b/internal/encoding/ini/codec_test.go
@@ -0,0 +1,112 @@
+package ini
+
+import (
+	"reflect"
+	"testing"
+)
+
+// original form of the data
+const original = `; key-value pair
+key=value ; key-value pair
+
+# map
+[map] # map
+key=%(key)s
+
+`
+
+// encoded form of the data
+const encoded = `key=value
+
+[map]
+key=value
+
+`
+
+// decoded form of the data
+//
+// in case of INI it's slightly different from Viper's internal representation
+// (eg. top level keys land in a section called default)
+var decoded = map[string]interface{}{
+	"DEFAULT": map[string]interface{}{
+		"key": "value",
+	},
+	"map": map[string]interface{}{
+		"key": "value",
+	},
+}
+
+// Viper's internal representation
+var data = map[string]interface{}{
+	"key": "value",
+	"map": map[string]interface{}{
+		"key": "value",
+	},
+}
+
+func TestCodec_Encode(t *testing.T) {
+	t.Run("OK", func(t *testing.T) {
+		codec := Codec{}
+
+		b, err := codec.Encode(data)
+		if err != nil {
+			t.Fatal(err)
+		}
+
+		if encoded != string(b) {
+			t.Fatalf("decoded value does not match the expected one\nactual:   %#v\nexpected: %#v", string(b), encoded)
+		}
+	})
+
+	t.Run("Default", func(t *testing.T) {
+		codec := Codec{}
+
+		data := map[string]interface{}{
+			"default": map[string]interface{}{
+				"key": "value",
+			},
+			"map": map[string]interface{}{
+				"key": "value",
+			},
+		}
+
+		b, err := codec.Encode(data)
+		if err != nil {
+			t.Fatal(err)
+		}
+
+		if encoded != string(b) {
+			t.Fatalf("decoded value does not match the expected one\nactual:   %#v\nexpected: %#v", string(b), encoded)
+		}
+	})
+}
+
+func TestCodec_Decode(t *testing.T) {
+	t.Run("OK", func(t *testing.T) {
+		codec := Codec{}
+
+		v := map[string]interface{}{}
+
+		err := codec.Decode([]byte(original), v)
+		if err != nil {
+			t.Fatal(err)
+		}
+
+		if !reflect.DeepEqual(decoded, v) {
+			t.Fatalf("decoded value does not match the expected one\nactual:   %#v\nexpected: %#v", v, decoded)
+		}
+	})
+
+	t.Run("InvalidData", func(t *testing.T) {
+		codec := Codec{}
+
+		v := map[string]interface{}{}
+
+		err := codec.Decode([]byte(`invalid data`), v)
+		if err == nil {
+			t.Fatal("expected decoding to fail")
+		}
+
+		t.Logf("decoding failed as expected: %s", err)
+	})
+}
diff --git a/internal/encoding/ini/map_utils.go b/internal/encoding/ini/map_utils.go
new file mode 100644
index 0000000..8329856
--- /dev/null
+++ b/internal/encoding/ini/map_utils.go
@@ -0,0 +1,74 @@
+package ini
+
+import (
+	"strings"
+
+	"github.com/spf13/cast"
+)
+
+// THIS CODE IS COPIED HERE: IT SHOULD NOT BE MODIFIED
+// AT SOME POINT IT WILL BE MOVED TO A COMMON PLACE
+// deepSearch scans deep maps, following the key indexes listed in the
+// sequence "path".
+// The last value is expected to be another map, and is returned.
+//
+// In case intermediate keys do not exist, or map to a non-map value,
+// a new map is created and inserted, and the search continues from there:
+// the initial map "m" may be modified!
+func deepSearch(m map[string]interface{}, path []string) map[string]interface{} {
+	for _, k := range path {
+		m2, ok := m[k]
+		if !ok {
+			// intermediate key does not exist
+			// => create it and continue from there
+			m3 := make(map[string]interface{})
+			m[k] = m3
+			m = m3
+			continue
+		}
+		m3, ok := m2.(map[string]interface{})
+		if !ok {
+			// intermediate key is a value
+			// => replace with a new map
+			m3 = make(map[string]interface{})
+			m[k] = m3
+		}
+		// continue search from here
+		m = m3
+	}
+	return m
+}
+
+// flattenAndMergeMap recursively flattens the given map into a new map
+// Code is based on the function with the same name in tha main package.
+// TODO: move it to a common place
+func flattenAndMergeMap(shadow map[string]interface{}, m map[string]interface{}, prefix string, delimiter string) map[string]interface{} {
+	if shadow != nil && prefix != "" && shadow[prefix] != nil {
+		// prefix is shadowed => nothing more to flatten
+		return shadow
+	}
+	if shadow == nil {
+		shadow = make(map[string]interface{})
+	}
+
+	var m2 map[string]interface{}
+	if prefix != "" {
+		prefix += delimiter
+	}
+	for k, val := range m {
+		fullKey := prefix + k
+		switch val.(type) {
+		case map[string]interface{}:
+			m2 = val.(map[string]interface{})
+		case map[interface{}]interface{}:
+			m2 = cast.ToStringMap(val)
+		default:
+			// immediate value
+			shadow[strings.ToLower(fullKey)] = val
+			continue
+		}
+		// recursively merge to shadow map
+		shadow = flattenAndMergeMap(shadow, m2, fullKey, delimiter)
+	}
+	return shadow
+}
diff --git a/internal/encoding/javaproperties/codec.go b/internal/encoding/javaproperties/codec.go
new file mode 100644
index 0000000..b8a2251
--- /dev/null
+++ b/internal/encoding/javaproperties/codec.go
@@ -0,0 +1,86 @@
+package javaproperties
+
+import (
+	"bytes"
+	"sort"
+	"strings"
+
+	"github.com/magiconair/properties"
+	"github.com/spf13/cast"
+)
+
+// Codec implements the encoding.Encoder and encoding.Decoder interfaces for Java properties encoding.
+type Codec struct {
+	KeyDelimiter string
+
+	// Store read properties on the object so that we can write back in order with comments.
+	// This will only be used if the configuration read is a properties file.
+	// TODO: drop this feature in v2
+	// TODO: make use of the global properties object optional
+	Properties *properties.Properties
+}
+
+func (c *Codec) Encode(v map[string]interface{}) ([]byte, error) {
+	if c.Properties == nil {
+		c.Properties = properties.NewProperties()
+	}
+
+	flattened := map[string]interface{}{}
+
+	flattened = flattenAndMergeMap(flattened, v, "", c.keyDelimiter())
+
+	keys := make([]string, 0, len(flattened))
+
+	for key := range flattened {
+		keys = append(keys, key)
+	}
+
+	sort.Strings(keys)
+
+	for _, key := range keys {
+		_, _, err := c.Properties.Set(key, cast.ToString(flattened[key]))
+		if err != nil {
+			return nil, err
+		}
+	}
+
+	var buf bytes.Buffer
+
+	_, err := c.Properties.WriteComment(&buf, "#", properties.UTF8)
+	if err != nil {
+		return nil, err
+	}
+
+	return buf.Bytes(), nil
+}
+
+func (c *Codec) Decode(b []byte, v map[string]interface{}) error {
+	var err error
+	c.Properties, err = properties.Load(b, properties.UTF8)
+	if err != nil {
+		return err
+	}
+
+	for _, key := range c.Properties.Keys() {
+		// ignore existence check: we know it's there
+		value, _ := c.Properties.Get(key)
+
+		// recursively build nested maps
+		path := strings.Split(key, c.keyDelimiter())
+		lastKey := strings.ToLower(path[len(path)-1])
+		deepestMap := deepSearch(v, path[0:len(path)-1])
+
+		// set innermost value
+		deepestMap[lastKey] = value
+	}
+
+	return nil
+}
+
+func (c Codec) keyDelimiter() string {
+	if c.KeyDelimiter == "" {
+		return "."
+	}
+
+	return c.KeyDelimiter
+}
diff --git a/internal/encoding/javaproperties/codec_test.go b/internal/encoding/javaproperties/codec_test.go
new file mode 100644
index 0000000..0a33ebf
--- /dev/null
+++ b/internal/encoding/javaproperties/codec_test.go
@@ -0,0 +1,89 @@
+package javaproperties
+
+import (
+	"reflect"
+	"testing"
+)
+
+// original form of the data
+const original = `#key-value pair
+key = value
+map.key = value
+`
+
+// encoded form of the data
+const encoded = `key = value
+map.key = value
+`
+
+// Viper's internal representation
+var data = map[string]interface{}{
+	"key": "value",
+	"map": map[string]interface{}{
+		"key": "value",
+	},
+}
+
+func TestCodec_Encode(t *testing.T) {
+	codec := Codec{}
+
+	b, err := codec.Encode(data)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if encoded != string(b) {
+		t.Fatalf("decoded value does not match the expected one\nactual:   %#v\nexpected: %#v", string(b), encoded)
+	}
+}
+
+func TestCodec_Decode(t *testing.T) {
+	t.Run("OK", func(t *testing.T) {
+		codec := Codec{}
+
+		v := map[string]interface{}{}
+
+		err := codec.Decode([]byte(original), v)
+		if err != nil {
+			t.Fatal(err)
+		}
+
+		if !reflect.DeepEqual(data, v) {
+			t.Fatalf("decoded value does not match the expected one\nactual:   %#v\nexpected: %#v", v, data)
+		}
+	})
+
+	t.Run("InvalidData", func(t *testing.T) {
+		t.Skip("TODO: needs invalid data example")
+
+		codec := Codec{}
+
+		v := map[string]interface{}{}
+
+		codec.Decode([]byte(``), v)
+
+		if len(v) > 0 {
+			t.Fatalf("expected map to be empty when data is invalid\nactual: %#v", v)
+		}
+	})
+}
+
+func TestCodec_DecodeEncode(t *testing.T) {
+	codec := Codec{}
+
+	v := map[string]interface{}{}
+
+	err := codec.Decode([]byte(original), v)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	b, err := codec.Encode(data)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if original != string(b) {
+		t.Fatalf("encoded value does not match the original\nactual:   %#v\nexpected: %#v", string(b), original)
+	}
+}
diff --git a/internal/encoding/javaproperties/map_utils.go b/internal/encoding/javaproperties/map_utils.go
new file mode 100644
index 0000000..93755ca
--- /dev/null
+++ b/internal/encoding/javaproperties/map_utils.go
@@ -0,0 +1,74 @@
+package javaproperties
+
+import (
+	"strings"
+
+	"github.com/spf13/cast"
+)
+
+// THIS CODE IS COPIED HERE: IT SHOULD NOT BE MODIFIED
+// AT SOME POINT IT WILL BE MOVED TO A COMMON PLACE
+// deepSearch scans deep maps, following the key indexes listed in the
+// sequence "path".
+// The last value is expected to be another map, and is returned.
+//
+// In case intermediate keys do not exist, or map to a non-map value,
+// a new map is created and inserted, and the search continues from there:
+// the initial map "m" may be modified!
+func deepSearch(m map[string]interface{}, path []string) map[string]interface{} {
+	for _, k := range path {
+		m2, ok := m[k]
+		if !ok {
+			// intermediate key does not exist
+			// => create it and continue from there
+			m3 := make(map[string]interface{})
+			m[k] = m3
+			m = m3
+			continue
+		}
+		m3, ok := m2.(map[string]interface{})
+		if !ok {
+			// intermediate key is a value
+			// => replace with a new map
+			m3 = make(map[string]interface{})
+			m[k] = m3
+		}
+		// continue search from here
+		m = m3
+	}
+	return m
+}
+
+// flattenAndMergeMap recursively flattens the given map into a new map
+// Code is based on the function with the same name in tha main package.
+// TODO: move it to a common place
+func flattenAndMergeMap(shadow map[string]interface{}, m map[string]interface{}, prefix string, delimiter string) map[string]interface{} {
+	if shadow != nil && prefix != "" && shadow[prefix] != nil {
+		// prefix is shadowed => nothing more to flatten
+		return shadow
+	}
+	if shadow == nil {
+		shadow = make(map[string]interface{})
+	}
+
+	var m2 map[string]interface{}
+	if prefix != "" {
+		prefix += delimiter
+	}
+	for k, val := range m {
+		fullKey := prefix + k
+		switch val.(type) {
+		case map[string]interface{}:
+			m2 = val.(map[string]interface{})
+		case map[interface{}]interface{}:
+			m2 = cast.ToStringMap(val)
+		default:
+			// immediate value
+			shadow[strings.ToLower(fullKey)] = val
+			continue
+		}
+		// recursively merge to shadow map
+		shadow = flattenAndMergeMap(shadow, m2, fullKey, delimiter)
+	}
+	return shadow
+}
diff --git a/internal/encoding/json/codec.go b/internal/encoding/json/codec.go
index dff9ec9..1b7caac 100644
--- a/internal/encoding/json/codec.go
+++ b/internal/encoding/json/codec.go
@@ -7,11 +7,11 @@ import (
 // Codec implements the encoding.Encoder and encoding.Decoder interfaces for JSON encoding.
 type Codec struct{}
 
-func (Codec) Encode(v interface{}) ([]byte, error) {
+func (Codec) Encode(v map[string]interface{}) ([]byte, error) {
 	// TODO: expose prefix and indent in the Codec as setting?
 	return json.MarshalIndent(v, "", "  ")
 }
 
-func (Codec) Decode(b []byte, v interface{}) error {
-	return json.Unmarshal(b, v)
+func (Codec) Decode(b []byte, v map[string]interface{}) error {
+	return json.Unmarshal(b, &v)
 }
diff --git a/internal/encoding/json/codec_test.go b/internal/encoding/json/codec_test.go
new file mode 100644
index 0000000..f4a71df
--- /dev/null
+++ b/internal/encoding/json/codec_test.go
@@ -0,0 +1,95 @@
+package json
+
+import (
+	"reflect"
+	"testing"
+)
+
+// encoded form of the data
+const encoded = `{
+  "key": "value",
+  "list": [
+    "item1",
+    "item2",
+    "item3"
+  ],
+  "map": {
+    "key": "value"
+  },
+  "nested_map": {
+    "map": {
+      "key": "value",
+      "list": [
+        "item1",
+        "item2",
+        "item3"
+      ]
+    }
+  }
+}`
+
+// Viper's internal representation
+var data = map[string]interface{}{
+	"key": "value",
+	"list": []interface{}{
+		"item1",
+		"item2",
+		"item3",
+	},
+	"map": map[string]interface{}{
+		"key": "value",
+	},
+	"nested_map": map[string]interface{}{
+		"map": map[string]interface{}{
+			"key": "value",
+			"list": []interface{}{
+				"item1",
+				"item2",
+				"item3",
+			},
+		},
+	},
+}
+
+func TestCodec_Encode(t *testing.T) {
+	codec := Codec{}
+
+	b, err := codec.Encode(data)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if encoded != string(b) {
+		t.Fatalf("decoded value does not match the expected one\nactual:   %#v\nexpected: %#v", string(b), encoded)
+	}
+}
+
+func TestCodec_Decode(t *testing.T) {
+	t.Run("OK", func(t *testing.T) {
+		codec := Codec{}
+
+		v := map[string]interface{}{}
+
+		err := codec.Decode([]byte(encoded), v)
+		if err != nil {
+			t.Fatal(err)
+		}
+
+		if !reflect.DeepEqual(data, v) {
+			t.Fatalf("decoded value does not match the expected one\nactual:   %#v\nexpected: %#v", v, data)
+		}
+	})
+
+	t.Run("InvalidData", func(t *testing.T) {
+		codec := Codec{}
+
+		v := map[string]interface{}{}
+
+		err := codec.Decode([]byte(`invalid data`), v)
+		if err == nil {
+			t.Fatal("expected decoding to fail")
+		}
+
+		t.Logf("decoding failed as expected: %s", err)
+	})
+}
diff --git a/internal/encoding/toml/codec.go b/internal/encoding/toml/codec.go
index c043802..ff1112c 100644
--- a/internal/encoding/toml/codec.go
+++ b/internal/encoding/toml/codec.go
@@ -1,3 +1,6 @@
+//go:build !viper_toml2
+// +build !viper_toml2
+
 package toml
 
 import (
@@ -7,39 +10,30 @@ import (
 // Codec implements the encoding.Encoder and encoding.Decoder interfaces for TOML encoding.
 type Codec struct{}
 
-func (Codec) Encode(v interface{}) ([]byte, error) {
-	if m, ok := v.(map[string]interface{}); ok {
-		t, err := toml.TreeFromMap(m)
-		if err != nil {
-			return nil, err
-		}
-
-		s, err := t.ToTomlString()
-		if err != nil {
-			return nil, err
-		}
+func (Codec) Encode(v map[string]interface{}) ([]byte, error) {
+	t, err := toml.TreeFromMap(v)
+	if err != nil {
+		return nil, err
+	}
 
-		return []byte(s), nil
+	s, err := t.ToTomlString()
+	if err != nil {
+		return nil, err
 	}
 
-	return toml.Marshal(v)
+	return []byte(s), nil
 }
 
-func (Codec) Decode(b []byte, v interface{}) error {
+func (Codec) Decode(b []byte, v map[string]interface{}) error {
 	tree, err := toml.LoadBytes(b)
 	if err != nil {
 		return err
 	}
 
-	if m, ok := v.(*map[string]interface{}); ok {
-		vmap := *m
-		tmap := tree.ToMap()
-		for k, v := range tmap {
-			vmap[k] = v
-		}
-
-		return nil
+	tmap := tree.ToMap()
+	for key, value := range tmap {
+		v[key] = value
 	}
 
-	return tree.Unmarshal(v)
+	return nil
 }
diff --git a/internal/encoding/toml/codec2.go b/internal/encoding/toml/codec2.go
new file mode 100644
index 0000000..566b706
--- /dev/null
+++ b/internal/encoding/toml/codec2.go
@@ -0,0 +1,19 @@
+//go:build viper_toml2
+// +build viper_toml2
+
+package toml
+
+import (
+	"github.com/pelletier/go-toml/v2"
+)
+
+// Codec implements the encoding.Encoder and encoding.Decoder interfaces for TOML encoding.
+type Codec struct{}
+
+func (Codec) Encode(v map[string]interface{}) ([]byte, error) {
+	return toml.Marshal(v)
+}
+
+func (Codec) Decode(b []byte, v map[string]interface{}) error {
+	return toml.Unmarshal(b, &v)
+}
diff --git a/internal/encoding/toml/codec2_test.go b/internal/encoding/toml/codec2_test.go
new file mode 100644
index 0000000..271e321
--- /dev/null
+++ b/internal/encoding/toml/codec2_test.go
@@ -0,0 +1,109 @@
+//go:build viper_toml2
+// +build viper_toml2
+
+package toml
+
+import (
+	"reflect"
+	"testing"
+)
+
+// original form of the data
+const original = `# key-value pair
+key = "value"
+list = ["item1", "item2", "item3"]
+
+[map]
+key = "value"
+
+# nested
+# map
+[nested_map]
+[nested_map.map]
+key = "value"
+list = [
+  "item1",
+  "item2",
+  "item3",
+]
+`
+
+// encoded form of the data
+const encoded = `key = 'value'
+list = ['item1', 'item2', 'item3']
+[map]
+key = 'value'
+
+[nested_map]
+[nested_map.map]
+key = 'value'
+list = ['item1', 'item2', 'item3']
+
+
+`
+
+// Viper's internal representation
+var data = map[string]interface{}{
+	"key": "value",
+	"list": []interface{}{
+		"item1",
+		"item2",
+		"item3",
+	},
+	"map": map[string]interface{}{
+		"key": "value",
+	},
+	"nested_map": map[string]interface{}{
+		"map": map[string]interface{}{
+			"key": "value",
+			"list": []interface{}{
+				"item1",
+				"item2",
+				"item3",
+			},
+		},
+	},
+}
+
+func TestCodec_Encode(t *testing.T) {
+	codec := Codec{}
+
+	b, err := codec.Encode(data)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if encoded != string(b) {
+		t.Fatalf("decoded value does not match the expected one\nactual:   %#v\nexpected: %#v", string(b), encoded)
+	}
+}
+
+func TestCodec_Decode(t *testing.T) {
+	t.Run("OK", func(t *testing.T) {
+		codec := Codec{}
+
+		v := map[string]interface{}{}
+
+		err := codec.Decode([]byte(original), v)
+		if err != nil {
+			t.Fatal(err)
+		}
+
+		if !reflect.DeepEqual(data, v) {
+			t.Fatalf("decoded value does not match the expected one\nactual:   %#v\nexpected: %#v", v, data)
+		}
+	})
+
+	t.Run("InvalidData", func(t *testing.T) {
+		codec := Codec{}
+
+		v := map[string]interface{}{}
+
+		err := codec.Decode([]byte(`invalid data`), v)
+		if err == nil {
+			t.Fatal("expected decoding to fail")
+		}
+
+		t.Logf("decoding failed as expected: %s", err)
+	})
+}
diff --git a/internal/encoding/toml/codec_test.go b/internal/encoding/toml/codec_test.go
new file mode 100644
index 0000000..c7a2756
--- /dev/null
+++ b/internal/encoding/toml/codec_test.go
@@ -0,0 +1,109 @@
+//go:build !viper_toml2
+// +build !viper_toml2
+
+package toml
+
+import (
+	"reflect"
+	"testing"
+)
+
+// original form of the data
+const original = `# key-value pair
+key = "value"
+list = ["item1", "item2", "item3"]
+
+[map]
+key = "value"
+
+# nested
+# map
+[nested_map]
+[nested_map.map]
+key = "value"
+list = [
+  "item1",
+  "item2",
+  "item3",
+]
+`
+
+// encoded form of the data
+const encoded = `key = "value"
+list = ["item1", "item2", "item3"]
+
+[map]
+  key = "value"
+
+[nested_map]
+
+  [nested_map.map]
+    key = "value"
+    list = ["item1", "item2", "item3"]
+`
+
+// Viper's internal representation
+var data = map[string]interface{}{
+	"key": "value",
+	"list": []interface{}{
+		"item1",
+		"item2",
+		"item3",
+	},
+	"map": map[string]interface{}{
+		"key": "value",
+	},
+	"nested_map": map[string]interface{}{
+		"map": map[string]interface{}{
+			"key": "value",
+			"list": []interface{}{
+				"item1",
+				"item2",
+				"item3",
+			},
+		},
+	},
+}
+
+func TestCodec_Encode(t *testing.T) {
+	codec := Codec{}
+
+	b, err := codec.Encode(data)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if encoded != string(b) {
+		t.Fatalf("decoded value does not match the expected one\nactual:   %#v\nexpected: %#v", string(b), encoded)
+	}
+}
+
+func TestCodec_Decode(t *testing.T) {
+	t.Run("OK", func(t *testing.T) {
+		codec := Codec{}
+
+		v := map[string]interface{}{}
+
+		err := codec.Decode([]byte(original), v)
+		if err != nil {
+			t.Fatal(err)
+		}
+
+		if !reflect.DeepEqual(data, v) {
+			t.Fatalf("decoded value does not match the expected one\nactual:   %#v\nexpected: %#v", v, data)
+		}
+	})
+
+	t.Run("InvalidData", func(t *testing.T) {
+		codec := Codec{}
+
+		v := map[string]interface{}{}
+
+		err := codec.Decode([]byte(`invalid data`), v)
+		if err == nil {
+			t.Fatal("expected decoding to fail")
+		}
+
+		t.Logf("decoding failed as expected: %s", err)
+	})
+}
diff --git a/internal/encoding/yaml/codec.go b/internal/encoding/yaml/codec.go
index f94b269..24cc19d 100644
--- a/internal/encoding/yaml/codec.go
+++ b/internal/encoding/yaml/codec.go
@@ -1,14 +1,14 @@
 package yaml
 
-import "gopkg.in/yaml.v2"
+// import "gopkg.in/yaml.v2"
 
 // Codec implements the encoding.Encoder and encoding.Decoder interfaces for YAML encoding.
 type Codec struct{}
 
-func (Codec) Encode(v interface{}) ([]byte, error) {
+func (Codec) Encode(v map[string]interface{}) ([]byte, error) {
 	return yaml.Marshal(v)
 }
 
-func (Codec) Decode(b []byte, v interface{}) error {
-	return yaml.Unmarshal(b, v)
+func (Codec) Decode(b []byte, v map[string]interface{}) error {
+	return yaml.Unmarshal(b, &v)
 }
diff --git a/internal/encoding/yaml/codec_test.go b/internal/encoding/yaml/codec_test.go
new file mode 100644
index 0000000..d24a013
--- /dev/null
+++ b/internal/encoding/yaml/codec_test.go
@@ -0,0 +1,49 @@
+package yaml
+
+import (
+	"reflect"
+	"testing"
+)
+
+func TestCodec_Encode(t *testing.T) {
+	codec := Codec{}
+
+	b, err := codec.Encode(data)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if encoded != string(b) {
+		t.Fatalf("decoded value does not match the expected one\nactual:   %#v\nexpected: %#v", string(b), encoded)
+	}
+}
+
+func TestCodec_Decode(t *testing.T) {
+	t.Run("OK", func(t *testing.T) {
+		codec := Codec{}
+
+		v := map[string]interface{}{}
+
+		err := codec.Decode([]byte(original), v)
+		if err != nil {
+			t.Fatal(err)
+		}
+
+		if !reflect.DeepEqual(decoded, v) {
+			t.Fatalf("decoded value does not match the expected one\nactual:   %#v\nexpected: %#v", v, decoded)
+		}
+	})
+
+	t.Run("InvalidData", func(t *testing.T) {
+		codec := Codec{}
+
+		v := map[string]interface{}{}
+
+		err := codec.Decode([]byte(`invalid data`), v)
+		if err == nil {
+			t.Fatal("expected decoding to fail")
+		}
+
+		t.Logf("decoding failed as expected: %s", err)
+	})
+}
diff --git a/internal/encoding/yaml/yaml2.go b/internal/encoding/yaml/yaml2.go
new file mode 100644
index 0000000..ca29b84
--- /dev/null
+++ b/internal/encoding/yaml/yaml2.go
@@ -0,0 +1,14 @@
+//go:build !viper_yaml3
+// +build !viper_yaml3
+
+package yaml
+
+import yamlv2 "gopkg.in/yaml.v2"
+
+var yaml = struct {
+	Marshal   func(in interface{}) (out []byte, err error)
+	Unmarshal func(in []byte, out interface{}) (err error)
+}{
+	Marshal:   yamlv2.Marshal,
+	Unmarshal: yamlv2.Unmarshal,
+}
diff --git a/internal/encoding/yaml/yaml2_test.go b/internal/encoding/yaml/yaml2_test.go
new file mode 100644
index 0000000..e39d429
--- /dev/null
+++ b/internal/encoding/yaml/yaml2_test.go
@@ -0,0 +1,91 @@
+//go:build !viper_yaml3
+// +build !viper_yaml3
+
+package yaml
+
+// original form of the data
+const original = `# key-value pair
+key: value
+list:
+- item1
+- item2
+- item3
+map:
+  key: value
+
+# nested
+# map
+nested_map:
+  map:
+    key: value
+    list:
+    - item1
+    - item2
+    - item3
+`
+
+// encoded form of the data
+const encoded = `key: value
+list:
+- item1
+- item2
+- item3
+map:
+  key: value
+nested_map:
+  map:
+    key: value
+    list:
+    - item1
+    - item2
+    - item3
+`
+
+// decoded form of the data
+//
+// in case of YAML it's slightly different from Viper's internal representation
+// (eg. map is decoded into a map with interface key)
+var decoded = map[string]interface{}{
+	"key": "value",
+	"list": []interface{}{
+		"item1",
+		"item2",
+		"item3",
+	},
+	"map": map[interface{}]interface{}{
+		"key": "value",
+	},
+	"nested_map": map[interface{}]interface{}{
+		"map": map[interface{}]interface{}{
+			"key": "value",
+			"list": []interface{}{
+				"item1",
+				"item2",
+				"item3",
+			},
+		},
+	},
+}
+
+// Viper's internal representation
+var data = map[string]interface{}{
+	"key": "value",
+	"list": []interface{}{
+		"item1",
+		"item2",
+		"item3",
+	},
+	"map": map[string]interface{}{
+		"key": "value",
+	},
+	"nested_map": map[string]interface{}{
+		"map": map[string]interface{}{
+			"key": "value",
+			"list": []interface{}{
+				"item1",
+				"item2",
+				"item3",
+			},
+		},
+	},
+}
diff --git a/internal/encoding/yaml/yaml3.go b/internal/encoding/yaml/yaml3.go
new file mode 100644
index 0000000..96b8957
--- /dev/null
+++ b/internal/encoding/yaml/yaml3.go
@@ -0,0 +1,14 @@
+//go:build viper_yaml3
+// +build viper_yaml3
+
+package yaml
+
+import yamlv3 "gopkg.in/yaml.v3"
+
+var yaml = struct {
+	Marshal   func(in interface{}) (out []byte, err error)
+	Unmarshal func(in []byte, out interface{}) (err error)
+}{
+	Marshal:   yamlv3.Marshal,
+	Unmarshal: yamlv3.Unmarshal,
+}
diff --git a/internal/encoding/yaml/yaml3_test.go b/internal/encoding/yaml/yaml3_test.go
new file mode 100644
index 0000000..6beb263
--- /dev/null
+++ b/internal/encoding/yaml/yaml3_test.go
@@ -0,0 +1,91 @@
+//go:build viper_yaml3
+// +build viper_yaml3
+
+package yaml
+
+// original form of the data
+const original = `# key-value pair
+key: value
+list:
+    - item1
+    - item2
+    - item3
+map:
+    key: value
+
+# nested
+# map
+nested_map:
+    map:
+        key: value
+        list:
+            - item1
+            - item2
+            - item3
+`
+
+// encoded form of the data
+const encoded = `key: value
+list:
+    - item1
+    - item2
+    - item3
+map:
+    key: value
+nested_map:
+    map:
+        key: value
+        list:
+            - item1
+            - item2
+            - item3
+`
+
+// decoded form of the data
+//
+// in case of YAML it's slightly different from Viper's internal representation
+// (eg. map is decoded into a map with interface key)
+var decoded = map[string]interface{}{
+	"key": "value",
+	"list": []interface{}{
+		"item1",
+		"item2",
+		"item3",
+	},
+	"map": map[string]interface{}{
+		"key": "value",
+	},
+	"nested_map": map[string]interface{}{
+		"map": map[string]interface{}{
+			"key": "value",
+			"list": []interface{}{
+				"item1",
+				"item2",
+				"item3",
+			},
+		},
+	},
+}
+
+// Viper's internal representation
+var data = map[string]interface{}{
+	"key": "value",
+	"list": []interface{}{
+		"item1",
+		"item2",
+		"item3",
+	},
+	"map": map[string]interface{}{
+		"key": "value",
+	},
+	"nested_map": map[string]interface{}{
+		"map": map[string]interface{}{
+			"key": "value",
+			"list": []interface{}{
+				"item1",
+				"item2",
+				"item3",
+			},
+		},
+	},
+}
diff --git a/viper.go b/viper.go
index 4a99358..3481d1d 100644
--- a/viper.go
+++ b/viper.go
@@ -35,16 +35,16 @@ import (
 	"time"
 
 	"github.com/fsnotify/fsnotify"
-	"github.com/magiconair/properties"
 	"github.com/mitchellh/mapstructure"
 	"github.com/spf13/afero"
 	"github.com/spf13/cast"
 	"github.com/spf13/pflag"
-	"github.com/subosito/gotenv"
-	"gopkg.in/ini.v1"
 
 	"github.com/spf13/viper/internal/encoding"
+	"github.com/spf13/viper/internal/encoding/dotenv"
 	"github.com/spf13/viper/internal/encoding/hcl"
+	"github.com/spf13/viper/internal/encoding/ini"
+	"github.com/spf13/viper/internal/encoding/javaproperties"
 	"github.com/spf13/viper/internal/encoding/json"
 	"github.com/spf13/viper/internal/encoding/toml"
 	"github.com/spf13/viper/internal/encoding/yaml"
@@ -67,47 +67,8 @@ type RemoteResponse struct {
 	Error error
 }
 
-var (
-	encoderRegistry = encoding.NewEncoderRegistry()
-	decoderRegistry = encoding.NewDecoderRegistry()
-)
-
 func init() {
 	v = New()
-
-	{
-		codec := yaml.Codec{}
-
-		encoderRegistry.RegisterEncoder("yaml", codec)
-		decoderRegistry.RegisterDecoder("yaml", codec)
-
-		encoderRegistry.RegisterEncoder("yml", codec)
-		decoderRegistry.RegisterDecoder("yml", codec)
-	}
-
-	{
-		codec := json.Codec{}
-
-		encoderRegistry.RegisterEncoder("json", codec)
-		decoderRegistry.RegisterDecoder("json", codec)
-	}
-
-	{
-		codec := toml.Codec{}
-
-		encoderRegistry.RegisterEncoder("toml", codec)
-		decoderRegistry.RegisterDecoder("toml", codec)
-	}
-
-	{
-		codec := hcl.Codec{}
-
-		encoderRegistry.RegisterEncoder("hcl", codec)
-		decoderRegistry.RegisterDecoder("hcl", codec)
-
-		encoderRegistry.RegisterEncoder("tfvars", codec)
-		decoderRegistry.RegisterDecoder("tfvars", codec)
-	}
 }
 
 type remoteConfigFactory interface {
@@ -254,13 +215,13 @@ type Viper struct {
 	aliases        map[string]string
 	typeByDefValue bool
 
-	// Store read properties on the object so that we can write back in order with comments.
-	// This will only be used if the configuration read is a properties file.
-	properties *properties.Properties
-
 	onConfigChange func(fsnotify.Event)
 
 	logger Logger
+
+	// TODO: should probably be protected with a mutex
+	encoderRegistry *encoding.EncoderRegistry
+	decoderRegistry *encoding.DecoderRegistry
 }
 
 // New returns an initialized Viper instance.
@@ -280,6 +241,8 @@ func New() *Viper {
 	v.typeByDefValue = false
 	v.logger = jwwLogger{}
 
+	v.resetEncoding()
+
 	return v
 }
 
@@ -326,6 +289,8 @@ func NewWithOptions(opts ...Option) *Viper {
 		opt.apply(v)
 	}
 
+	v.resetEncoding()
+
 	return v
 }
 
@@ -338,6 +303,84 @@ func Reset() {
 	SupportedRemoteProviders = []string{"etcd", "consul", "firestore"}
 }
 
+// TODO: make this lazy initialization instead
+func (v *Viper) resetEncoding() {
+	encoderRegistry := encoding.NewEncoderRegistry()
+	decoderRegistry := encoding.NewDecoderRegistry()
+
+	{
+		codec := yaml.Codec{}
+
+		encoderRegistry.RegisterEncoder("yaml", codec)
+		decoderRegistry.RegisterDecoder("yaml", codec)
+
+		encoderRegistry.RegisterEncoder("yml", codec)
+		decoderRegistry.RegisterDecoder("yml", codec)
+	}
+
+	{
+		codec := json.Codec{}
+
+		encoderRegistry.RegisterEncoder("json", codec)
+		decoderRegistry.RegisterDecoder("json", codec)
+	}
+
+	{
+		codec := toml.Codec{}
+
+		encoderRegistry.RegisterEncoder("toml", codec)
+		decoderRegistry.RegisterDecoder("toml", codec)
+	}
+
+	{
+		codec := hcl.Codec{}
+
+		encoderRegistry.RegisterEncoder("hcl", codec)
+		decoderRegistry.RegisterDecoder("hcl", codec)
+
+		encoderRegistry.RegisterEncoder("tfvars", codec)
+		decoderRegistry.RegisterDecoder("tfvars", codec)
+	}
+
+	{
+		codec := ini.Codec{
+			KeyDelimiter: v.keyDelim,
+			LoadOptions:  v.iniLoadOptions,
+		}
+
+		encoderRegistry.RegisterEncoder("ini", codec)
+		decoderRegistry.RegisterDecoder("ini", codec)
+	}
+
+	{
+		codec := &javaproperties.Codec{
+			KeyDelimiter: v.keyDelim,
+		}
+
+		encoderRegistry.RegisterEncoder("properties", codec)
+		decoderRegistry.RegisterDecoder("properties", codec)
+
+		encoderRegistry.RegisterEncoder("props", codec)
+		decoderRegistry.RegisterDecoder("props", codec)
+
+		encoderRegistry.RegisterEncoder("prop", codec)
+		decoderRegistry.RegisterDecoder("prop", codec)
+	}
+
+	{
+		codec := &dotenv.Codec{}
+
+		encoderRegistry.RegisterEncoder("dotenv", codec)
+		decoderRegistry.RegisterDecoder("dotenv", codec)
+
+		encoderRegistry.RegisterEncoder("env", codec)
+		decoderRegistry.RegisterDecoder("env", codec)
+	}
+
+	v.encoderRegistry = encoderRegistry
+	v.decoderRegistry = decoderRegistry
+}
+
 type defaultRemoteProvider struct {
 	provider      string
 	endpoint      string
@@ -433,7 +476,7 @@ func (v *Viper) WatchConfig() {
 							v.onConfigChange(event)
 						}
 					} else if filepath.Clean(event.Name) == configFile &&
-						event.Op&fsnotify.Remove&fsnotify.Remove != 0 {
+						event.Op&fsnotify.Remove != 0 {
 						eventsWG.Done()
 						return
 					}
@@ -1634,53 +1677,11 @@ func (v *Viper) unmarshalReader(in io.Reader, c map[string]interface{}) error {
 	buf.ReadFrom(in)
 
 	switch format := strings.ToLower(v.getConfigType()); format {
-	case "yaml", "yml", "json", "toml", "hcl", "tfvars":
-		err := decoderRegistry.Decode(format, buf.Bytes(), &c)
-		if err != nil {
-			return ConfigParseError{err}
-		}
-
-	case "dotenv", "env":
-		env, err := gotenv.StrictParse(buf)
-		if err != nil {
-			return ConfigParseError{err}
-		}
-		for k, v := range env {
-			c[k] = v
-		}
-
-	case "properties", "props", "prop":
-		v.properties = properties.NewProperties()
-		var err error
-		if v.properties, err = properties.Load(buf.Bytes(), properties.UTF8); err != nil {
-			return ConfigParseError{err}
-		}
-		for _, key := range v.properties.Keys() {
-			value, _ := v.properties.Get(key)
-			// recursively build nested maps
-			path := strings.Split(key, ".")
-			lastKey := strings.ToLower(path[len(path)-1])
-			deepestMap := deepSearch(c, path[0:len(path)-1])
-			// set innermost value
-			deepestMap[lastKey] = value
-		}
-
-	case "ini":
-		cfg := ini.Empty(v.iniLoadOptions)
-		err := cfg.Append(buf.Bytes())
+	case "yaml", "yml", "json", "toml", "hcl", "tfvars", "ini", "properties", "props", "prop", "dotenv", "env":
+		err := v.decoderRegistry.Decode(format, buf.Bytes(), c)
 		if err != nil {
 			return ConfigParseError{err}
 		}
-		sections := cfg.Sections()
-		for i := 0; i < len(sections); i++ {
-			section := sections[i]
-			keys := section.Keys()
-			for j := 0; j < len(keys); j++ {
-				key := keys[j]
-				value := cfg.Section(section.Name()).Key(key.Name()).String()
-				c[section.Name()+"."+key.Name()] = value
-			}
-		}
 	}
 
 	insensitiviseMap(c)
@@ -1691,8 +1692,8 @@ func (v *Viper) unmarshalReader(in io.Reader, c map[string]interface{}) error {
 func (v *Viper) marshalWriter(f afero.File, configType string) error {
 	c := v.AllSettings()
 	switch configType {
-	case "yaml", "yml", "json", "toml", "hcl", "tfvars":
-		b, err := encoderRegistry.Encode(configType, c)
+	case "yaml", "yml", "json", "toml", "hcl", "tfvars", "ini", "prop", "props", "properties", "dotenv", "env":
+		b, err := v.encoderRegistry.Encode(configType, c)
 		if err != nil {
 			return ConfigMarshalError{err}
 		}
@@ -1701,50 +1702,6 @@ func (v *Viper) marshalWriter(f afero.File, configType string) error {
 		if err != nil {
 			return ConfigMarshalError{err}
 		}
-
-	case "prop", "props", "properties":
-		if v.properties == nil {
-			v.properties = properties.NewProperties()
-		}
-		p := v.properties
-		for _, key := range v.AllKeys() {
-			_, _, err := p.Set(key, v.GetString(key))
-			if err != nil {
-				return ConfigMarshalError{err}
-			}
-		}
-		_, err := p.WriteComment(f, "#", properties.UTF8)
-		if err != nil {
-			return ConfigMarshalError{err}
-		}
-
-	case "dotenv", "env":
-		lines := []string{}
-		for _, key := range v.AllKeys() {
-			envName := strings.ToUpper(strings.Replace(key, ".", "_", -1))
-			val := v.Get(key)
-			lines = append(lines, fmt.Sprintf("%v=%v", envName, val))
-		}
-		s := strings.Join(lines, "\n")
-		if _, err := f.WriteString(s); err != nil {
-			return ConfigMarshalError{err}
-		}
-
-	case "ini":
-		keys := v.AllKeys()
-		cfg := ini.Empty()
-		ini.PrettyFormat = false
-		for i := 0; i < len(keys); i++ {
-			key := keys[i]
-			lastSep := strings.LastIndex(key, ".")
-			sectionName := key[:(lastSep)]
-			keyName := key[(lastSep + 1):]
-			if sectionName == "default" {
-				sectionName = ""
-			}
-			cfg.Section(sectionName).Key(keyName).SetValue(v.GetString(key))
-		}
-		cfg.WriteTo(f)
 	}
 	return nil
 }
@@ -1823,17 +1780,6 @@ func mergeMaps(
 
 		svType := reflect.TypeOf(sv)
 		tvType := reflect.TypeOf(tv)
-		if tvType != nil && svType != tvType { // Allow for the target to be nil
-			v.logger.Error(
-				"svType != tvType",
-				"key", sk,
-				"st", svType,
-				"tt", tvType,
-				"sv", sv,
-				"tv", tv,
-			)
-			continue
-		}
 
 		v.logger.Trace(
 			"processing",
@@ -1847,13 +1793,27 @@ func mergeMaps(
 		switch ttv := tv.(type) {
 		case map[interface{}]interface{}:
 			v.logger.Trace("merging maps (must convert)")
-			tsv := sv.(map[interface{}]interface{})
+			tsv, ok := sv.(map[interface{}]interface{})
+			if !ok {
+				v.logger.Error(
+					"Could not cast sv to map[interface{}]interface{}; key=%s, st=%v, tt=%v, sv=%v, tv=%v",
+					sk, svType, tvType, sv, tv)
+				continue
+			}
+
 			ssv := castToMapStringInterface(tsv)
 			stv := castToMapStringInterface(ttv)
 			mergeMaps(ssv, stv, ttv)
 		case map[string]interface{}:
 			v.logger.Trace("merging maps")
-			mergeMaps(sv.(map[string]interface{}), ttv, nil)
+			tsv, ok := sv.(map[string]interface{})
+			if !ok {
+				v.logger.Error(
+					"Could not cast sv to map[string]interface{}; key=%s, st=%v, tt=%v, sv=%v, tv=%v",
+					sk, svType, tvType, sv, tv)
+				continue
+			}
+			mergeMaps(tsv, ttv, nil)
 		default:
 			v.logger.Trace("setting value")
 			tgt[tk] = sv
diff --git a/viper_test.go b/viper_test.go
index 8c864f1..c41a1e7 100644
--- a/viper_test.go
+++ b/viper_test.go
@@ -33,21 +33,21 @@ import (
 	"github.com/spf13/viper/internal/testutil"
 )
 
-var yamlExample = []byte(`Hacker: true
-name: steve
-hobbies:
-- skateboarding
-- snowboarding
-- go
-clothing:
-  jacket: leather
-  trousers: denim
-  pants:
-    size: large
-age: 35
-eyes : brown
-beard: true
-`)
+// var yamlExample = []byte(`Hacker: true
+// name: steve
+// hobbies:
+//     - skateboarding
+//     - snowboarding
+//     - go
+// clothing:
+//     jacket: leather
+//     trousers: denim
+//     pants:
+//         size: large
+// age: 35
+// eyes : brown
+// beard: true
+// `)
 
 var yamlExampleWithExtras = []byte(`Existing: true
 Bogus: true
@@ -1558,21 +1558,21 @@ p_ppu = 0.55
 p_batters.batter.type = Regular
 `)
 
-var yamlWriteExpected = []byte(`age: 35
-beard: true
-clothing:
-  jacket: leather
-  pants:
-    size: large
-  trousers: denim
-eyes: brown
-hacker: true
-hobbies:
-- skateboarding
-- snowboarding
-- go
-name: steve
-`)
+// var yamlWriteExpected = []byte(`age: 35
+// beard: true
+// clothing:
+//     jacket: leather
+//     pants:
+//         size: large
+//     trousers: denim
+// eyes: brown
+// hacker: true
+// hobbies:
+//     - skateboarding
+//     - snowboarding
+//     - go
+// name: steve
+// `)
 
 func TestWriteConfig(t *testing.T) {
 	fs := afero.NewMemMapFs()
@@ -1912,6 +1912,24 @@ hello:
 fu: bar
 `)
 
+var jsonMergeExampleTgt = []byte(`
+{
+	"hello": {
+		"foo": null,
+		"pop": 123456
+	}
+}
+`)
+
+var jsonMergeExampleSrc = []byte(`
+{
+	"hello": {
+		"foo": "foo str",
+		"pop": "pop str"
+	}
+}
+`)
+
 func TestMergeConfig(t *testing.T) {
 	v := New()
 	v.SetConfigType("yml")
@@ -1984,6 +2002,26 @@ func TestMergeConfig(t *testing.T) {
 	}
 }
 
+func TestMergeConfigOverrideType(t *testing.T) {
+	v := New()
+	v.SetConfigType("json")
+	if err := v.ReadConfig(bytes.NewBuffer(jsonMergeExampleTgt)); err != nil {
+		t.Fatal(err)
+	}
+
+	if err := v.MergeConfig(bytes.NewBuffer(jsonMergeExampleSrc)); err != nil {
+		t.Fatal(err)
+	}
+
+	if pop := v.GetString("hello.pop"); pop != "pop str" {
+		t.Fatalf("pop != \"pop str\", = %s", pop)
+	}
+
+	if foo := v.GetString("hello.foo"); foo != "foo str" {
+		t.Fatalf("foo != \"foo str\", = %s", foo)
+	}
+}
+
 func TestMergeConfigNoMerge(t *testing.T) {
 	v := New()
 	v.SetConfigType("yml")
@@ -2410,25 +2448,25 @@ func TestUnmarshal_DotSeparatorBackwardCompatibility(t *testing.T) {
 	assert.Equal(t, "cobra_flag", config.Foo.Bar)
 }
 
-var yamlExampleWithDot = []byte(`Hacker: true
-name: steve
-hobbies:
-  - skateboarding
-  - snowboarding
-  - go
-clothing:
-  jacket: leather
-  trousers: denim
-  pants:
-    size: large
-age: 35
-eyes : brown
-beard: true
-emails:
-  steve@hacker.com:
-    created: 01/02/03
-    active: true
-`)
+// var yamlExampleWithDot = []byte(`Hacker: true
+// name: steve
+// hobbies:
+//     - skateboarding
+//     - snowboarding
+//     - go
+// clothing:
+//     jacket: leather
+//     trousers: denim
+//     pants:
+//         size: large
+// age: 35
+// eyes : brown
+// beard: true
+// emails:
+//     steve@hacker.com:
+//         created: 01/02/03
+//         active: true
+// `)
 
 func TestKeyDelimiter(t *testing.T) {
 	v := NewWithOptions(KeyDelimiter("::"))
diff --git a/viper_yaml2_test.go b/viper_yaml2_test.go
new file mode 100644
index 0000000..5a8a51e
--- /dev/null
+++ b/viper_yaml2_test.go
@@ -0,0 +1,56 @@
+//go:build !viper_yaml3
+// +build !viper_yaml3
+
+package viper
+
+var yamlExample = []byte(`Hacker: true
+name: steve
+hobbies:
+- skateboarding
+- snowboarding
+- go
+clothing:
+  jacket: leather
+  trousers: denim
+  pants:
+    size: large
+age: 35
+eyes : brown
+beard: true
+`)
+
+var yamlWriteExpected = []byte(`age: 35
+beard: true
+clothing:
+  jacket: leather
+  pants:
+    size: large
+  trousers: denim
+eyes: brown
+hacker: true
+hobbies:
+- skateboarding
+- snowboarding
+- go
+name: steve
+`)
+
+var yamlExampleWithDot = []byte(`Hacker: true
+name: steve
+hobbies:
+- skateboarding
+- snowboarding
+- go
+clothing:
+  jacket: leather
+  trousers: denim
+  pants:
+    size: large
+age: 35
+eyes : brown
+beard: true
+emails:
+  steve@hacker.com:
+    created: 01/02/03
+    active: true
+`)
diff --git a/viper_yaml3_test.go b/viper_yaml3_test.go
new file mode 100644
index 0000000..c397865
--- /dev/null
+++ b/viper_yaml3_test.go
@@ -0,0 +1,56 @@
+//go:build viper_yaml3
+// +build viper_yaml3
+
+package viper
+
+var yamlExample = []byte(`Hacker: true
+name: steve
+hobbies:
+    - skateboarding
+    - snowboarding
+    - go
+clothing:
+    jacket: leather
+    trousers: denim
+    pants:
+        size: large
+age: 35
+eyes : brown
+beard: true
+`)
+
+var yamlWriteExpected = []byte(`age: 35
+beard: true
+clothing:
+    jacket: leather
+    pants:
+        size: large
+    trousers: denim
+eyes: brown
+hacker: true
+hobbies:
+    - skateboarding
+    - snowboarding
+    - go
+name: steve
+`)
+
+var yamlExampleWithDot = []byte(`Hacker: true
+name: steve
+hobbies:
+    - skateboarding
+    - snowboarding
+    - go
+clothing:
+    jacket: leather
+    trousers: denim
+    pants:
+        size: large
+age: 35
+eyes : brown
+beard: true
+emails:
+    steve@hacker.com:
+        created: 01/02/03
+        active: true
+`)