Services:
- Orders:
- ID: $save ID1
SupplierOrderCode: $SupplierOrderCode
- ID: $save ID2
SupplierOrderCode: 111111
I want to convert this yaml string to json, cause the source data is dynamic, so I can't map it to a struct:
var body interface{}
err := yaml.Unmarshal([]byte(s), &body)
Then I want to convert that interface to json string again:
b, _ := json.Marshal(body)
But an error occur:
panic: json: unsupported type: map[interface {}]interface {}
Foreword: I optimized and improved the below solution, and released it as a library here: github.com/icza/dyno
. The below convert()
function is available as dyno.ConvertMapI2MapS()
.
The problem is that if you use the most generic interface{}
type to unmarshal into, the default type used by the github.com/go-yaml/yaml
package to unmarshal key-value pairs will be map[interface{}]interface{}
.
First idea would be to use map[string]interface{}
:
var body map[string]interface{}
But this attempt falls short if the depth of the yaml config is more than one, as this body
map will contain additional maps whose type will again be map[interface{}]interface{}
.
The problem is that the depth is unknown, and there may be other values than maps, so using map[string]map[string]interface{}
is not good.
A viable approach is to let yaml
unmarshal into a value of type interface{}
, and go through the result recursively, and convert each encountered map[interface{}]interface{}
to a map[string]interface{}
value. Both maps and slices have to be handled.
Here's an example of this converter function:
func convert(i interface{}) interface{} {
switch x := i.(type) {
case map[interface{}]interface{}:
m2 := map[string]interface{}{}
for k, v := range x {
m2[k.(string)] = convert(v)
}
return m2
case []interface{}:
for i, v := range x {
x[i] = convert(v)
}
}
return i
}
And using it:
func main() {
fmt.Printf("Input: %s\n", s)
var body interface{}
if err := yaml.Unmarshal([]byte(s), &body); err != nil {
panic(err)
}
body = convert(body)
if b, err := json.Marshal(body); err != nil {
panic(err)
} else {
fmt.Printf("Output: %s\n", b)
}
}
const s = `Services:
- Orders:
- ID: $save ID1
SupplierOrderCode: $SupplierOrderCode
- ID: $save ID2
SupplierOrderCode: 111111
`
Output:
Input: Services:
- Orders:
- ID: $save ID1
SupplierOrderCode: $SupplierOrderCode
- ID: $save ID2
SupplierOrderCode: 111111
Output: {"Services":[{"Orders":[
{"ID":"$save ID1","SupplierOrderCode":"$SupplierOrderCode"},
{"ID":"$save ID2","SupplierOrderCode":111111}]}]}
One thing to note: by switching from yaml to JSON via Go maps you'll lose the order of the items, as elements (key-value pairs) in a Go map are not ordered. This may or may not be a problem.
http://sigs.k8s.io/yaml is "a wrapper around go-yaml designed to enable a better way of handling YAML when marshaling to and from structs". Among other things, it provides yaml.YAMLToJSON
method that should do what you want.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With