New Upstream Snapshot - golang-k8s-sigs-yaml

Ready changes

Summary

Merged new upstream version: 1.3.0+git20220809.0.d865f09+ds (was: 1.3.0+git20211208.1.b5bdf49).

Resulting package

Built on 2022-11-19T02:04 (took 17m26s)

The resulting binary packages can be installed (if you have the apt repository enabled) by running one of:

apt install -t fresh-snapshots golang-k8s-sigs-yaml-dev

Lintian Result

Diff

diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml
deleted file mode 100644
index 6a9b57f..0000000
--- a/.github/workflows/go.yml
+++ /dev/null
@@ -1,31 +0,0 @@
-name: Go
-
-on:
-  push:
-    branches: [ master ]
-  pull_request:
-    branches: [ master ]
-jobs:
-  build:
-    strategy:
-      matrix: 
-        go-versions: [1.13.x, 1.14.x, 1.15.x]
-    runs-on: ubuntu-latest
-    steps:
-    - uses: actions/checkout@v2
-    - name: Set up Go
-      uses: actions/setup-go@v2
-      with:
-        go-version: ${{ matrix.go-versions }}
-    - name: Install
-      run: GO111MODULE=off go get golang.org/x/lint/golint
-    - name: Run gofmt
-      run: diff -u <(echo -n) <(gofmt -d *.go)
-    - name: Run golint
-      run: diff -u <(echo -n) <(golint $(go list -e ./...) | grep -v YAMLToJSON)
-    - name: Run go vet
-      run: GO111MODULE=on go vet .
-    - name: Run go test
-      run: GO111MODULE=on go test -v -race ./...
-    - name: Check diff
-      run: git diff --exit-code
diff --git a/.gitignore b/.gitignore
deleted file mode 100644
index 2dc9290..0000000
--- a/.gitignore
+++ /dev/null
@@ -1,24 +0,0 @@
-# OSX leaves these everywhere on SMB shares
-._*
-
-# Eclipse files
-.classpath
-.project
-.settings/**
-
-# Idea files
-.idea/**
-.idea/
-
-# Emacs save files
-*~
-
-# Vim-related files
-[._]*.s[a-w][a-z]
-[._]s[a-w][a-z]
-*.un~
-Session.vim
-.netrwhist
-
-# Go test binaries
-*.test
diff --git a/OWNERS b/OWNERS
index 325b40b..428c2b4 100644
--- a/OWNERS
+++ b/OWNERS
@@ -21,7 +21,6 @@ reviewers:
 - liggitt
 - gmarek
 - sttts
-- ncdc
 - tallclair
 labels:
 - sig/api-machinery
diff --git a/bench_test.go b/bench_test.go
new file mode 100644
index 0000000..107413b
--- /dev/null
+++ b/bench_test.go
@@ -0,0 +1,384 @@
+/*
+Copyright 2022 The Kubernetes Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package yaml
+
+import (
+	"encoding/json"
+	"fmt"
+	"testing"
+
+	"gopkg.in/yaml.v2"
+)
+
+func newBenchmarkObject() interface{} {
+	data := struct {
+		Object map[string]interface{}
+		Items  []interface{}
+	}{
+		Object: map[string]interface{}{
+			"apiVersion": "v1",
+			"kind":       "PodList",
+		},
+		Items: []interface{}{},
+	}
+	for i := 0; i < 1000; i++ {
+		item := struct {
+			Object map[string]interface{}
+		}{
+			Object: map[string]interface{}{
+				"apiVersion": "v1",
+				"kind":       "Pod",
+				"metadata": map[string]interface{}{
+					"creationTimestamp": "2022-04-18T21:03:19Z",
+					"labels": map[string]interface{}{
+						"run": fmt.Sprintf("pod%d", i),
+					},
+					"name":            fmt.Sprintf("pod%d", i),
+					"namespace":       "default",
+					"resourceVersion": "27622089",
+					"uid":             "e8fe9315-3bed-4bb6-a70a-fb697c60deda",
+				},
+				"spec": map[string]interface{}{
+					"containers": map[string]interface{}{
+						"args": []string{
+							"nc",
+							"-lk",
+							"-p",
+							"8080",
+							"-e",
+							"cat",
+						},
+						"image":                    "busybox",
+						"imagePullPolicy":          "Always",
+						"name":                     "echo",
+						"resources":                map[string]interface{}{},
+						"terminationMessagePath":   "/dev/termination-log",
+						"terminationMessagePolicy": "File",
+						"volumeMounts": map[string]interface{}{
+							"mountPath": "/var/run/secrets/kubernetes.io/serviceaccount",
+							"name":      "kube-api-access-cpxzb",
+							"readOnly":  true,
+						},
+					},
+					"dnsPolicy":                     "ClusterFirst",
+					"enableServiceLinks":            true,
+					"nodeName":                      "k8s-worker-1",
+					"preemptionPolicy":              "PreemptLowerPriority",
+					"priority":                      0,
+					"restartPolicy":                 "Always",
+					"schedulerName":                 "default-scheduler",
+					"securityContext":               map[string]interface{}{},
+					"serviceAccount":                "default",
+					"serviceAccountName":            "default",
+					"terminationGracePeriodSeconds": 30,
+					"tolerations": []map[string]interface{}{
+						{
+							"effect":            "NoExecute",
+							"key":               "node.kubernetes.io/not-ready",
+							"operator":          "Exists",
+							"tolerationSeconds": 300,
+						},
+						{
+							"effect":            "NoExecute",
+							"key":               "node.kubernetes.io/unreachable",
+							"operator":          "Exists",
+							"tolerationSeconds": 300,
+						},
+					},
+					"volumes": []map[string]interface{}{
+						{
+							"name": "kube-api-access-cpxzb",
+							"projected": map[string]interface{}{
+								"defaultMode": 420,
+								"sources": []map[string]interface{}{
+									{
+										"serviceAccountToken": map[string]interface{}{
+											"expirationSeconds": 3607,
+											"path":              "token",
+										},
+									},
+									{
+										"configMap": map[string]interface{}{
+											"items": []map[string]interface{}{
+												{
+													"key":  "ca.crt",
+													"path": "ca.crt",
+												},
+											},
+											"name": "kube-root-ca.crt",
+										},
+									},
+									{
+										"downwardAPI": map[string]interface{}{
+											"items": []map[string]interface{}{
+												{
+													"fieldRef": map[string]interface{}{
+														"apiVersion": "v1",
+														"fieldPath":  "metadata.namespace",
+													},
+													"path": "namespace",
+												},
+											},
+										},
+									},
+								},
+							},
+						},
+					},
+					"status": map[string]interface{}{
+						"conditions": []map[string]interface{}{
+							{
+								"lastProbeTime":      nil,
+								"lastTransitionTime": "2022-04-18T21:03:19Z",
+								"status":             "True",
+								"type":               "Initialized",
+							},
+							{
+								"lastProbeTime":      nil,
+								"lastTransitionTime": "2022-04-18T21:03:20Z",
+								"status":             "True",
+								"type":               "Ready",
+							},
+							{
+								"lastProbeTime":      nil,
+								"lastTransitionTime": "2022-04-18T21:03:20Z",
+								"status":             "True",
+								"type":               "ContainersReady",
+							},
+							{
+								"lastProbeTime":      nil,
+								"lastTransitionTime": "2022-04-18T21:03:19Z",
+								"status":             "True",
+								"type":               "PodScheduled",
+							},
+						},
+						"containerStatuses": []map[string]interface{}{
+							{
+								"containerID":  "containerd://ed8afc051a21749e911a4dd4671e520dc81c8e1424853b6254872a3f461bb157",
+								"image":        "docker.io/library/busybox:latest",
+								"imageID":      "docker.io/library/busybox@sha256:d2b53584f580310186df7a2055ce3ff83cc0df6caacf1e3489bff8cf5d0af5d8",
+								"lastState":    map[string]interface{}{},
+								"name":         "echo",
+								"ready":        true,
+								"restartCount": 0,
+								"started":      true,
+								"state": map[string]interface{}{
+									"running": map[string]interface{}{
+										"startedAt": "2022-04-18T21:03:20Z",
+									},
+								},
+							},
+						},
+						"hostIP": "192.168.200.12",
+						"phase":  "Running",
+						"podIP":  "10.244.1.248",
+						"podIPs": []map[string]interface{}{
+							{
+								"ip": "10.244.1.248",
+							},
+						},
+						"qosClass":  "BestEffort",
+						"startTime": "2022-04-18T21:03:19Z",
+					},
+				},
+			},
+		}
+		data.Items = append(data.Items, item)
+	}
+	return data
+}
+
+func newBenchmarkYAML() ([]byte, error) {
+	return yaml.Marshal(newBenchmarkObject())
+}
+
+func BenchmarkMarshal(b *testing.B) {
+	// Setup
+	obj := newBenchmarkObject()
+
+	// Record the number of bytes per operation
+	result, err := Marshal(obj)
+	if err != nil {
+		b.Errorf("error marshaling YAML: %v", err)
+	}
+	b.SetBytes(int64(len(result)))
+
+	// Start the benchmark
+	b.ResetTimer()
+	b.ReportAllocs()
+	b.RunParallel(func(pb *testing.PB) {
+		for pb.Next() {
+			if _, err := Marshal(obj); err != nil {
+				b.Errorf("error marshaling YAML: %v", err)
+			}
+		}
+	})
+}
+
+func BenchmarkUnmarshal(b *testing.B) {
+	// Setup
+	yamlBytes, err := newBenchmarkYAML()
+	if err != nil {
+		b.Fatalf("error initializing YAML: %v", err)
+	}
+
+	// Record the number of bytes per operation
+	b.SetBytes(int64(len(yamlBytes)))
+
+	// Start the benchmark
+	b.ResetTimer()
+	b.ReportAllocs()
+	b.RunParallel(func(pb *testing.PB) {
+		for pb.Next() {
+			var result interface{}
+			if err = Unmarshal(yamlBytes, &result); err != nil {
+				b.Errorf("error unmarshaling YAML: %v", err)
+			}
+		}
+	})
+}
+
+func BenchmarkUnmarshalStrict(b *testing.B) {
+	// Setup
+	yamlBytes, err := newBenchmarkYAML()
+	if err != nil {
+		b.Fatalf("error initializing YAML: %v", err)
+	}
+
+	// Record the number of bytes per operation
+	b.SetBytes(int64(len(yamlBytes)))
+
+	// Start the benchmark
+	b.ResetTimer()
+	b.ReportAllocs()
+	b.RunParallel(func(pb *testing.PB) {
+		for pb.Next() {
+			var result interface{}
+			if err = UnmarshalStrict(yamlBytes, &result); err != nil {
+				b.Errorf("error unmarshaling YAML (Strict): %v", err)
+			}
+		}
+	})
+}
+
+func BenchmarkJSONToYAML(b *testing.B) {
+	// Setup
+	yamlBytes, err := newBenchmarkYAML()
+	if err != nil {
+		b.Fatalf("error initializing YAML: %v", err)
+	}
+	jsonBytes, err := YAMLToJSON(yamlBytes)
+	if err != nil {
+		b.Fatalf("error initializing JSON: %v", err)
+	}
+
+	// Record the number of bytes per operation
+	result, err := JSONToYAML(jsonBytes)
+	if err != nil {
+		b.Errorf("error converting JSON to YAML: %v", err)
+	}
+	b.SetBytes(int64(len(result)))
+
+	// Start the benchmark
+	b.ResetTimer()
+	b.ReportAllocs()
+	b.RunParallel(func(pb *testing.PB) {
+		for pb.Next() {
+			if _, err := JSONToYAML(jsonBytes); err != nil {
+				b.Errorf("error converting JSON to YAML: %v", err)
+			}
+		}
+	})
+}
+
+func BenchmarkYAMLtoJSON(b *testing.B) {
+	// Setup
+	yamlBytes, err := newBenchmarkYAML()
+	if err != nil {
+		b.Fatalf("error initializing YAML: %v", err)
+	}
+
+	// Record the number of bytes per operation
+	result, err := YAMLToJSON(yamlBytes)
+	if err != nil {
+		b.Errorf("error converting YAML to JSON: %v", err)
+	}
+	b.SetBytes(int64(len(result)))
+
+	// Start the benchmark
+	b.ResetTimer()
+	b.ReportAllocs()
+	b.RunParallel(func(pb *testing.PB) {
+		for pb.Next() {
+			if _, err := YAMLToJSON(yamlBytes); err != nil {
+				b.Errorf("error converting YAML to JSON: %v", err)
+			}
+		}
+	})
+}
+
+func BenchmarkYAMLtoJSONStrict(b *testing.B) {
+	// Setup
+	yamlBytes, err := newBenchmarkYAML()
+	if err != nil {
+		b.Fatalf("error initializing YAML: %v", err)
+	}
+
+	// Record the number of bytes per operation
+	result, err := YAMLToJSONStrict(yamlBytes)
+	if err != nil {
+		b.Errorf("error converting YAML to JSON (Strict): %v", err)
+	}
+	b.SetBytes(int64(len(result)))
+
+	// Start the benchmark
+	b.ResetTimer()
+	b.ReportAllocs()
+	b.RunParallel(func(pb *testing.PB) {
+		for pb.Next() {
+			if _, err := YAMLToJSONStrict(yamlBytes); err != nil {
+				b.Errorf("error converting YAML to JSON (Strict): %v", err)
+			}
+		}
+	})
+}
+
+func BenchmarkJSONObjectToYAMLObject(b *testing.B) {
+	// Setup
+	yamlBytes, err := newBenchmarkYAML()
+	if err != nil {
+		b.Fatalf("error initializing YAML: %v", err)
+	}
+	jsonBytes, err := YAMLToJSON(yamlBytes)
+	if err != nil {
+		b.Fatalf("error initializing JSON: %v", err)
+	}
+	var m map[string]interface{}
+	err = json.Unmarshal(jsonBytes, &m)
+	if err != nil {
+		b.Fatalf("error initializing map: %v", err)
+	}
+
+	// Start the benchmark
+	b.ResetTimer()
+	b.ReportAllocs()
+	b.RunParallel(func(pb *testing.PB) {
+		for pb.Next() {
+			JSONObjectToYAMLObject(m)
+		}
+	})
+}
diff --git a/debian/changelog b/debian/changelog
index db67651..e4af59c 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,10 @@
+golang-k8s-sigs-yaml (1.3.0+git20220809.0.d865f09+ds-1) UNRELEASED; urgency=low
+
+  * New upstream snapshot.
+  * New upstream snapshot.
+
+ -- Debian Janitor <janitor@jelmer.uk>  Sat, 19 Nov 2022 01:55:39 -0000
+
 golang-k8s-sigs-yaml (1.3.0-1) unstable; urgency=medium
 
   * New upstream release v1.3.0
diff --git a/debian/patches/0001-Fix-failed-test-on-32-bit-arch.patch b/debian/patches/0001-Fix-failed-test-on-32-bit-arch.patch
index 02ee147..a614b16 100644
--- a/debian/patches/0001-Fix-failed-test-on-32-bit-arch.patch
+++ b/debian/patches/0001-Fix-failed-test-on-32-bit-arch.patch
@@ -6,11 +6,11 @@ Subject: Fix failed test on 32 bit arch
  yaml_test.go | 2 +-
  1 file changed, 1 insertion(+), 1 deletion(-)
 
-diff --git a/yaml_test.go b/yaml_test.go
-index 9da43e1..117dda5 100644
---- a/yaml_test.go
-+++ b/yaml_test.go
-@@ -24,7 +24,7 @@ type MarshalTest struct {
+Index: golang-k8s-sigs-yaml.git/yaml_test.go
+===================================================================
+--- golang-k8s-sigs-yaml.git.orig/yaml_test.go
++++ golang-k8s-sigs-yaml.git/yaml_test.go
+@@ -181,7 +181,7 @@ type MarshalTest struct {
  func TestMarshal(t *testing.T) {
  	f32String := strconv.FormatFloat(math.MaxFloat32, 'g', -1, 32)
  	s := MarshalTest{"a", math.MaxInt64, math.MaxFloat32}
diff --git a/fields.go b/fields.go
index 235b7f2..0ea28bd 100644
--- a/fields.go
+++ b/fields.go
@@ -16,53 +16,53 @@ import (
 	"unicode/utf8"
 )
 
-// indirect walks down v allocating pointers as needed,
+// indirect walks down 'value' allocating pointers as needed,
 // until it gets to a non-pointer.
 // if it encounters an Unmarshaler, indirect stops and returns that.
 // if decodingNull is true, indirect stops at the last pointer so it can be set to nil.
-func indirect(v reflect.Value, decodingNull bool) (json.Unmarshaler, encoding.TextUnmarshaler, reflect.Value) {
-	// If v is a named type and is addressable,
+func indirect(value reflect.Value, decodingNull bool) (json.Unmarshaler, encoding.TextUnmarshaler, reflect.Value) {
+	// If 'value' is a named type and is addressable,
 	// start with its address, so that if the type has pointer methods,
 	// we find them.
-	if v.Kind() != reflect.Ptr && v.Type().Name() != "" && v.CanAddr() {
-		v = v.Addr()
+	if value.Kind() != reflect.Ptr && value.Type().Name() != "" && value.CanAddr() {
+		value = value.Addr()
 	}
 	for {
 		// Load value from interface, but only if the result will be
 		// usefully addressable.
-		if v.Kind() == reflect.Interface && !v.IsNil() {
-			e := v.Elem()
-			if e.Kind() == reflect.Ptr && !e.IsNil() && (!decodingNull || e.Elem().Kind() == reflect.Ptr) {
-				v = e
+		if value.Kind() == reflect.Interface && !value.IsNil() {
+			element := value.Elem()
+			if element.Kind() == reflect.Ptr && !element.IsNil() && (!decodingNull || element.Elem().Kind() == reflect.Ptr) {
+				value = element
 				continue
 			}
 		}
 
-		if v.Kind() != reflect.Ptr {
+		if value.Kind() != reflect.Ptr {
 			break
 		}
 
-		if v.Elem().Kind() != reflect.Ptr && decodingNull && v.CanSet() {
+		if value.Elem().Kind() != reflect.Ptr && decodingNull && value.CanSet() {
 			break
 		}
-		if v.IsNil() {
-			if v.CanSet() {
-				v.Set(reflect.New(v.Type().Elem()))
+		if value.IsNil() {
+			if value.CanSet() {
+				value.Set(reflect.New(value.Type().Elem()))
 			} else {
-				v = reflect.New(v.Type().Elem())
+				value = reflect.New(value.Type().Elem())
 			}
 		}
-		if v.Type().NumMethod() > 0 {
-			if u, ok := v.Interface().(json.Unmarshaler); ok {
+		if value.Type().NumMethod() > 0 {
+			if u, ok := value.Interface().(json.Unmarshaler); ok {
 				return u, nil, reflect.Value{}
 			}
-			if u, ok := v.Interface().(encoding.TextUnmarshaler); ok {
+			if u, ok := value.Interface().(encoding.TextUnmarshaler); ok {
 				return nil, u, reflect.Value{}
 			}
 		}
-		v = v.Elem()
+		value = value.Elem()
 	}
-	return nil, nil, v
+	return nil, nil, value
 }
 
 // A field represents a single field found in a struct.
@@ -134,8 +134,8 @@ func typeFields(t reflect.Type) []field {
 	next := []field{{typ: t}}
 
 	// Count of queued names for current level and the next.
-	count := map[reflect.Type]int{}
-	nextCount := map[reflect.Type]int{}
+	var count map[reflect.Type]int
+	var nextCount map[reflect.Type]int
 
 	// Types already visited at an earlier level.
 	visited := map[reflect.Type]bool{}
@@ -348,8 +348,9 @@ const (
 // 4) simpleLetterEqualFold, no specials, no non-letters.
 //
 // The letters S and K are special because they map to 3 runes, not just 2:
-//  * S maps to s and to U+017F 'ſ' Latin small letter long s
-//  * k maps to K and to U+212A 'K' Kelvin sign
+//   - S maps to s and to U+017F 'ſ' Latin small letter long s
+//   - k maps to K and to U+212A 'K' Kelvin sign
+//
 // See http://play.golang.org/p/tTxjOc0OGo
 //
 // The returned function is specialized for matching against s and
@@ -420,10 +421,8 @@ func equalFoldRight(s, t []byte) bool {
 		t = t[size:]
 
 	}
-	if len(t) > 0 {
-		return false
-	}
-	return true
+
+	return len(t) <= 0
 }
 
 // asciiEqualFold is a specialization of bytes.EqualFold for use when
diff --git a/yaml.go b/yaml.go
index efbc535..39d8c24 100644
--- a/yaml.go
+++ b/yaml.go
@@ -1,3 +1,19 @@
+/*
+Copyright 2021 The Kubernetes Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
 package yaml
 
 import (
@@ -11,53 +27,56 @@ import (
 	"gopkg.in/yaml.v2"
 )
 
-// Marshal marshals the object into JSON then converts JSON to YAML and returns the
-// YAML.
-func Marshal(o interface{}) ([]byte, error) {
-	j, err := json.Marshal(o)
+// Marshal marshals obj into JSON using stdlib json.Marshal, and then converts JSON to YAML using JSONToYAML (see that method for more reference)
+func Marshal(obj interface{}) ([]byte, error) {
+	jsonBytes, err := json.Marshal(obj)
 	if err != nil {
-		return nil, fmt.Errorf("error marshaling into JSON: %v", err)
+		return nil, fmt.Errorf("error marshaling into JSON: %w", err)
 	}
 
-	y, err := JSONToYAML(j)
-	if err != nil {
-		return nil, fmt.Errorf("error converting JSON to YAML: %v", err)
-	}
-
-	return y, nil
+	return JSONToYAML(jsonBytes)
 }
 
 // JSONOpt is a decoding option for decoding from JSON format.
 type JSONOpt func(*json.Decoder) *json.Decoder
 
-// Unmarshal converts YAML to JSON then uses JSON to unmarshal into an object,
-// optionally configuring the behavior of the JSON unmarshal.
-func Unmarshal(y []byte, o interface{}, opts ...JSONOpt) error {
-	return yamlUnmarshal(y, o, false, opts...)
+// Unmarshal first converts the given YAML to JSON, and then unmarshals the JSON into obj. Options for the
+// standard library json.Decoder can be optionally specified, e.g. to decode untyped numbers into json.Number instead of float64, or to disallow unknown fields (but for that purpose, see also UnmarshalStrict). obj must be a non-nil pointer.
+//
+// Important notes about the Unmarshal logic:
+//
+//   - Decoding is case-insensitive, unlike the rest of Kubernetes API machinery, as this is using the stdlib json library. This might be confusing to users.
+//   - This decodes any number (although it is an integer) into a float64 if the type of obj is unknown, e.g. *map[string]interface{}, *interface{}, or *[]interface{}. This means integers above +/- 2^53 will lose precision when round-tripping. Make a JSONOpt that calls d.UseNumber() to avoid this.
+//   - Duplicate fields, including in-case-sensitive matches, are ignored in an undefined order. Note that the YAML specification forbids duplicate fields, so this logic is more permissive than it needs to. See UnmarshalStrict for an alternative.
+//   - Unknown fields, i.e. serialized data that do not map to a field in obj, are ignored. Use d.DisallowUnknownFields() or UnmarshalStrict to override.
+//   - As per the YAML 1.1 specification, which yaml.v2 used underneath implements, literal 'yes' and 'no' strings without quotation marks will be converted to true/false implicitly.
+//   - YAML non-string keys, e.g. ints, bools and floats, are converted to strings implicitly during the YAML to JSON conversion process.
+//   - There are no compatibility guarantees for returned error values.
+func Unmarshal(yamlBytes []byte, obj interface{}, opts ...JSONOpt) error {
+	return unmarshal(yamlBytes, obj, yaml.Unmarshal, opts...)
 }
 
-// UnmarshalStrict strictly converts YAML to JSON then uses JSON to unmarshal
-// into an object, optionally configuring the behavior of the JSON unmarshal.
-func UnmarshalStrict(y []byte, o interface{}, opts ...JSONOpt) error {
-	return yamlUnmarshal(y, o, true, append(opts, DisallowUnknownFields)...)
+// UnmarshalStrict is similar to Unmarshal (please read its documentation for reference), with the following exceptions:
+//
+//   - Duplicate fields in an object yield an error. This is according to the YAML specification.
+//   - If obj, or any of its recursive children, is a struct, presence of fields in the serialized data unknown to the struct will yield an error.
+func UnmarshalStrict(yamlBytes []byte, obj interface{}, opts ...JSONOpt) error {
+	return unmarshal(yamlBytes, obj, yaml.UnmarshalStrict, append(opts, DisallowUnknownFields)...)
 }
 
-// yamlUnmarshal unmarshals the given YAML byte stream into the given interface,
+// unmarshal unmarshals the given YAML byte stream into the given interface,
 // optionally performing the unmarshalling strictly
-func yamlUnmarshal(y []byte, o interface{}, strict bool, opts ...JSONOpt) error {
-	vo := reflect.ValueOf(o)
-	unmarshalFn := yaml.Unmarshal
-	if strict {
-		unmarshalFn = yaml.UnmarshalStrict
-	}
-	j, err := yamlToJSON(y, &vo, unmarshalFn)
+func unmarshal(yamlBytes []byte, obj interface{}, unmarshalFn func([]byte, interface{}) error, opts ...JSONOpt) error {
+	jsonTarget := reflect.ValueOf(obj)
+
+	jsonBytes, err := yamlToJSONTarget(yamlBytes, &jsonTarget, unmarshalFn)
 	if err != nil {
-		return fmt.Errorf("error converting YAML to JSON: %v", err)
+		return fmt.Errorf("error converting YAML to JSON: %w", err)
 	}
 
-	err = jsonUnmarshal(bytes.NewReader(j), o, opts...)
+	err = jsonUnmarshal(bytes.NewReader(jsonBytes), obj, opts...)
 	if err != nil {
-		return fmt.Errorf("error unmarshaling JSON: %v", err)
+		return fmt.Errorf("error unmarshaling JSON: %w", err)
 	}
 
 	return nil
@@ -67,21 +86,23 @@ func yamlUnmarshal(y []byte, o interface{}, strict bool, opts ...JSONOpt) error
 // object, optionally applying decoder options prior to decoding.  We are not
 // using json.Unmarshal directly as we want the chance to pass in non-default
 // options.
-func jsonUnmarshal(r io.Reader, o interface{}, opts ...JSONOpt) error {
-	d := json.NewDecoder(r)
+func jsonUnmarshal(reader io.Reader, obj interface{}, opts ...JSONOpt) error {
+	d := json.NewDecoder(reader)
 	for _, opt := range opts {
 		d = opt(d)
 	}
-	if err := d.Decode(&o); err != nil {
-		return fmt.Errorf("while decoding JSON: %v", err)
-	}
-	return nil
+	return d.Decode(obj)
 }
 
-// JSONToYAML Converts JSON to YAML.
+// JSONToYAML converts JSON to YAML. Notable implementation details:
+//
+//   - Duplicate fields, are case-sensitively ignored in an undefined order.
+//   - The sequence indentation style is compact, which means that the "- " marker for a YAML sequence will be on the same indentation level as the sequence field name.
+//   - Unlike Unmarshal, all integers, up to 64 bits, are preserved during this round-trip.
 func JSONToYAML(j []byte) ([]byte, error) {
 	// Convert the JSON to an object.
 	var jsonObj interface{}
+
 	// We are using yaml.Unmarshal here (instead of json.Unmarshal) because the
 	// Go JSON library doesn't try to pick the right number type (int, float,
 	// etc.) when unmarshalling to interface{}, it just picks float64
@@ -89,41 +110,52 @@ func JSONToYAML(j []byte) ([]byte, error) {
 	// number type, so we can preserve number type throughout this process.
 	err := yaml.Unmarshal(j, &jsonObj)
 	if err != nil {
-		return nil, err
+		return nil, fmt.Errorf("error converting JSON to YAML: %w", err)
 	}
 
 	// Marshal this object into YAML.
-	return yaml.Marshal(jsonObj)
+	yamlBytes, err := yaml.Marshal(jsonObj)
+	if err != nil {
+		return nil, fmt.Errorf("error converting JSON to YAML: %w", err)
+	}
+
+	return yamlBytes, nil
 }
 
 // YAMLToJSON converts YAML to JSON. Since JSON is a subset of YAML,
 // passing JSON through this method should be a no-op.
 //
-// Things YAML can do that are not supported by JSON:
-// * In YAML you can have binary and null keys in your maps. These are invalid
-//   in JSON. (int and float keys are converted to strings.)
-// * Binary data in YAML with the !!binary tag is not supported. If you want to
-//   use binary data with this library, encode the data as base64 as usual but do
-//   not use the !!binary tag in your YAML. This will ensure the original base64
-//   encoded data makes it all the way through to the JSON.
+// Some things YAML can do that are not supported by JSON:
+//   - In YAML you can have binary and null keys in your maps. These are invalid
+//     in JSON, and therefore int, bool and float keys are converted to strings implicitly.
+//   - Binary data in YAML with the !!binary tag is not supported. If you want to
+//     use binary data with this library, encode the data as base64 as usual but do
+//     not use the !!binary tag in your YAML. This will ensure the original base64
+//     encoded data makes it all the way through to the JSON.
+//   - And more... read the YAML specification for more details.
+//
+// Notable about the implementation:
 //
-// For strict decoding of YAML, use YAMLToJSONStrict.
+// - Duplicate fields are case-sensitively ignored in an undefined order. Note that the YAML specification forbids duplicate fields, so this logic is more permissive than it needs to. See YAMLToJSONStrict for an alternative.
+// - As per the YAML 1.1 specification, which yaml.v2 used underneath implements, literal 'yes' and 'no' strings without quotation marks will be converted to true/false implicitly.
+// - Unlike Unmarshal, all integers, up to 64 bits, are preserved during this round-trip.
+// - There are no compatibility guarantees for returned error values.
 func YAMLToJSON(y []byte) ([]byte, error) {
-	return yamlToJSON(y, nil, yaml.Unmarshal)
+	return yamlToJSONTarget(y, nil, yaml.Unmarshal)
 }
 
 // YAMLToJSONStrict is like YAMLToJSON but enables strict YAML decoding,
 // returning an error on any duplicate field names.
 func YAMLToJSONStrict(y []byte) ([]byte, error) {
-	return yamlToJSON(y, nil, yaml.UnmarshalStrict)
+	return yamlToJSONTarget(y, nil, yaml.UnmarshalStrict)
 }
 
-func yamlToJSON(y []byte, jsonTarget *reflect.Value, yamlUnmarshal func([]byte, interface{}) error) ([]byte, error) {
+func yamlToJSONTarget(yamlBytes []byte, jsonTarget *reflect.Value, unmarshalFn func([]byte, interface{}) error) ([]byte, error) {
 	// Convert the YAML to an object.
 	var yamlObj interface{}
-	err := yamlUnmarshal(y, &yamlObj)
+	err := unmarshalFn(yamlBytes, &yamlObj)
 	if err != nil {
-		return nil, err
+		return nil, fmt.Errorf("error converting YAML to JSON: %w", err)
 	}
 
 	// YAML objects are not completely compatible with JSON objects (e.g. you
@@ -132,11 +164,15 @@ func yamlToJSON(y []byte, jsonTarget *reflect.Value, yamlUnmarshal func([]byte,
 	// incompatibilties happen along the way.
 	jsonObj, err := convertToJSONableObject(yamlObj, jsonTarget)
 	if err != nil {
-		return nil, err
+		return nil, fmt.Errorf("error converting YAML to JSON: %w", err)
 	}
 
 	// Convert this object to JSON and return the data.
-	return json.Marshal(jsonObj)
+	jsonBytes, err := json.Marshal(jsonObj)
+	if err != nil {
+		return nil, fmt.Errorf("error converting YAML to JSON: %w", err)
+	}
+	return jsonBytes, nil
 }
 
 func convertToJSONableObject(yamlObj interface{}, jsonTarget *reflect.Value) (interface{}, error) {
@@ -147,13 +183,13 @@ func convertToJSONableObject(yamlObj interface{}, jsonTarget *reflect.Value) (in
 	// decoding into the value, we're just checking if the ultimate target is a
 	// string.
 	if jsonTarget != nil {
-		ju, tu, pv := indirect(*jsonTarget, false)
+		jsonUnmarshaler, textUnmarshaler, pointerValue := indirect(*jsonTarget, false)
 		// We have a JSON or Text Umarshaler at this level, so we can't be trying
 		// to decode into a string.
-		if ju != nil || tu != nil {
+		if jsonUnmarshaler != nil || textUnmarshaler != nil {
 			jsonTarget = nil
 		} else {
-			jsonTarget = &pv
+			jsonTarget = &pointerValue
 		}
 	}
 
@@ -205,7 +241,7 @@ func convertToJSONableObject(yamlObj interface{}, jsonTarget *reflect.Value) (in
 					keyString = "false"
 				}
 			default:
-				return nil, fmt.Errorf("Unsupported map key of type: %s, key: %+#v, value: %+#v",
+				return nil, fmt.Errorf("unsupported map key of type: %s, key: %+#v, value: %+#v",
 					reflect.TypeOf(k), k, v)
 			}
 
diff --git a/yaml_go110.go b/yaml_go110.go
index ab3e06a..94abc17 100644
--- a/yaml_go110.go
+++ b/yaml_go110.go
@@ -1,7 +1,24 @@
 // This file contains changes that are only compatible with go 1.10 and onwards.
 
+//go:build go1.10
 // +build go1.10
 
+/*
+Copyright 2021 The Kubernetes Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
 package yaml
 
 import "encoding/json"
diff --git a/yaml_go110_test.go b/yaml_go110_test.go
index e4707e8..629fff4 100644
--- a/yaml_go110_test.go
+++ b/yaml_go110_test.go
@@ -1,5 +1,22 @@
+//go:build go1.10
 // +build go1.10
 
+/*
+Copyright 2021 The Kubernetes Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
 package yaml
 
 import (
diff --git a/yaml_test.go b/yaml_test.go
index 9da43e1..ba40b71 100644
--- a/yaml_test.go
+++ b/yaml_test.go
@@ -1,3 +1,19 @@
+/*
+Copyright 2021 The Kubernetes Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
 package yaml
 
 import (
@@ -13,6 +29,147 @@ import (
 	yaml "gopkg.in/yaml.v2"
 )
 
+/* Test helper functions */
+
+func strPtr(str string) *string {
+	return &str
+}
+
+type errorType int
+
+const (
+	noErrorsType    errorType = 0
+	fatalErrorsType errorType = 1 << iota
+)
+
+type unmarshalTestCase struct {
+	encoded    []byte
+	decodeInto interface{}
+	decoded    interface{}
+	err        errorType
+}
+
+type testUnmarshalFunc = func(yamlBytes []byte, obj interface{}) error
+
+var (
+	funcUnmarshal testUnmarshalFunc = func(yamlBytes []byte, obj interface{}) error {
+		return Unmarshal(yamlBytes, obj)
+	}
+
+	funcUnmarshalStrict testUnmarshalFunc = func(yamlBytes []byte, obj interface{}) error {
+		return UnmarshalStrict(yamlBytes, obj)
+	}
+)
+
+func testUnmarshal(t *testing.T, f testUnmarshalFunc, tests map[string]unmarshalTestCase) {
+	for testName, test := range tests {
+		t.Run(testName, func(t *testing.T) {
+			typ := reflect.TypeOf(test.decodeInto)
+			if typ.Kind() != reflect.Ptr {
+				t.Errorf("unmarshalTest.ptr %T is not a pointer type", test.decodeInto)
+			}
+
+			value := reflect.New(typ.Elem())
+
+			if !reflect.DeepEqual(test.decodeInto, value.Interface()) {
+				// There's no reason for ptr to point to non-zero data,
+				// as we decode into new(right-type), so the data is
+				// discarded.
+				// This can easily mean tests that silently don't test
+				// what they should. To test decoding into existing
+				// data, see TestPrefilled.
+				t.Errorf("unmarshalTest.ptr %#v is not a pointer to a zero value", test.decodeInto)
+			}
+
+			err := f(test.encoded, value.Interface())
+			if err != nil && test.err == noErrorsType {
+				t.Errorf("error unmarshaling YAML: %v", err)
+			}
+			if err == nil && test.err&fatalErrorsType != 0 {
+				t.Errorf("expected a fatal error, but no fatal error was returned, yaml: `%s`", test.encoded)
+			}
+
+			if test.err&fatalErrorsType != 0 {
+				// Don't check output if error is fatal
+				return
+			}
+
+			if !reflect.DeepEqual(value.Elem().Interface(), test.decoded) {
+				t.Errorf("unmarshal YAML was unsuccessful, expected: %+#v, got: %+#v", test.decoded, value.Elem().Interface())
+			}
+		})
+	}
+}
+
+type yamlToJSONTestcase struct {
+	yaml string
+	json string
+	// By default we test that reversing the output == input. But if there is a
+	// difference in the reversed output, you can optionally specify it here.
+	yamlReverseOverwrite *string
+	err                  errorType
+}
+
+type testYAMLToJSONFunc = func(yamlBytes []byte) ([]byte, error)
+
+var (
+	funcYAMLToJSON testYAMLToJSONFunc = func(yamlBytes []byte) ([]byte, error) {
+		return YAMLToJSON(yamlBytes)
+	}
+
+	funcYAMLToJSONStrict testYAMLToJSONFunc = func(yamlBytes []byte) ([]byte, error) {
+		return YAMLToJSONStrict(yamlBytes)
+	}
+)
+
+func testYAMLToJSON(t *testing.T, f testYAMLToJSONFunc, tests map[string]yamlToJSONTestcase) {
+	for testName, test := range tests {
+		t.Run(fmt.Sprintf("%s_YAMLToJSON", testName), func(t *testing.T) {
+			// Convert Yaml to Json
+			jsonBytes, err := f([]byte(test.yaml))
+			if err != nil && test.err == noErrorsType {
+				t.Errorf("Failed to convert YAML to JSON, yaml: `%s`, err: %v", test.yaml, err)
+			}
+			if err == nil && test.err&fatalErrorsType != 0 {
+				t.Errorf("expected a fatal error, but no fatal error was returned, yaml: `%s`", test.yaml)
+			}
+
+			if test.err&fatalErrorsType != 0 {
+				// Don't check output if error is fatal
+				return
+			}
+
+			// Check it against the expected output.
+			if string(jsonBytes) != test.json {
+				t.Errorf("Failed to convert YAML to JSON, yaml: `%s`, expected json `%s`, got `%s`", test.yaml, test.json, string(jsonBytes))
+			}
+		})
+
+		t.Run(fmt.Sprintf("%s_JSONToYAML", testName), func(t *testing.T) {
+			// Convert JSON to YAML
+			yamlBytes, err := JSONToYAML([]byte(test.json))
+			if err != nil {
+				t.Errorf("Failed to convert JSON to YAML, json: `%s`, err: %v", test.json, err)
+			}
+
+			// Set the string that we will compare the reversed output to.
+			correctYamlString := test.yaml
+
+			// If a special reverse string was specified, use that instead.
+			if test.yamlReverseOverwrite != nil {
+				correctYamlString = *test.yamlReverseOverwrite
+			}
+
+			// Check it against the expected output.
+			if string(yamlBytes) != correctYamlString {
+				t.Errorf("Failed to convert JSON to YAML, json: `%s`, expected yaml `%s`, got `%s`", test.json, correctYamlString, string(yamlBytes))
+			}
+		})
+	}
+}
+
+/* Start tests */
+
 type MarshalTest struct {
 	A string
 	B int64
@@ -37,398 +194,583 @@ func TestMarshal(t *testing.T) {
 	}
 }
 
-type UnmarshalString struct {
+type UnmarshalUntaggedStruct struct {
 	A    string
 	True string
 }
 
-type UnmarshalStringMap struct {
-	A map[string]string
+type UnmarshalTaggedStruct struct {
+	AUpper            string `json:"A"`
+	ALower            string `json:"a"`
+	TrueUpper         string `json:"True"`
+	TrueLower         string `json:"true"`
+	YesUpper          string `json:"Yes"`
+	YesLower          string `json:"yes"`
+	Int3              string `json:"3"`
+	IntBig1           string `json:"9007199254740993"` // 2^53 + 1
+	IntBig2           string `json:"1000000000000000000000000000000000000"`
+	IntBig2Scientific string `json:"1e+36"`
+	Float3dot3        string `json:"3.3"`
 }
 
-type UnmarshalNestedString struct {
-	A NestedString
+type UnmarshalStruct struct {
+	A string  `json:"a"`
+	B *string `json:"b"`
+	C string  `json:"c"`
 }
 
-type NestedString struct {
-	A string
+type UnmarshalStringMap struct {
+	A map[string]string `json:"a"`
 }
 
-type UnmarshalSlice struct {
-	A []NestedSlice
+type UnmarshalNestedStruct struct {
+	A UnmarshalStruct `json:"a"`
 }
 
-type NestedSlice struct {
-	B string
-	C *string
+type UnmarshalSlice struct {
+	A []UnmarshalStruct `json:"a"`
 }
 
-func TestUnmarshal(t *testing.T) {
-	y := []byte("a: 1")
-	s1 := UnmarshalString{}
-	e1 := UnmarshalString{A: "1"}
-	unmarshal(t, y, &s1, &e1)
-
-	y = []byte("a: true")
-	s1 = UnmarshalString{}
-	e1 = UnmarshalString{A: "true"}
-	unmarshal(t, y, &s1, &e1)
-
-	y = []byte("true: 1")
-	s1 = UnmarshalString{}
-	e1 = UnmarshalString{True: "1"}
-	unmarshal(t, y, &s1, &e1)
-
-	y = []byte("a:\n  a: 1")
-	s2 := UnmarshalNestedString{}
-	e2 := UnmarshalNestedString{NestedString{"1"}}
-	unmarshal(t, y, &s2, &e2)
-
-	y = []byte("a:\n  - b: abc\n    c: def\n  - b: 123\n    c: 456\n")
-	s3 := UnmarshalSlice{}
-	e3 := UnmarshalSlice{[]NestedSlice{NestedSlice{"abc", strPtr("def")}, NestedSlice{"123", strPtr("456")}}}
-	unmarshal(t, y, &s3, &e3)
-
-	y = []byte("a:\n  b: 1")
-	s4 := UnmarshalStringMap{}
-	e4 := UnmarshalStringMap{map[string]string{"b": "1"}}
-	unmarshal(t, y, &s4, &e4)
-
-	y = []byte(`
-a:
-  name: TestA
-b:
-  name: TestB
-`)
-	type NamedThing struct {
-		Name string `json:"name"`
-	}
-	s5 := map[string]*NamedThing{}
-	e5 := map[string]*NamedThing{
-		"a": &NamedThing{Name: "TestA"},
-		"b": &NamedThing{Name: "TestB"},
-	}
-	unmarshal(t, y, &s5, &e5)
+type UnmarshalEmbedStruct struct {
+	UnmarshalStruct
+	B string `json:"b"`
 }
 
-func unmarshal(t *testing.T, y []byte, s, e interface{}, opts ...JSONOpt) {
-	err := Unmarshal(y, s, opts...)
-	if err != nil {
-		t.Errorf("error unmarshaling YAML: %v", err)
-	}
-
-	if !reflect.DeepEqual(s, e) {
-		t.Errorf("unmarshal YAML was unsuccessful, expected: %+#v, got: %+#v",
-			e, s)
-	}
+type UnmarshalEmbedStructPointer struct {
+	*UnmarshalStruct
+	B string `json:"b"`
 }
 
-func TestUnmarshalStrict(t *testing.T) {
-	y := []byte("a: 1")
-	s1 := UnmarshalString{}
-	e1 := UnmarshalString{A: "1"}
-	unmarshalStrict(t, y, &s1, &e1)
-
-	y = []byte("a: true")
-	s1 = UnmarshalString{}
-	e1 = UnmarshalString{A: "true"}
-	unmarshalStrict(t, y, &s1, &e1)
-
-	y = []byte("true: 1")
-	s1 = UnmarshalString{}
-	e1 = UnmarshalString{True: "1"}
-	unmarshalStrict(t, y, &s1, &e1)
-
-	y = []byte("a:\n  a: 1")
-	s2 := UnmarshalNestedString{}
-	e2 := UnmarshalNestedString{NestedString{"1"}}
-	unmarshalStrict(t, y, &s2, &e2)
-
-	y = []byte("a:\n  - b: abc\n    c: def\n  - b: 123\n    c: 456\n")
-	s3 := UnmarshalSlice{}
-	e3 := UnmarshalSlice{[]NestedSlice{NestedSlice{"abc", strPtr("def")}, NestedSlice{"123", strPtr("456")}}}
-	unmarshalStrict(t, y, &s3, &e3)
-
-	y = []byte("a:\n  b: 1")
-	s4 := UnmarshalStringMap{}
-	e4 := UnmarshalStringMap{map[string]string{"b": "1"}}
-	unmarshalStrict(t, y, &s4, &e4)
-
-	y = []byte(`
-a:
-  name: TestA
-b:
-  name: TestB
-`)
-	type NamedThing struct {
-		Name string `json:"name"`
-	}
-	s5 := map[string]*NamedThing{}
-	e5 := map[string]*NamedThing{
-		"a": &NamedThing{Name: "TestA"},
-		"b": &NamedThing{Name: "TestB"},
-	}
-	unmarshal(t, y, &s5, &e5)
-
-	// When using not-so-strict unmarshal, we should
-	// be picking up the ID-1 as the value in the "id" field
-	y = []byte(`
-a:
-  name: TestA
-  id: ID-A
-  id: ID-1
-`)
-	type NamedThing2 struct {
-		Name string `json:"name"`
-		ID   string `json:"id"`
-	}
-	s6 := map[string]*NamedThing2{}
-	e6 := map[string]*NamedThing2{
-		"a": {Name: "TestA", ID: "ID-1"},
-	}
-	unmarshal(t, y, &s6, &e6)
+type UnmarshalEmbedRecursiveStruct struct {
+	*UnmarshalEmbedRecursiveStruct `json:"a"`
+	B                              string `json:"b"`
 }
 
-func TestUnmarshalStrictFails(t *testing.T) {
-	y := []byte("a: true\na: false")
-	s1 := UnmarshalString{}
-	unmarshalStrictFail(t, y, &s1)
+func TestUnmarshal(t *testing.T) {
+	tests := map[string]unmarshalTestCase{
+		// casematched / non-casematched untagged keys
+		"untagged casematched string key": {
+			encoded:    []byte("A: test"),
+			decodeInto: new(UnmarshalUntaggedStruct),
+			decoded:    UnmarshalUntaggedStruct{A: "test"},
+		},
+		"untagged non-casematched string key": {
+			encoded:    []byte("a: test"),
+			decodeInto: new(UnmarshalUntaggedStruct),
+			decoded:    UnmarshalUntaggedStruct{A: "test"},
+		},
+		"untagged casematched boolean key": {
+			encoded:    []byte("True: test"),
+			decodeInto: new(UnmarshalUntaggedStruct),
+			decoded:    UnmarshalUntaggedStruct{True: "test"},
+		},
+		"untagged non-casematched boolean key": {
+			encoded:    []byte("true: test"),
+			decodeInto: new(UnmarshalUntaggedStruct),
+			decoded:    UnmarshalUntaggedStruct{True: "test"},
+		},
 
-	y = []byte("a:\n  - b: abc\n    c: 32\n      b: 123")
-	s2 := UnmarshalSlice{}
-	unmarshalStrictFail(t, y, &s2)
+		// casematched / non-casematched tagged keys
+		"tagged casematched string key": {
+			encoded:    []byte("A: test"),
+			decodeInto: new(UnmarshalTaggedStruct),
+			decoded:    UnmarshalTaggedStruct{AUpper: "test"},
+		},
+		"tagged non-casematched string key": {
+			encoded:    []byte("a: test"),
+			decodeInto: new(UnmarshalTaggedStruct),
+			decoded:    UnmarshalTaggedStruct{ALower: "test"},
+		},
+		"tagged casematched boolean key": {
+			encoded:    []byte("True: test"),
+			decodeInto: new(UnmarshalTaggedStruct),
+			decoded:    UnmarshalTaggedStruct{TrueLower: "test"},
+		},
+		"tagged non-casematched boolean key": {
+			encoded:    []byte("true: test"),
+			decodeInto: new(UnmarshalTaggedStruct),
+			decoded:    UnmarshalTaggedStruct{TrueLower: "test"},
+		},
+		"tagged casematched boolean key (yes)": {
+			encoded:    []byte("Yes: test"),
+			decodeInto: new(UnmarshalTaggedStruct),
+			decoded:    UnmarshalTaggedStruct{TrueLower: "test"},
+		},
+		"tagged non-casematched boolean key (yes)": {
+			encoded:    []byte("yes: test"),
+			decodeInto: new(UnmarshalTaggedStruct),
+			decoded:    UnmarshalTaggedStruct{TrueLower: "test"},
+		},
+		"tagged integer key": {
+			encoded:    []byte("3: test"),
+			decodeInto: new(UnmarshalTaggedStruct),
+			decoded:    UnmarshalTaggedStruct{Int3: "test"},
+		},
+		"tagged big integer key 2^53 + 1": {
+			encoded:    []byte("9007199254740993: test"),
+			decodeInto: new(UnmarshalTaggedStruct),
+			decoded:    UnmarshalTaggedStruct{IntBig1: "test"},
+		},
+		"tagged big integer key 1000000000000000000000000000000000000": {
+			encoded:    []byte("1000000000000000000000000000000000000: test"),
+			decodeInto: new(UnmarshalTaggedStruct),
+			decoded:    UnmarshalTaggedStruct{IntBig2Scientific: "test"},
+		},
+		"tagged float key": {
+			encoded:    []byte("3.3: test"),
+			decodeInto: new(UnmarshalTaggedStruct),
+			decoded:    UnmarshalTaggedStruct{Float3dot3: "test"},
+		},
 
-	y = []byte("a:\n  b: 1\n    c: 3")
-	s3 := UnmarshalStringMap{}
-	unmarshalStrictFail(t, y, &s3)
+		// decode into string field
+		"string value into string field": {
+			encoded:    []byte("a: test"),
+			decodeInto: new(UnmarshalStruct),
+			decoded:    UnmarshalStruct{A: "test"},
+		},
+		"integer value into string field": {
+			encoded:    []byte("a: 1"),
+			decodeInto: new(UnmarshalStruct),
+			decoded:    UnmarshalStruct{A: "1"},
+		},
+		"boolean value into string field": {
+			encoded:    []byte("a: true"),
+			decodeInto: new(UnmarshalStruct),
+			decoded:    UnmarshalStruct{A: "true"},
+		},
+		"boolean value (no) into string field": {
+			encoded:    []byte("a: no"),
+			decodeInto: new(UnmarshalStruct),
+			decoded:    UnmarshalStruct{A: "false"},
+		},
 
-	type NamedThing struct {
-		Name string `json:"name"`
-		ID   string `json:"id"`
-	}
-	// When using strict unmarshal, we should see
-	// the unmarshal fail if there are multiple keys
-	y = []byte(`
-a:
-  name: TestA
-  id: ID-A
-  id: ID-1
-`)
-	s4 := NamedThing{}
-	unmarshalStrictFail(t, y, &s4)
-
-	// Strict unmarshal should fail for unknown fields
-	y = []byte(`
-name: TestB
-id: ID-B
-unknown: Some-Value
-`)
-	s5 := NamedThing{}
-	unmarshalStrictFail(t, y, &s5)
-}
+		// decode into complex fields
+		"decode into nested struct": {
+			encoded:    []byte("a:\n  a: 1"),
+			decodeInto: new(UnmarshalNestedStruct),
+			decoded:    UnmarshalNestedStruct{UnmarshalStruct{A: "1"}},
+		},
+		"decode into slice": {
+			encoded:    []byte("a:\n  - a: abc\n    b: def\n  - a: 123"),
+			decodeInto: new(UnmarshalSlice),
+			decoded:    UnmarshalSlice{[]UnmarshalStruct{{A: "abc", B: strPtr("def")}, {A: "123"}}},
+		},
+		"decode into string map": {
+			encoded:    []byte("a:\n  b: 1"),
+			decodeInto: new(UnmarshalStringMap),
+			decoded:    UnmarshalStringMap{map[string]string{"b": "1"}},
+		},
+		"decode into struct pointer map": {
+			encoded:    []byte("a:\n  a: TestA\nb:\n  a: TestB\n  b: TestC"),
+			decodeInto: new(map[string]*UnmarshalStruct),
+			decoded: map[string]*UnmarshalStruct{
+				"a": {A: "TestA"},
+				"b": {A: "TestB", B: strPtr("TestC")},
+			},
+		},
 
-func unmarshalStrict(t *testing.T, y []byte, s, e interface{}, opts ...JSONOpt) {
-	err := UnmarshalStrict(y, s, opts...)
-	if err != nil {
-		t.Errorf("error unmarshaling YAML: %v", err)
-	}
+		// decoding into string map
+		"string map: decode string key": {
+			encoded:    []byte("a:"),
+			decodeInto: new(map[string]struct{}),
+			decoded: map[string]struct{}{
+				"a": {},
+			},
+		},
+		"string map: decode boolean key": {
+			encoded:    []byte("True:"),
+			decodeInto: new(map[string]struct{}),
+			decoded: map[string]struct{}{
+				"true": {},
+			},
+		},
+		"string map: decode boolean key (yes)": {
+			encoded:    []byte("Yes:"),
+			decodeInto: new(map[string]struct{}),
+			decoded: map[string]struct{}{
+				"true": {},
+			},
+		},
+		"string map: decode integer key": {
+			encoded:    []byte("44:"),
+			decodeInto: new(map[string]struct{}),
+			decoded: map[string]struct{}{
+				"44": {},
+			},
+		},
+		"string map: decode float key": {
+			encoded:    []byte("444.444:"),
+			decodeInto: new(map[string]struct{}),
+			decoded: map[string]struct{}{
+				"444.444": {},
+			},
+		},
 
-	if !reflect.DeepEqual(s, e) {
-		t.Errorf("unmarshal YAML was unsuccessful, expected: %+#v, got: %+#v",
-			e, s)
-	}
-}
+		// decoding integers
+		"decode 2^53 + 1 into int": {
+			encoded:    []byte("9007199254740993"),
+			decodeInto: new(int),
+			decoded:    9007199254740993,
+		},
+		"decode 2^53 + 1 into interface": {
+			encoded:    []byte("9007199254740993"),
+			decodeInto: new(interface{}),
+			decoded:    9.007199254740992e+15,
+		},
 
-func unmarshalStrictFail(t *testing.T, y []byte, s interface{}, opts ...JSONOpt) {
-	err := UnmarshalStrict(y, s, opts...)
-	if err == nil {
-		t.Errorf("error unmarshaling YAML: %v", err)
-	}
-}
+		// decode into interface
+		"float into interface": {
+			encoded:    []byte("3.0"),
+			decodeInto: new(interface{}),
+			decoded:    float64(3),
+		},
+		"integer into interface": {
+			encoded:    []byte("3"),
+			decodeInto: new(interface{}),
+			decoded:    float64(3),
+		},
+		"empty vs empty string into interface": {
+			encoded:    []byte("a: \"\"\nb: \n"),
+			decodeInto: new(interface{}),
+			decoded: map[string]interface{}{
+				"a": "",
+				"b": nil,
+			},
+		},
 
-type Case struct {
-	input  string
-	output string
-	// By default we test that reversing the output == input. But if there is a
-	// difference in the reversed output, you can optionally specify it here.
-	reverse *string
-}
+		// duplicate (non-casematched) keys (NOTE: this is very non-ideal behaviour!)
+		"decode duplicate (non-casematched) into nested struct 1": {
+			encoded:    []byte("a:\n  a: 1\n  b: 1\n  c: test\n\nA:\n  a: 2"),
+			decodeInto: new(UnmarshalNestedStruct),
+			decoded:    UnmarshalNestedStruct{A: UnmarshalStruct{A: "1", B: strPtr("1"), C: "test"}},
+		},
+		"decode duplicate (non-casematched) into nested struct 2": {
+			encoded:    []byte("A:\n  a: 1\n  b: 1\n  c: test\na:\n  a: 2"),
+			decodeInto: new(UnmarshalNestedStruct),
+			decoded:    UnmarshalNestedStruct{A: UnmarshalStruct{A: "2", B: strPtr("1"), C: "test"}},
+		},
+		"decode duplicate (non-casematched) into nested slice 1": {
+			encoded:    []byte("a:\n  - a: abc\n    b: def\nA:\n  - a: 123"),
+			decodeInto: new(UnmarshalSlice),
+			decoded:    UnmarshalSlice{[]UnmarshalStruct{{A: "abc", B: strPtr("def")}}},
+		},
+		"decode duplicate (non-casematched) into nested slice 2": {
+			encoded:    []byte("A:\n  - a: abc\n    b: def\na:\n  - a: 123"),
+			decodeInto: new(UnmarshalSlice),
+			decoded:    UnmarshalSlice{[]UnmarshalStruct{{A: "123", B: strPtr("def")}}},
+		},
+		"decode duplicate (non-casematched) into nested string map 1": {
+			encoded:    []byte("a:\n  b: 1\nA:\n  c: 1"),
+			decodeInto: new(UnmarshalStringMap),
+			decoded:    UnmarshalStringMap{map[string]string{"b": "1", "c": "1"}},
+		},
+		"decode duplicate (non-casematched) into nested string map 2": {
+			encoded:    []byte("A:\n  b: 1\na:\n  c: 1"),
+			decodeInto: new(UnmarshalStringMap),
+			decoded:    UnmarshalStringMap{map[string]string{"b": "1", "c": "1"}},
+		},
+		"decode duplicate (non-casematched) into string map": {
+			encoded:    []byte("a: test\nb: test\nA: test2"),
+			decodeInto: new(map[string]string),
+			decoded: map[string]string{
+				"a": "test",
+				"A": "test2",
+				"b": "test",
+			},
+		},
 
-type RunType int
+		// decoding embeded structs
+		"decode embeded struct": {
+			encoded:    []byte("a: testA\nb: testB"),
+			decodeInto: new(UnmarshalEmbedStruct),
+			decoded: UnmarshalEmbedStruct{
+				UnmarshalStruct: UnmarshalStruct{
+					A: "testA",
+				},
+				B: "testB",
+			},
+		},
+		"decode embeded structpointer": {
+			encoded:    []byte("a: testA\nb: testB"),
+			decodeInto: new(UnmarshalEmbedStructPointer),
+			decoded: UnmarshalEmbedStructPointer{
+				UnmarshalStruct: &UnmarshalStruct{
+					A: "testA",
+				},
+				B: "testB",
+			},
+		},
+		"decode recursive embeded structpointer": {
+			encoded:    []byte("b: testB\na:\n  b: testA"),
+			decodeInto: new(UnmarshalEmbedRecursiveStruct),
+			decoded: UnmarshalEmbedRecursiveStruct{
+				UnmarshalEmbedRecursiveStruct: &UnmarshalEmbedRecursiveStruct{
+					B: "testA",
+				},
+				B: "testB",
+			},
+		},
 
-const (
-	RunTypeJSONToYAML RunType = iota
-	RunTypeYAMLToJSON
-)
+		// BUG: type info gets lost (#58)
+		"decode embeded struct and cast integer to string": {
+			encoded:    []byte("a: 11\nb: testB"),
+			decodeInto: new(UnmarshalEmbedStruct),
+			decoded: UnmarshalEmbedStruct{
+				UnmarshalStruct: UnmarshalStruct{
+					A: "11",
+				},
+				B: "testB",
+			},
+			err: fatalErrorsType,
+		},
+		"decode embeded structpointer and cast integer to string": {
+			encoded:    []byte("a: 11\nb: testB"),
+			decodeInto: new(UnmarshalEmbedStructPointer),
+			decoded: UnmarshalEmbedStructPointer{
+				UnmarshalStruct: &UnmarshalStruct{
+					A: "11",
+				},
+				B: "testB",
+			},
+			err: fatalErrorsType,
+		},
 
-func TestJSONToYAML(t *testing.T) {
-	cases := []Case{
-		{
-			`{"t":"a"}`,
-			"t: a\n",
-			nil,
-		}, {
-			`{"t":null}`,
-			"t: null\n",
-			nil,
-		}, {
-			`{"t":"this is very long line with spaces and it must be longer than 80 so we will repeat that it must be longer that 80"}`,
-			"t: this is very long line with spaces and it must be longer than 80 so we will repeat\n  that it must be longer that 80\n",
-			nil,
+		// decoding into incompatible type
+		"decode into stringmap with incompatible type": {
+			encoded:    []byte("a:\n  a:\n    a: 3"),
+			decodeInto: new(UnmarshalStringMap),
+			err:        fatalErrorsType,
 		},
 	}
 
-	runCases(t, RunTypeJSONToYAML, cases)
+	t.Run("Unmarshal", func(t *testing.T) {
+		testUnmarshal(t, funcUnmarshal, tests)
+	})
+
+	t.Run("UnmarshalStrict", func(t *testing.T) {
+		testUnmarshal(t, funcUnmarshalStrict, tests)
+	})
 }
 
-func TestYAMLToJSON(t *testing.T) {
-	cases := []Case{
-		{
-			"t: a\n",
-			`{"t":"a"}`,
-			nil,
-		}, {
-			"t: \n",
-			`{"t":null}`,
-			strPtr("t: null\n"),
-		}, {
-			"t: null\n",
-			`{"t":null}`,
-			nil,
-		}, {
-			"1: a\n",
-			`{"1":"a"}`,
-			strPtr("\"1\": a\n"),
-		}, {
-			"1000000000000000000000000000000000000: a\n",
-			`{"1e+36":"a"}`,
-			strPtr("\"1e+36\": a\n"),
-		}, {
-			"1e+36: a\n",
-			`{"1e+36":"a"}`,
-			strPtr("\"1e+36\": a\n"),
-		}, {
-			"\"1e+36\": a\n",
-			`{"1e+36":"a"}`,
-			nil,
-		}, {
-			"\"1.2\": a\n",
-			`{"1.2":"a"}`,
-			nil,
-		}, {
-			"- t: a\n",
-			`[{"t":"a"}]`,
-			nil,
-		}, {
-			"- t: a\n" +
-				"- t:\n" +
-				"    b: 1\n" +
-				"    c: 2\n",
-			`[{"t":"a"},{"t":{"b":1,"c":2}}]`,
-			nil,
-		}, {
-			`[{t: a}, {t: {b: 1, c: 2}}]`,
-			`[{"t":"a"},{"t":{"b":1,"c":2}}]`,
-			strPtr("- t: a\n" +
-				"- t:\n" +
-				"    b: 1\n" +
-				"    c: 2\n"),
-		}, {
-			"- t: \n",
-			`[{"t":null}]`,
-			strPtr("- t: null\n"),
-		}, {
-			"- t: null\n",
-			`[{"t":null}]`,
-			nil,
+func TestUnmarshalStrictFails(t *testing.T) {
+	tests := map[string]unmarshalTestCase{
+		// decoding with duplicate values
+		"decode into struct pointer map with duplicate string value": {
+			encoded:    []byte("a:\n  a: TestA\n  b: ID-A\n  b: ID-1"),
+			decodeInto: new(map[string]*UnmarshalStruct),
+			decoded: map[string]*UnmarshalStruct{
+				"a": {A: "TestA", B: strPtr("ID-1")},
+			},
 		},
-	}
-
-	// Cases that should produce errors.
-	_ = []Case{
-		{
-			"~: a",
-			`{"null":"a"}`,
-			nil,
-		}, {
-			"a: !!binary gIGC\n",
-			"{\"a\":\"\x80\x81\x82\"}",
-			nil,
+		"decode into string field with duplicate boolean value": {
+			encoded:    []byte("a: true\na: false"),
+			decodeInto: new(UnmarshalStruct),
+			decoded:    UnmarshalStruct{A: "false"},
+		},
+		"decode into slice with duplicate string-boolean value": {
+			encoded:    []byte("a:\n- b: abc\n  a: 32\n  b: 123"),
+			decodeInto: new(UnmarshalSlice),
+			decoded:    UnmarshalSlice{[]UnmarshalStruct{{A: "32", B: strPtr("123")}}},
 		},
-	}
 
-	runCases(t, RunTypeYAMLToJSON, cases)
-}
+		// decoding with unknown fields
+		"decode into struct with unknown field": {
+			encoded:    []byte("a: TestB\nb: ID-B\nunknown: Some-Value"),
+			decodeInto: new(UnmarshalStruct),
+			decoded:    UnmarshalStruct{A: "TestB", B: strPtr("ID-B")},
+		},
 
-func runCases(t *testing.T, runType RunType, cases []Case) {
-	var f func([]byte) ([]byte, error)
-	var invF func([]byte) ([]byte, error)
-	var msg string
-	var invMsg string
-	if runType == RunTypeJSONToYAML {
-		f = JSONToYAML
-		invF = YAMLToJSON
-		msg = "JSON to YAML"
-		invMsg = "YAML back to JSON"
-	} else {
-		f = YAMLToJSON
-		invF = JSONToYAML
-		msg = "YAML to JSON"
-		invMsg = "JSON back to YAML"
+		// decoding with duplicate complex values
+		"decode duplicate into nested struct": {
+			encoded:    []byte("a:\n  a: 1\na:\n  a: 2"),
+			decodeInto: new(UnmarshalNestedStruct),
+			decoded:    UnmarshalNestedStruct{A: UnmarshalStruct{A: "2"}},
+		},
+		"decode duplicate into nested slice": {
+			encoded:    []byte("a:\n  - a: abc\n    b: def\na:\n  - a: 123"),
+			decodeInto: new(UnmarshalSlice),
+			decoded:    UnmarshalSlice{[]UnmarshalStruct{{A: "123"}}},
+		},
+		"decode duplicate into nested string map": {
+			encoded:    []byte("a:\n  b: 1\na:\n  c: 1"),
+			decodeInto: new(UnmarshalStringMap),
+			decoded:    UnmarshalStringMap{map[string]string{"c": "1"}},
+		},
+		"decode duplicate into string map": {
+			encoded:    []byte("a: test\nb: test\na: test2"),
+			decodeInto: new(map[string]string),
+			decoded: map[string]string{
+				"a": "test2",
+				"b": "test",
+			},
+		},
 	}
 
-	for _, c := range cases {
-		// Convert the string.
-		t.Logf("converting %s\n", c.input)
-		output, err := f([]byte(c.input))
-		if err != nil {
-			t.Errorf("Failed to convert %s, input: `%s`, err: %v", msg, c.input, err)
-		}
-
-		// Check it against the expected output.
-		if string(output) != c.output {
-			t.Errorf("Failed to convert %s, input: `%s`, expected `%s`, got `%s`",
-				msg, c.input, c.output, string(output))
-		}
+	t.Run("Unmarshal", func(t *testing.T) {
+		testUnmarshal(t, funcUnmarshal, tests)
+	})
 
-		// Set the string that we will compare the reversed output to.
-		reverse := c.input
-		// If a special reverse string was specified, use that instead.
-		if c.reverse != nil {
-			reverse = *c.reverse
+	t.Run("UnmarshalStrict", func(t *testing.T) {
+		failTests := map[string]unmarshalTestCase{}
+		for name, test := range tests {
+			test.err = fatalErrorsType
+			failTests[name] = test
 		}
+		testUnmarshal(t, funcUnmarshalStrict, failTests)
+	})
+}
 
-		// Reverse the output.
-		input, err := invF(output)
-		if err != nil {
-			t.Errorf("Failed to convert %s, input: `%s`, err: %v", invMsg, string(output), err)
-		}
+func TestYAMLToJSON(t *testing.T) {
+	tests := map[string]yamlToJSONTestcase{
+		"string value": {
+			yaml: "t: a\n",
+			json: `{"t":"a"}`,
+		},
+		"null value": {
+			yaml: "t: null\n",
+			json: `{"t":null}`,
+		},
+		"boolean value": {
+			yaml:                 "t: True\n",
+			json:                 `{"t":true}`,
+			yamlReverseOverwrite: strPtr("t: true\n"),
+		},
+		"boolean value (no)": {
+			yaml:                 "t: no\n",
+			json:                 `{"t":false}`,
+			yamlReverseOverwrite: strPtr("t: false\n"),
+		},
+		"integer value (2^53 + 1)": {
+			yaml:                 "t: 9007199254740993\n",
+			json:                 `{"t":9007199254740993}`,
+			yamlReverseOverwrite: strPtr("t: 9007199254740993\n"),
+		},
+		"integer value (1000000000000000000000000000000000000)": {
+			yaml:                 "t: 1000000000000000000000000000000000000\n",
+			json:                 `{"t":1e+36}`,
+			yamlReverseOverwrite: strPtr("t: 1e+36\n"),
+		},
+		"line-wrapped string value": {
+			yaml: "t: this is very long line with spaces and it must be longer than 80 so we will repeat\n  that it must be longer that 80\n",
+			json: `{"t":"this is very long line with spaces and it must be longer than 80 so we will repeat that it must be longer that 80"}`,
+		},
+		"empty yaml value": {
+			yaml:                 "t: ",
+			json:                 `{"t":null}`,
+			yamlReverseOverwrite: strPtr("t: null\n"),
+		},
+		"boolean key": {
+			yaml:                 "True: a",
+			json:                 `{"true":"a"}`,
+			yamlReverseOverwrite: strPtr("\"true\": a\n"),
+		},
+		"boolean key (no)": {
+			yaml:                 "no: a",
+			json:                 `{"false":"a"}`,
+			yamlReverseOverwrite: strPtr("\"false\": a\n"),
+		},
+		"integer key": {
+			yaml:                 "1: a",
+			json:                 `{"1":"a"}`,
+			yamlReverseOverwrite: strPtr("\"1\": a\n"),
+		},
+		"float key": {
+			yaml:                 "1.2: a",
+			json:                 `{"1.2":"a"}`,
+			yamlReverseOverwrite: strPtr("\"1.2\": a\n"),
+		},
+		"large integer key": {
+			yaml:                 "1000000000000000000000000000000000000: a",
+			json:                 `{"1e+36":"a"}`,
+			yamlReverseOverwrite: strPtr("\"1e+36\": a\n"),
+		},
+		"large integer key (scientific notation)": {
+			yaml:                 "1e+36: a",
+			json:                 `{"1e+36":"a"}`,
+			yamlReverseOverwrite: strPtr("\"1e+36\": a\n"),
+		},
+		"string key (large integer as string)": {
+			yaml: "\"1e+36\": a\n",
+			json: `{"1e+36":"a"}`,
+		},
+		"string key (float as string)": {
+			yaml: "\"1.2\": a\n",
+			json: `{"1.2":"a"}`,
+		},
+		"array": {
+			yaml: "- t: a\n",
+			json: `[{"t":"a"}]`,
+		},
+		"nested struct array": {
+			yaml: "- t: a\n- t:\n    b: 1\n    c: 2\n",
+			json: `[{"t":"a"},{"t":{"b":1,"c":2}}]`,
+		},
+		"nested struct array (json notation)": {
+			yaml:                 `[{t: a}, {t: {b: 1, c: 2}}]`,
+			json:                 `[{"t":"a"},{"t":{"b":1,"c":2}}]`,
+			yamlReverseOverwrite: strPtr("- t: a\n- t:\n    b: 1\n    c: 2\n"),
+		},
+		"empty struct value": {
+			yaml:                 "- t: ",
+			json:                 `[{"t":null}]`,
+			yamlReverseOverwrite: strPtr("- t: null\n"),
+		},
+		"null struct value": {
+			yaml: "- t: null\n",
+			json: `[{"t":null}]`,
+		},
+		"binary data": {
+			yaml:                 "a: !!binary gIGC",
+			json:                 `{"a":"\ufffd\ufffd\ufffd"}`,
+			yamlReverseOverwrite: strPtr("a: \ufffd\ufffd\ufffd\n"),
+		},
 
-		// Check the reverse is equal to the input (or to *c.reverse).
-		if string(input) != reverse {
-			t.Errorf("Failed to convert %s, input: `%s`, expected `%s`, got `%s`",
-				invMsg, string(output), reverse, string(input))
-		}
+		// Cases that should produce errors.
+		"~ key": {
+			yaml:                 "~: a",
+			json:                 `{"null":"a"}`,
+			yamlReverseOverwrite: strPtr("\"null\": a\n"),
+			err:                  fatalErrorsType,
+		},
+		"null key": {
+			yaml:                 "null: a",
+			json:                 `{"null":"a"}`,
+			yamlReverseOverwrite: strPtr("\"null\": a\n"),
+			err:                  fatalErrorsType,
+		},
 	}
 
-}
+	t.Run("YAMLToJSON", func(t *testing.T) {
+		testYAMLToJSON(t, funcYAMLToJSON, tests)
+	})
 
-// To be able to easily fill in the *Case.reverse string above.
-func strPtr(s string) *string {
-	return &s
+	t.Run("YAMLToJSONStrict", func(t *testing.T) {
+		testYAMLToJSON(t, funcYAMLToJSONStrict, tests)
+	})
 }
 
-func TestYAMLToJSONStrict(t *testing.T) {
-	const data = `
-foo: bar
-foo: baz
-`
-	if _, err := YAMLToJSON([]byte(data)); err != nil {
-		t.Error("expected YAMLtoJSON to pass on duplicate field names")
-	}
-	if _, err := YAMLToJSONStrict([]byte(data)); err == nil {
-		t.Error("expected YAMLtoJSONStrict to fail on duplicate field names")
+func TestYAMLToJSONStrictFails(t *testing.T) {
+	tests := map[string]yamlToJSONTestcase{
+		// expect YAMLtoJSON to pass on duplicate field names
+		"duplicate struct value": {
+			yaml:                 "foo: bar\nfoo: baz\n",
+			json:                 `{"foo":"baz"}`,
+			yamlReverseOverwrite: strPtr("foo: baz\n"),
+		},
 	}
+
+	t.Run("YAMLToJSON", func(t *testing.T) {
+		testYAMLToJSON(t, funcYAMLToJSON, tests)
+	})
+
+	t.Run("YAMLToJSONStrict", func(t *testing.T) {
+		failTests := map[string]yamlToJSONTestcase{}
+		for name, test := range tests {
+			test.err = fatalErrorsType
+			failTests[name] = test
+		}
+		testYAMLToJSON(t, funcYAMLToJSONStrict, failTests)
+	})
 }
 
 func TestJSONObjectToYAMLObject(t *testing.T) {

Debdiff

[The following lists of changes regard files as different if they have different names, permissions or owners.]

Files in second set of .debs but not in first

-rw-r--r--  root/root   /usr/share/gocode/src/sigs.k8s.io/yaml/bench_test.go

Files in first set of .debs but not in second

-rw-r--r--  root/root   /usr/share/gocode/src/sigs.k8s.io/yaml/vendor/github.com/davecgh/go-spew/spew/bypass.go
-rw-r--r--  root/root   /usr/share/gocode/src/sigs.k8s.io/yaml/vendor/github.com/davecgh/go-spew/spew/bypasssafe.go
-rw-r--r--  root/root   /usr/share/gocode/src/sigs.k8s.io/yaml/vendor/github.com/davecgh/go-spew/spew/common.go
-rw-r--r--  root/root   /usr/share/gocode/src/sigs.k8s.io/yaml/vendor/github.com/davecgh/go-spew/spew/config.go
-rw-r--r--  root/root   /usr/share/gocode/src/sigs.k8s.io/yaml/vendor/github.com/davecgh/go-spew/spew/doc.go
-rw-r--r--  root/root   /usr/share/gocode/src/sigs.k8s.io/yaml/vendor/github.com/davecgh/go-spew/spew/dump.go
-rw-r--r--  root/root   /usr/share/gocode/src/sigs.k8s.io/yaml/vendor/github.com/davecgh/go-spew/spew/format.go
-rw-r--r--  root/root   /usr/share/gocode/src/sigs.k8s.io/yaml/vendor/github.com/davecgh/go-spew/spew/spew.go
-rw-r--r--  root/root   /usr/share/gocode/src/sigs.k8s.io/yaml/vendor/gopkg.in/yaml.v2/apic.go
-rw-r--r--  root/root   /usr/share/gocode/src/sigs.k8s.io/yaml/vendor/gopkg.in/yaml.v2/decode.go
-rw-r--r--  root/root   /usr/share/gocode/src/sigs.k8s.io/yaml/vendor/gopkg.in/yaml.v2/emitterc.go
-rw-r--r--  root/root   /usr/share/gocode/src/sigs.k8s.io/yaml/vendor/gopkg.in/yaml.v2/encode.go
-rw-r--r--  root/root   /usr/share/gocode/src/sigs.k8s.io/yaml/vendor/gopkg.in/yaml.v2/parserc.go
-rw-r--r--  root/root   /usr/share/gocode/src/sigs.k8s.io/yaml/vendor/gopkg.in/yaml.v2/readerc.go
-rw-r--r--  root/root   /usr/share/gocode/src/sigs.k8s.io/yaml/vendor/gopkg.in/yaml.v2/resolve.go
-rw-r--r--  root/root   /usr/share/gocode/src/sigs.k8s.io/yaml/vendor/gopkg.in/yaml.v2/scannerc.go
-rw-r--r--  root/root   /usr/share/gocode/src/sigs.k8s.io/yaml/vendor/gopkg.in/yaml.v2/sorter.go
-rw-r--r--  root/root   /usr/share/gocode/src/sigs.k8s.io/yaml/vendor/gopkg.in/yaml.v2/writerc.go
-rw-r--r--  root/root   /usr/share/gocode/src/sigs.k8s.io/yaml/vendor/gopkg.in/yaml.v2/yaml.go
-rw-r--r--  root/root   /usr/share/gocode/src/sigs.k8s.io/yaml/vendor/gopkg.in/yaml.v2/yamlh.go
-rw-r--r--  root/root   /usr/share/gocode/src/sigs.k8s.io/yaml/vendor/gopkg.in/yaml.v2/yamlprivateh.go

No differences were encountered in the control files

More details

Full run details