From 11b095771ed1dff38dbd72e39d39de5614cbfb34 Mon Sep 17 00:00:00 2001 From: Frederic BIDON Date: Thu, 21 Dec 2023 17:08:40 +0100 Subject: [PATCH] fix(yaml): make MarshalYAML deterministic This PR sorts the keys of a map[string]interface{} before marshaling to YAML, similar to what the standard encoding/json does. * fixes go-swagger/go-swagger#2850 Signed-off-by: Frederic BIDON --- yaml.go | 10 +++++++++- yaml_test.go | 24 +++++++++++++++++++++++- 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/yaml.go b/yaml.go index b79623a..a8c4e35 100644 --- a/yaml.go +++ b/yaml.go @@ -19,6 +19,7 @@ import ( "fmt" "path/filepath" "reflect" + "sort" "strconv" "github.com/mailru/easyjson/jlexer" @@ -286,7 +287,14 @@ func json2yaml(item interface{}) (*yaml.Node, error) { case map[string]interface{}: var n yaml.Node n.Kind = yaml.MappingNode - for k, v := range val { + keys := make([]string, 0, len(val)) + for k := range val { + keys = append(keys, k) + } + sort.Strings(keys) + + for _, k := range keys { + v := val[k] childNode, err := json2yaml(v) if err != nil { return nil, err diff --git a/yaml_test.go b/yaml_test.go index 400968d..3dd0065 100644 --- a/yaml_test.go +++ b/yaml_test.go @@ -75,8 +75,30 @@ y: some value assert.Equal(t, expected, string(ny.([]byte))) } -func TestYAMLToJSON(t *testing.T) { +func TestMarshalYAML(t *testing.T) { + t.Run("marshalYAML should be deterministic", func(t *testing.T) { + const ( + jazon = `{"1":"x","2":null,"3":{"a":1,"b":2,"c":3}}` + expected = `"1": x +"2": null +"3": + a: !!float 1 + b: !!float 2 + c: !!float 3 +` + ) + const iterations = 10 + for n := 0; n < iterations; n++ { + var data JSONMapSlice + require.NoError(t, json.Unmarshal([]byte(jazon), &data)) + ny, err := data.MarshalYAML() + require.NoError(t, err) + assert.Equal(t, expected, string(ny.([]byte))) + } + }) +} +func TestYAMLToJSON(t *testing.T) { sd := `--- 1: the int key value name: a string value