New Upstream Release - golang-github-go-openapi-swag

Ready changes

Summary

Merged new upstream version: 0.22.3 (was: 0.21.1).

Resulting package

Built on 2023-06-08T06:57 (took 4m20s)

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

apt install -t fresh-releases golang-github-go-openapi-swag-dev

Lintian Result

Diff

diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
index f243a50..4c99b3e 100644
--- a/.github/workflows/ci.yaml
+++ b/.github/workflows/ci.yaml
@@ -1,11 +1,26 @@
-name: Go
+name: Verify
 
 on: [push, pull_request]
 
 jobs:
+  lint:
+    runs-on: ${{ matrix.os }}
+    strategy:
+      matrix:
+        os: [ ubuntu-latest, macos-latest, windows-latest ]
+      fail-fast: false
+
+    steps:
+    - uses: actions/checkout@v2
+    - uses: golangci/golangci-lint-action@v2
+      with:
+        args: --timeout=5m
+
   build:
     runs-on: ${{ matrix.os }}
 
+    needs: [lint]
+
     strategy:
       matrix:
         # No Windows this time. Some tests expect Unix-style paths.
@@ -18,30 +33,16 @@ jobs:
     - name: Set up Go
       uses: actions/setup-go@v2
       with:
-        go-version: 1.17
+        go-version: 1.19
+        cache: true
 
-    - name: Setup gotestsum
-      uses: autero1/action-gotestsum@v1.0.0
-      with:
-        gotestsum_version: 1.7.0
+    - name: Install Tools
+      run: go install gotest.tools/gotestsum@latest
 
     - name: Test
-      run: gotestsum --format short-verbose -- -race -timeout=20m -coverprofile=coverage_txt -covermode=atomic ./...
+      run: gotestsum -- -race -timeout=20m -coverprofile=coverage_txt -covermode=atomic ./...
 
-    - uses: codecov/codecov-action@v2
+    - uses: codecov/codecov-action@v3
       with:
         files: coverage_txt
 
-  lint:
-    runs-on: ${{ matrix.os }}
-
-    strategy:
-      matrix:
-        os: [ ubuntu-latest, macos-latest, windows-latest ]
-      fail-fast: false
-
-    steps:
-    - uses: actions/checkout@v2
-    - uses: golangci/golangci-lint-action@v2
-      with:
-        args: --timeout=5m
diff --git a/.golangci.yml b/.golangci.yml
index 2a4a71f..bf503e4 100644
--- a/.golangci.yml
+++ b/.golangci.yml
@@ -48,3 +48,7 @@ linters:
     - goimports
     - tenv
     - golint
+    - exhaustruct
+    - nilnil
+    - nonamedreturns
+    - nosnakecase
diff --git a/debian/changelog b/debian/changelog
index 57e208a..0389247 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,9 @@
+golang-github-go-openapi-swag (1:0.22.3-1) UNRELEASED; urgency=low
+
+  * New upstream release.
+
+ -- Debian Janitor <janitor@jelmer.uk>  Thu, 08 Jun 2023 06:53:36 -0000
+
 golang-github-go-openapi-swag (1:0.21.1-1) unstable; urgency=medium
 
   * Team upload.
diff --git a/doc.go b/doc.go
index 8d2c8c5..55094cb 100644
--- a/doc.go
+++ b/doc.go
@@ -17,16 +17,15 @@ Package swag contains a bunch of helper functions for go-openapi and go-swagger
 
 You may also use it standalone for your projects.
 
-  * convert between value and pointers for builtin types
-  * convert from string to builtin types (wraps strconv)
-  * fast json concatenation
-  * search in path
-  * load from file or http
-  * name mangling
-
+  - convert between value and pointers for builtin types
+  - convert from string to builtin types (wraps strconv)
+  - fast json concatenation
+  - search in path
+  - load from file or http
+  - name mangling
 
 This repo has only few dependencies outside of the standard library:
 
-  * YAML utilities depend on gopkg.in/yaml.v2
+  - YAML utilities depend on gopkg.in/yaml.v2
 */
 package swag
diff --git a/go.mod b/go.mod
index fb29b65..60144c0 100644
--- a/go.mod
+++ b/go.mod
@@ -1,18 +1,21 @@
 module github.com/go-openapi/swag
 
+require (
+	github.com/mailru/easyjson v0.7.7
+	github.com/stretchr/testify v1.8.0
+	gopkg.in/yaml.v3 v3.0.1
+)
+
 require (
 	github.com/davecgh/go-spew v1.1.1 // indirect
+	github.com/josharian/intern v1.0.0 // indirect
 	github.com/kr/text v0.2.0 // indirect
-	github.com/mailru/easyjson v0.7.6
-	github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
-	github.com/stretchr/testify v1.6.1
-	gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect
-	gopkg.in/yaml.v2 v2.4.0
-	gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 // indirect
+	github.com/pmezard/go-difflib v1.0.0 // indirect
+	gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
 )
 
-replace github.com/golang/lint => golang.org/x/lint v0.0.0-20190409202823-959b441ac422
+// replace github.com/golang/lint => golang.org/x/lint v0.0.0-20190409202823-959b441ac422
 
-replace sourcegraph.com/sourcegraph/go-diff => github.com/sourcegraph/go-diff v0.5.1
+// replace sourcegraph.com/sourcegraph/go-diff => github.com/sourcegraph/go-diff v0.5.1
 
-go 1.11
+go 1.18
diff --git a/go.sum b/go.sum
index a45da80..76683c4 100644
--- a/go.sum
+++ b/go.sum
@@ -4,26 +4,24 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
 github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
+github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
+github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
 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/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
 github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
-github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA=
-github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
-github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
-github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
+github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
+github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
 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/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
-github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
-github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
+github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
+github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
-gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
-gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ=
-gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/loading.go b/loading.go
index 9a60409..00038c3 100644
--- a/loading.go
+++ b/loading.go
@@ -16,10 +16,11 @@ package swag
 
 import (
 	"fmt"
-	"io/ioutil"
+	"io"
 	"log"
 	"net/http"
 	"net/url"
+	"os"
 	"path/filepath"
 	"runtime"
 	"strings"
@@ -40,13 +41,13 @@ var LoadHTTPCustomHeaders = map[string]string{}
 
 // LoadFromFileOrHTTP loads the bytes from a file or a remote http server based on the path passed in
 func LoadFromFileOrHTTP(path string) ([]byte, error) {
-	return LoadStrategy(path, ioutil.ReadFile, loadHTTPBytes(LoadHTTPTimeout))(path)
+	return LoadStrategy(path, os.ReadFile, loadHTTPBytes(LoadHTTPTimeout))(path)
 }
 
 // LoadFromFileOrHTTPWithTimeout loads the bytes from a file or a remote http server based on the path passed in
 // timeout arg allows for per request overriding of the request timeout
 func LoadFromFileOrHTTPWithTimeout(path string, timeout time.Duration) ([]byte, error) {
-	return LoadStrategy(path, ioutil.ReadFile, loadHTTPBytes(timeout))(path)
+	return LoadStrategy(path, os.ReadFile, loadHTTPBytes(timeout))(path)
 }
 
 // LoadStrategy returns a loader function for a given path or uri
@@ -86,7 +87,7 @@ func LoadStrategy(path string, local, remote func(string) ([]byte, error)) func(
 func loadHTTPBytes(timeout time.Duration) func(path string) ([]byte, error) {
 	return func(path string) ([]byte, error) {
 		client := &http.Client{Timeout: timeout}
-		req, err := http.NewRequest("GET", path, nil) // nolint: noctx
+		req, err := http.NewRequest(http.MethodGet, path, nil) //nolint:noctx
 		if err != nil {
 			return nil, err
 		}
@@ -115,6 +116,6 @@ func loadHTTPBytes(timeout time.Duration) func(path string) ([]byte, error) {
 			return nil, fmt.Errorf("could not access document at %q [%s] ", path, resp.Status)
 		}
 
-		return ioutil.ReadAll(resp.Body)
+		return io.ReadAll(resp.Body)
 	}
 }
diff --git a/path_test.go b/path_test.go
index fb0548c..e439918 100644
--- a/path_test.go
+++ b/path_test.go
@@ -15,7 +15,6 @@
 package swag
 
 import (
-	"io/ioutil"
 	"os"
 	"path"
 	"path/filepath"
@@ -29,11 +28,11 @@ func makeDirStructure(tgt string) (string, string, error) {
 	if tgt == "" {
 		tgt = "pkgpaths"
 	}
-	td, err := ioutil.TempDir("", tgt)
+	td, err := os.MkdirTemp("", tgt)
 	if err != nil {
 		return "", "", err
 	}
-	td2, err := ioutil.TempDir("", tgt+"-2")
+	td2, err := os.MkdirTemp("", tgt+"-2")
 	if err != nil {
 		return "", "", err
 	}
@@ -97,7 +96,7 @@ func TestFindPackage(t *testing.T) {
 	assert.Empty(t, pkg)
 }
 
-// nolint: unparam
+//nolint:unparam
 func assertPath(t testing.TB, expected, actual string) bool {
 	fp, err := filepath.EvalSymlinks(expected)
 	if assert.NoError(t, err) {
diff --git a/util.go b/util.go
index 193702f..f78ab68 100644
--- a/util.go
+++ b/util.go
@@ -99,10 +99,11 @@ const (
 )
 
 // JoinByFormat joins a string array by a known format (e.g. swagger's collectionFormat attribute):
-//		ssv: space separated value
-//		tsv: tab separated value
-//		pipes: pipe (|) separated value
-//		csv: comma separated value (default)
+//
+//	ssv: space separated value
+//	tsv: tab separated value
+//	pipes: pipe (|) separated value
+//	csv: comma separated value (default)
 func JoinByFormat(data []string, format string) []string {
 	if len(data) == 0 {
 		return data
@@ -124,11 +125,11 @@ func JoinByFormat(data []string, format string) []string {
 }
 
 // SplitByFormat splits a string by a known format:
-//		ssv: space separated value
-//		tsv: tab separated value
-//		pipes: pipe (|) separated value
-//		csv: comma separated value (default)
 //
+//	ssv: space separated value
+//	tsv: tab separated value
+//	pipes: pipe (|) separated value
+//	csv: comma separated value (default)
 func SplitByFormat(data, format string) []string {
 	if data == "" {
 		return nil
diff --git a/yaml.go b/yaml.go
index ec96914..f09ee60 100644
--- a/yaml.go
+++ b/yaml.go
@@ -22,7 +22,7 @@ import (
 
 	"github.com/mailru/easyjson/jlexer"
 	"github.com/mailru/easyjson/jwriter"
-	yaml "gopkg.in/yaml.v2"
+	yaml "gopkg.in/yaml.v3"
 )
 
 // YAMLMatcher matches yaml
@@ -43,16 +43,126 @@ func YAMLToJSON(data interface{}) (json.RawMessage, error) {
 
 // BytesToYAMLDoc converts a byte slice into a YAML document
 func BytesToYAMLDoc(data []byte) (interface{}, error) {
-	var canary map[interface{}]interface{} // validate this is an object and not a different type
-	if err := yaml.Unmarshal(data, &canary); err != nil {
+	var document yaml.Node // preserve order that is present in the document
+	if err := yaml.Unmarshal(data, &document); err != nil {
 		return nil, err
 	}
+	if document.Kind != yaml.DocumentNode || len(document.Content) != 1 || document.Content[0].Kind != yaml.MappingNode {
+		return nil, fmt.Errorf("only YAML documents that are objects are supported")
+	}
+	return &document, nil
+}
 
-	var document yaml.MapSlice // preserve order that is present in the document
-	if err := yaml.Unmarshal(data, &document); err != nil {
-		return nil, err
+func yamlNode(root *yaml.Node) (interface{}, error) {
+	switch root.Kind {
+	case yaml.DocumentNode:
+		return yamlDocument(root)
+	case yaml.SequenceNode:
+		return yamlSequence(root)
+	case yaml.MappingNode:
+		return yamlMapping(root)
+	case yaml.ScalarNode:
+		return yamlScalar(root)
+	case yaml.AliasNode:
+		return yamlNode(root.Alias)
+	default:
+		return nil, fmt.Errorf("unsupported YAML node type: %v", root.Kind)
+	}
+}
+
+func yamlDocument(node *yaml.Node) (interface{}, error) {
+	if len(node.Content) != 1 {
+		return nil, fmt.Errorf("unexpected YAML Document node content length: %d", len(node.Content))
+	}
+	return yamlNode(node.Content[0])
+}
+
+func yamlMapping(node *yaml.Node) (interface{}, error) {
+	m := make(JSONMapSlice, len(node.Content)/2)
+
+	var j int
+	for i := 0; i < len(node.Content); i += 2 {
+		var nmi JSONMapItem
+		k, err := yamlStringScalarC(node.Content[i])
+		if err != nil {
+			return nil, fmt.Errorf("unable to decode YAML map key: %w", err)
+		}
+		nmi.Key = k
+		v, err := yamlNode(node.Content[i+1])
+		if err != nil {
+			return nil, fmt.Errorf("unable to process YAML map value for key %q: %w", k, err)
+		}
+		nmi.Value = v
+		m[j] = nmi
+		j++
+	}
+	return m, nil
+}
+
+func yamlSequence(node *yaml.Node) (interface{}, error) {
+	s := make([]interface{}, 0)
+
+	for i := 0; i < len(node.Content); i++ {
+
+		v, err := yamlNode(node.Content[i])
+		if err != nil {
+			return nil, fmt.Errorf("unable to decode YAML sequence value: %w", err)
+		}
+		s = append(s, v)
+	}
+	return s, nil
+}
+
+const ( // See https://yaml.org/type/
+	yamlStringScalar = "tag:yaml.org,2002:str"
+	yamlIntScalar    = "tag:yaml.org,2002:int"
+	yamlBoolScalar   = "tag:yaml.org,2002:bool"
+	yamlFloatScalar  = "tag:yaml.org,2002:float"
+	yamlTimestamp    = "tag:yaml.org,2002:timestamp"
+	yamlNull         = "tag:yaml.org,2002:null"
+)
+
+func yamlScalar(node *yaml.Node) (interface{}, error) {
+	switch node.LongTag() {
+	case yamlStringScalar:
+		return node.Value, nil
+	case yamlBoolScalar:
+		b, err := strconv.ParseBool(node.Value)
+		if err != nil {
+			return nil, fmt.Errorf("unable to process scalar node. Got %q. Expecting bool content: %w", node.Value, err)
+		}
+		return b, nil
+	case yamlIntScalar:
+		i, err := strconv.ParseInt(node.Value, 10, 64)
+		if err != nil {
+			return nil, fmt.Errorf("unable to process scalar node. Got %q. Expecting integer content: %w", node.Value, err)
+		}
+		return i, nil
+	case yamlFloatScalar:
+		f, err := strconv.ParseFloat(node.Value, 64)
+		if err != nil {
+			return nil, fmt.Errorf("unable to process scalar node. Got %q. Expecting float content: %w", node.Value, err)
+		}
+		return f, nil
+	case yamlTimestamp:
+		return node.Value, nil
+	case yamlNull:
+		return nil, nil
+	default:
+		return nil, fmt.Errorf("YAML tag %q is not supported", node.LongTag())
+	}
+}
+
+func yamlStringScalarC(node *yaml.Node) (string, error) {
+	if node.Kind != yaml.ScalarNode {
+		return "", fmt.Errorf("expecting a string scalar but got %q", node.Kind)
+	}
+	switch node.LongTag() {
+	case yamlStringScalar, yamlIntScalar, yamlFloatScalar:
+		return node.Value, nil
+	default:
+		return "", fmt.Errorf("YAML tag %q is not supported as map key", node.LongTag())
 	}
-	return document, nil
 }
 
 // JSONMapSlice represent a JSON object, with the order of keys maintained
@@ -105,6 +215,113 @@ func (s *JSONMapSlice) UnmarshalEasyJSON(in *jlexer.Lexer) {
 	*s = result
 }
 
+func (s JSONMapSlice) MarshalYAML() (interface{}, error) {
+	var n yaml.Node
+	n.Kind = yaml.DocumentNode
+	var nodes []*yaml.Node
+	for _, item := range s {
+		nn, err := json2yaml(item.Value)
+		if err != nil {
+			return nil, err
+		}
+		ns := []*yaml.Node{
+			{
+				Kind:  yaml.ScalarNode,
+				Tag:   yamlStringScalar,
+				Value: item.Key,
+			},
+			nn,
+		}
+		nodes = append(nodes, ns...)
+	}
+
+	n.Content = []*yaml.Node{
+		{
+			Kind:    yaml.MappingNode,
+			Content: nodes,
+		},
+	}
+
+	return yaml.Marshal(&n)
+}
+
+func json2yaml(item interface{}) (*yaml.Node, error) {
+	switch val := item.(type) {
+	case JSONMapSlice:
+		var n yaml.Node
+		n.Kind = yaml.MappingNode
+		for i := range val {
+			childNode, err := json2yaml(&val[i].Value)
+			if err != nil {
+				return nil, err
+			}
+			n.Content = append(n.Content, &yaml.Node{
+				Kind:  yaml.ScalarNode,
+				Tag:   yamlStringScalar,
+				Value: val[i].Key,
+			}, childNode)
+		}
+		return &n, nil
+	case map[string]interface{}:
+		var n yaml.Node
+		n.Kind = yaml.MappingNode
+		for k, v := range val {
+			childNode, err := json2yaml(v)
+			if err != nil {
+				return nil, err
+			}
+			n.Content = append(n.Content, &yaml.Node{
+				Kind:  yaml.ScalarNode,
+				Tag:   yamlStringScalar,
+				Value: k,
+			}, childNode)
+		}
+		return &n, nil
+	case []interface{}:
+		var n yaml.Node
+		n.Kind = yaml.SequenceNode
+		for i := range val {
+			childNode, err := json2yaml(val[i])
+			if err != nil {
+				return nil, err
+			}
+			n.Content = append(n.Content, childNode)
+		}
+		return &n, nil
+	case string:
+		return &yaml.Node{
+			Kind:  yaml.ScalarNode,
+			Tag:   yamlStringScalar,
+			Value: val,
+		}, nil
+	case float64:
+		return &yaml.Node{
+			Kind:  yaml.ScalarNode,
+			Tag:   yamlFloatScalar,
+			Value: strconv.FormatFloat(val, 'f', -1, 64),
+		}, nil
+	case int64:
+		return &yaml.Node{
+			Kind:  yaml.ScalarNode,
+			Tag:   yamlIntScalar,
+			Value: strconv.FormatInt(val, 10),
+		}, nil
+	case uint64:
+		return &yaml.Node{
+			Kind:  yaml.ScalarNode,
+			Tag:   yamlIntScalar,
+			Value: strconv.FormatUint(val, 10),
+		}, nil
+	case bool:
+		return &yaml.Node{
+			Kind:  yaml.ScalarNode,
+			Tag:   yamlBoolScalar,
+			Value: strconv.FormatBool(val),
+		}, nil
+	}
+	return nil, nil
+}
+
 // JSONMapItem represents the value of a key in a JSON object held by JSONMapSlice
 type JSONMapItem struct {
 	Key   string
@@ -173,23 +390,10 @@ func transformData(input interface{}) (out interface{}, err error) {
 	}
 
 	switch in := input.(type) {
-	case yaml.MapSlice:
-
-		o := make(JSONMapSlice, len(in))
-		for i, mi := range in {
-			var nmi JSONMapItem
-			if nmi.Key, err = format(mi.Key); err != nil {
-				return nil, err
-			}
-
-			v, ert := transformData(mi.Value)
-			if ert != nil {
-				return nil, ert
-			}
-			nmi.Value = v
-			o[i] = nmi
-		}
-		return o, nil
+	case yaml.Node:
+		return yamlNode(&in)
+	case *yaml.Node:
+		return yamlNode(in)
 	case map[interface{}]interface{}:
 		o := make(JSONMapSlice, 0, len(in))
 		for ke, va := range in {
diff --git a/yaml_test.go b/yaml_test.go
index 7271005..5eb5f1a 100644
--- a/yaml_test.go
+++ b/yaml_test.go
@@ -20,17 +20,45 @@ import (
 	"testing"
 
 	"github.com/stretchr/testify/assert"
-	yaml "gopkg.in/yaml.v2"
+	"github.com/stretchr/testify/require"
+	yaml "gopkg.in/yaml.v3"
 )
 
-/* currently unused:
-type failJSONMarshal struct {
-}
+func TestJSONToYAML(t *testing.T) {
+	sd := `{"1":"the int key value","name":"a string value","y":"some value"}`
+	var data JSONMapSlice
+	require.NoError(t, json.Unmarshal([]byte(sd), &data))
+
+	y, err := data.MarshalYAML()
+	require.NoError(t, err)
+	const expected = `"1": the int key value
+name: a string value
+y: some value
+`
+	assert.Equal(t, expected, string(y.([]byte)))
+
+	nstd := `{"1":"the int key value","name":"a string value","y":"some value","tag":{"name":"tag name"}}`
+	const nestpected = `"1": the int key value
+name: a string value
+y: some value
+tag:
+    name: tag name
+`
+	var ndata JSONMapSlice
+	require.NoError(t, json.Unmarshal([]byte(nstd), &ndata))
+	ny, err := ndata.MarshalYAML()
+	require.NoError(t, err)
+	assert.Equal(t, nestpected, string(ny.([]byte)))
+
+	ydoc, err := BytesToYAMLDoc([]byte(fixtures2224))
+	require.NoError(t, err)
+	b, err := YAMLToJSON(ydoc)
+	require.NoError(t, err)
+
+	var bdata JSONMapSlice
+	require.NoError(t, json.Unmarshal(b, &bdata))
 
-func (f failJSONMarshal) MarshalJSON() ([]byte, error) {
-	return nil, errors.New("expected")
 }
-*/
 
 func TestYAMLToJSON(t *testing.T) {
 
@@ -39,7 +67,7 @@ func TestYAMLToJSON(t *testing.T) {
 name: a string value
 'y': some value
 `
-	var data yaml.MapSlice
+	var data yaml.Node
 	_ = yaml.Unmarshal([]byte(sd), &data)
 
 	d, err := YAMLToJSON(data)
@@ -47,22 +75,65 @@ name: a string value
 		assert.Equal(t, `{"1":"the int key value","name":"a string value","y":"some value"}`, string(d))
 	}
 
-	data = append(data, yaml.MapItem{Key: true, Value: "the bool value"})
+	ns := []*yaml.Node{
+		{
+			Kind:  yaml.ScalarNode,
+			Value: "true",
+			Tag:   "!!bool",
+		},
+		{
+			Kind:  yaml.ScalarNode,
+			Value: "the bool value",
+			Tag:   "!!str",
+		},
+	}
+	data.Content[0].Content = append(data.Content[0].Content, ns...)
 	d, err = YAMLToJSON(data)
 	assert.Error(t, err)
 	assert.Nil(t, d)
 
-	data = data[:len(data)-1]
+	data.Content[0].Content = data.Content[0].Content[:len(data.Content[0].Content)-2]
 
-	tag := yaml.MapSlice{{Key: "name", Value: "tag name"}}
-	data = append(data, yaml.MapItem{Key: "tag", Value: tag})
+	tag := []*yaml.Node{
+		{
+			Kind:  yaml.ScalarNode,
+			Value: "tag",
+			Tag:   "!!str",
+		},
+		{
+			Kind: yaml.MappingNode,
+			Content: []*yaml.Node{
+				{
+					Kind:  yaml.ScalarNode,
+					Value: "name",
+					Tag:   "!!str",
+				},
+				{
+					Kind:  yaml.ScalarNode,
+					Value: "tag name",
+					Tag:   "!!str",
+				},
+			},
+		},
+	}
+	data.Content[0].Content = append(data.Content[0].Content, tag...)
 
 	d, err = YAMLToJSON(data)
 	assert.NoError(t, err)
 	assert.Equal(t, `{"1":"the int key value","name":"a string value","y":"some value","tag":{"name":"tag name"}}`, string(d))
 
-	tag = yaml.MapSlice{{Key: true, Value: "bool tag name"}}
-	data = append(data[:len(data)-1], yaml.MapItem{Key: "tag", Value: tag})
+	tag[1].Content = []*yaml.Node{
+		{
+			Kind:  yaml.ScalarNode,
+			Value: "true",
+			Tag:   "!!bool",
+		},
+		{
+			Kind:  yaml.ScalarNode,
+			Value: "the bool tag name",
+			Tag:   "!!str",
+		},
+	}
 
 	d, err = YAMLToJSON(data)
 	assert.Error(t, err)
@@ -104,7 +175,7 @@ func TestWithYKey(t *testing.T) {
 	doc, err := BytesToYAMLDoc([]byte(withYKey))
 	if assert.NoError(t, err) {
 		_, err := YAMLToJSON(doc)
-		if assert.Error(t, err) {
+		if assert.NoError(t, err) {
 			doc, err := BytesToYAMLDoc([]byte(withQuotedYKey))
 			if assert.NoError(t, err) {
 				jsond, err := YAMLToJSON(doc)
@@ -131,21 +202,6 @@ func TestWithYKey(t *testing.T) {
 }
 
 func TestMapKeyTypes(t *testing.T) {
-	d := yaml.MapSlice{
-		yaml.MapItem{Key: 12345, Value: "int"},
-		yaml.MapItem{Key: int8(1), Value: "int8"},
-		yaml.MapItem{Key: int16(12345), Value: "int16"},
-		yaml.MapItem{Key: int32(12345678), Value: "int32"},
-		yaml.MapItem{Key: int64(12345678910), Value: "int64"},
-		yaml.MapItem{Key: uint(12345), Value: "uint"},
-		yaml.MapItem{Key: uint8(1), Value: "uint8"},
-		yaml.MapItem{Key: uint16(12345), Value: "uint16"},
-		yaml.MapItem{Key: uint32(12345678), Value: "uint32"},
-		yaml.MapItem{Key: uint64(12345678910), Value: "uint64"},
-	}
-	_, err := YAMLToJSON(d)
-	assert.NoError(t, err)
-
 	dm := map[interface{}]interface{}{
 		12345:               "int",
 		int8(1):             "int8",
@@ -158,10 +214,188 @@ func TestMapKeyTypes(t *testing.T) {
 		uint32(12345678):    "uint32",
 		uint64(12345678910): "uint64",
 	}
-	_, err = YAMLToJSON(dm)
+	_, err := YAMLToJSON(dm)
 	assert.NoError(t, err)
 }
 
+const fixtures2224 = `definitions:
+  Time:
+    type: string
+    format: date-time
+    x-go-type:
+      import:
+        package: time
+      embedded: true
+      type: Time
+    x-nullable: true
+
+  TimeAsObject:  # <- time.Time is actually a struct
+    type: string
+    format: date-time
+    x-go-type:
+      import:
+        package: time
+        hints:
+          kind: object
+      embedded: true
+      type: Time
+    x-nullable: true
+
+  Raw:
+    x-go-type:
+      import:
+        package: encoding/json
+      hints:
+        kind: primitive
+      embedded: true
+      type: RawMessage
+
+  Request:
+    x-go-type:
+      import:
+        package: net/http
+      hints:
+        kind: object
+      embedded: true
+      type: Request
+
+  RequestPointer:
+    x-go-type:
+      import:
+        package: net/http
+      hints:
+        kind: object
+        nullable: true
+      embedded: true
+      type: Request
+
+  OldStyleImport:
+    type: object
+    x-go-type:
+      import:
+        package: net/http
+      type: Request
+      hints:
+        noValidation: true
+
+  OldStyleRenamed:
+    type: object
+    x-go-type:
+      import:
+        package: net/http
+      type: Request
+      hints:
+        noValidation: true
+    x-go-name: OldRenamed
+
+  ObjectWithEmbedded:
+    type: object
+    properties:
+      a:
+        $ref: '#/definitions/Time'
+      b:
+        $ref: '#/definitions/Request'
+      c:
+        $ref: '#/definitions/TimeAsObject'
+      d:
+        $ref: '#/definitions/Raw'
+      e:
+        $ref: '#/definitions/JSONObject'
+      f:
+        $ref: '#/definitions/JSONMessage'
+      g:
+        $ref: '#/definitions/JSONObjectWithAlias'
+
+  ObjectWithExternals:
+    type: object
+    properties:
+      a:
+        $ref: '#/definitions/OldStyleImport'
+      b:
+        $ref: '#/definitions/OldStyleRenamed'
+
+  Base:
+    properties: &base
+      id:
+        type: integer
+        format: uint64
+        x-go-custom-tag: 'gorm:"primary_key"'
+      FBID:
+        type: integer
+        format: uint64
+        x-go-custom-tag: 'gorm:"index"'
+      created_at:
+        $ref: "#/definitions/Time"
+      updated_at:
+        $ref: "#/definitions/Time"
+      version:
+        type: integer
+        format: uint64
+
+  HotspotType:
+    type: string
+    enum:
+      - A
+      - B
+      - C
+
+  Hotspot:
+    type: object
+    allOf:
+      - properties: *base
+      - properties:
+          access_points:
+            type: array
+            items:
+              $ref: '#/definitions/AccessPoint'
+          type:
+            $ref: '#/definitions/HotspotType'
+        required:
+          - type
+
+  AccessPoint:
+    type: object
+    allOf:
+      - properties: *base
+      - properties:
+          mac_address:
+            type: string
+            x-go-custom-tag: 'gorm:"index;not null;unique"'
+          hotspot_id:
+            type: integer
+            format: uint64
+          hotspot:
+            $ref: '#/definitions/Hotspot'
+
+  JSONObject:
+    type: object
+    additionalProperties:
+      type: array
+      items:
+        $ref: '#/definitions/Raw'
+
+  JSONObjectWithAlias:
+    type: object
+    additionalProperties:
+      type: object
+      properties:
+        message:
+          $ref: '#/definitions/JSONMessage'
+
+  JSONMessage:
+    $ref: '#/definitions/Raw'
+
+  Incorrect:
+    x-go-type:
+      import:
+        package: net
+        hints:
+          kind: array
+      embedded: true
+      type: Buffers
+    x-nullable: true
+`
+
 const withQuotedYKey = `consumes:
 - application/json
 definitions:

Debdiff

File lists identical (after any substitutions)

No differences were encountered in the control files

More details

Full run details