-
Notifications
You must be signed in to change notification settings - Fork 34
/
jsontoyaml.go
113 lines (106 loc) · 2.75 KB
/
jsontoyaml.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
// Copyright (c) 2019 Palantir Technologies. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package safeyaml
import (
"bytes"
"encoding/json"
"fmt"
"gopkg.in/yaml.v2"
)
// JSONtoYAMLMapSlice decodes JSON bytes into an interface{} where all objects and maps are decoded
// as a yaml.MapSlice to preserve key ordering. This can be useful if a type defines custom JSON
// marshal logic and translating the JSON representation to a YAML representation while
// preserving key ordering is sufficient for serialization. For example:
//
// func (o Foo) MarshalYAML() (interface{}, error) {
// jsonBytes, err := json.Marshal(o)
// if err != nil {
// return nil, err
// }
// return JSONtoYAML(jsonBytes)
// }
func JSONtoYAMLMapSlice(jsonBytes []byte) (interface{}, error) {
dec := json.NewDecoder(bytes.NewReader(jsonBytes))
dec.UseNumber()
val, err := tokenizerToYAML(dec)
if err != nil {
return val, err
}
if dec.More() {
return nil, fmt.Errorf("invalid input after top-level JSON value")
}
return val, nil
}
// JSONtoYAMLBytes converts json data to yaml output while preserving order of object fields.
func JSONtoYAMLBytes(jsonBytes []byte) ([]byte, error) {
obj, err := JSONtoYAMLMapSlice(jsonBytes)
if err != nil {
return nil, err
}
return yaml.Marshal(obj)
}
var errClosingArrayDelim = fmt.Errorf("unexpected ']' delimiter")
var errClosingObjectDelim = fmt.Errorf("unexpected '}' delimiter")
func tokenizerToYAML(dec *json.Decoder) (interface{}, error) {
tok, err := dec.Token()
if err != nil {
return nil, err
}
if tok == nil {
return nil, nil
}
switch v := tok.(type) {
case string, bool, float64:
return v, nil
case json.Number:
if numI, err := v.Int64(); err == nil {
return numI, nil
}
if numF, err := v.Float64(); err == nil {
return numF, nil
}
return v.String(), nil
case json.Delim:
switch v {
case '[':
arr := make([]interface{}, 0)
for {
elem, err := tokenizerToYAML(dec)
if err == errClosingArrayDelim {
break
}
if err != nil {
return nil, err
}
arr = append(arr, elem)
}
return arr, nil
case '{':
obj := make(yaml.MapSlice, 0)
for {
objectKeyI, err := tokenizerToYAML(dec)
if err == errClosingObjectDelim {
break
}
if err != nil {
return nil, err
}
objectValueI, err := tokenizerToYAML(dec)
if err != nil {
return nil, err
}
obj = append(obj, yaml.MapItem{Key: objectKeyI, Value: objectValueI})
}
return obj, nil
case ']':
return nil, errClosingArrayDelim
case '}':
return nil, errClosingObjectDelim
default:
return nil, fmt.Errorf("unrecognized delimiter")
}
default:
return nil, fmt.Errorf("unrecognized token type %T", tok)
}
}