Skip to content

Commit 2c9b5be

Browse files
committed
Fixed json decode to maintain key order
1 parent a91a8cc commit 2c9b5be

File tree

3 files changed

+57
-27
lines changed

3 files changed

+57
-27
lines changed

pkg/yqlib/decoder_json.go

Lines changed: 22 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,13 @@ func (dec *jsonDecoder) Init(reader io.Reader) {
2222

2323
func (dec *jsonDecoder) Decode(rootYamlNode *yaml.Node) error {
2424

25-
var dataBucket interface{}
25+
var dataBucket orderedMap
2626
log.Debug("going to decode")
2727
err := dec.decoder.Decode(&dataBucket)
2828
if err != nil {
2929
return err
3030
}
31-
node, err := dec.convertToYamlNode(dataBucket)
31+
node, err := dec.convertToYamlNode(&dataBucket)
3232

3333
if err != nil {
3434
return err
@@ -38,39 +38,36 @@ func (dec *jsonDecoder) Decode(rootYamlNode *yaml.Node) error {
3838
return nil
3939
}
4040

41-
func (dec *jsonDecoder) convertToYamlNode(data interface{}) (*yaml.Node, error) {
42-
switch data := data.(type) {
43-
case nil:
44-
return createScalarNode(nil, "null"), nil
45-
case float64, float32:
46-
// json decoder returns ints as float.
47-
return parseSnippet(fmt.Sprintf("%v", data))
48-
case int, int64, int32, string, bool:
49-
return createScalarNode(data, fmt.Sprintf("%v", data)), nil
50-
case map[string]interface{}:
51-
return dec.parseMap(data)
52-
case []interface{}:
53-
return dec.parseArray(data)
54-
default:
55-
return nil, fmt.Errorf("unrecognised type :(")
41+
func (dec *jsonDecoder) convertToYamlNode(data *orderedMap) (*yaml.Node, error) {
42+
if data.kv == nil {
43+
switch rawData := data.altVal.(type) {
44+
case nil:
45+
return createScalarNode(nil, "null"), nil
46+
case float64, float32:
47+
// json decoder returns ints as float.
48+
return parseSnippet(fmt.Sprintf("%v", rawData))
49+
case int, int64, int32, string, bool:
50+
return createScalarNode(rawData, fmt.Sprintf("%v", rawData)), nil
51+
case []*orderedMap:
52+
return dec.parseArray(rawData)
53+
default:
54+
return nil, fmt.Errorf("unrecognised type :( %v", rawData)
55+
}
5656
}
57-
}
58-
59-
func (dec *jsonDecoder) parseMap(dataMap map[string]interface{}) (*yaml.Node, error) {
6057

6158
var yamlMap = &yaml.Node{Kind: yaml.MappingNode}
62-
63-
for key, value := range dataMap {
64-
yamlValue, err := dec.convertToYamlNode(value)
59+
for _, keyValuePair := range data.kv {
60+
yamlValue, err := dec.convertToYamlNode(&keyValuePair.V)
6561
if err != nil {
6662
return nil, err
6763
}
68-
yamlMap.Content = append(yamlMap.Content, createScalarNode(key, fmt.Sprintf("%v", key)), yamlValue)
64+
yamlMap.Content = append(yamlMap.Content, createScalarNode(keyValuePair.K, keyValuePair.K), yamlValue)
6965
}
7066
return yamlMap, nil
67+
7168
}
7269

73-
func (dec *jsonDecoder) parseArray(dataArray []interface{}) (*yaml.Node, error) {
70+
func (dec *jsonDecoder) parseArray(dataArray []*orderedMap) (*yaml.Node, error) {
7471

7572
var yamlMap = &yaml.Node{Kind: yaml.SequenceNode}
7673

pkg/yqlib/encoder.go

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,13 @@ func (o *orderedMap) UnmarshalJSON(data []byte) error {
6666
}
6767
return nil
6868
case '[':
69-
var arr []orderedMap
70-
return json.Unmarshal(data, &arr)
69+
var res []*orderedMap
70+
if err := json.Unmarshal(data, &res); err != nil {
71+
return err
72+
}
73+
o.altVal = res
74+
o.kv = nil
75+
return nil
7176
}
7277

7378
return json.Unmarshal(data, &o.altVal)

pkg/yqlib/json_test.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,13 @@ const sampleNdJson = `{"this": "is a multidoc json file"}
2222
{"a number": 4}
2323
`
2424

25+
const sampleNdJsonKey = `{"a": "first", "b": "next", "ab": "last"}`
26+
27+
const expectedJsonKeysInOrder = `a: first
28+
b: next
29+
ab: last
30+
`
31+
2532
const expectedNdJsonYaml = `this: is a multidoc json file
2633
---
2734
each:
@@ -157,13 +164,34 @@ var jsonScenarios = []formatScenario{
157164
expected: expectedNdJsonYaml,
158165
scenarioType: "decode-ndjson",
159166
},
167+
{
168+
description: "Decode NDJSON, maintain key order",
169+
skipDoc: true,
170+
input: sampleNdJsonKey,
171+
expected: expectedJsonKeysInOrder,
172+
scenarioType: "decode-ndjson",
173+
},
160174
{
161175
description: "numbers",
162176
skipDoc: true,
163177
input: "[3, 3.0, 3.1, -1]",
164178
expected: "- 3\n- 3\n- 3.1\n- -1\n",
165179
scenarioType: "decode-ndjson",
166180
},
181+
{
182+
description: "number single",
183+
skipDoc: true,
184+
input: "3",
185+
expected: "3\n",
186+
scenarioType: "decode-ndjson",
187+
},
188+
{
189+
description: "empty string",
190+
skipDoc: true,
191+
input: `""`,
192+
expected: "\"\"\n",
193+
scenarioType: "decode-ndjson",
194+
},
167195
{
168196
description: "strings",
169197
skipDoc: true,

0 commit comments

Comments
 (0)