go-yaml / yaml

YAML support for the Go language.
Other
6.76k stars 1.03k forks source link

Objects should be decoded into map[string]interface{} instead of map[interface{}]interface{} #825

Open bluebrown opened 2 years ago

bluebrown commented 2 years ago

The standard lib JSON package decodes objects into map[string]interface{}. That is useful when trying to marshal the result again.

This YAML package decodes into map[interface{}]interface{} which is causing errors when trying to marshal the result.

I actually don't see any reason why map[interface{}]interface{} is required. Object keys in, YAML and JSON, can only be strings, there is no other option. At least as far as I can tell.

https://play.golang.com/p/KwXpFr8KC08

Example Code: ```golang package main import ( "encoding/json" "fmt" "io" "strings" "gopkg.in/yaml.v2" ) func main() { fmt.Printf("JSON\n-------\n") test(decodeJson) fmt.Printf("\nYAML\n-------\n") test(decodeYaml) } func test(decode func(s string) interface{}) { v := decode(`{"foo": "bar"}`) fmt.Printf("%T\n", v) b, err := json.MarshalIndent(v, "", " ") if err != nil { panic(err) } println(string(b)) } func decodeYaml(s string) interface{} { dec := yaml.NewDecoder(strings.NewReader(s)) var v interface{} for { if err := dec.Decode(&v); err == io.EOF { break } else if err != nil { panic(err) } } return v } func decodeJson(s string) interface{} { dec := json.NewDecoder(strings.NewReader(s)) var v interface{} for { if err := dec.Decode(&v); err == io.EOF { break } else if err != nil { panic(err) } } return v } ``` Output: ``` JSON ------- map[string]interface {} { "foo": "bar" } YAML ------- map[interface {}]interface {} panic: json: unsupported type: map[interface {}]interface {} goroutine 1 [running]: main.test(0x542a00) /home/blue/projects/own/cmdpipe/main.go:24 +0x106 main.main() /home/blue/projects/own/cmdpipe/main.go:16 +0x7b exit status 2 ```
colecrouter commented 2 years ago

I'd like to add onto this. Not sure if this is intentional, but when Unmarshalling into a pre-existing struct, the yaml.Unmarshal seems to always return a map[interface{}]interface{}, with the only solution being to type assert it to the desired type. In contrast, json.Unmarshal always returns the same type as the struct passed to it. I'll try to provide an example later.

pivotaljohn commented 2 years ago

Note: from the YAML 1.2 spec

The content of a mapping node is an unordered set of key/value node pairs, with the restriction that each of the keys is unique. YAML places no further restrictions on the nodes. In particular, keys may be arbitrary nodes, the same node may be used as the value of several key/value pairs and a mapping could even contain itself as a key or a value.

amurchick commented 8 months ago

Same issue - see https://go.dev/play/p/OjcJTBbcTFA

Encoding result of map[bool]any:

s := map[string]any{
    "question": map[bool]any{
        true:  "Positive",
        false: "Negative",
    },
}

Is OK:

question:
    false: Negative
    true: Positive

But when decode this - decoded as map[interface{}]interface{}, not map[bool]any (or map[string]any):

map[string]interface{}{
    "question": map[interface{}]interface{}{
        false: "Negative",
        true:  "Positive",
    },
}
Vyom-Yadav commented 6 months ago

I actually don't see any reason why map[interface{}]interface{} is required

I wanted to check if there are any null values in yaml, map[any]any is very useful in that case.

Edit - v3 decodes to map[string]any and map[any]any only, so it is fine, handling an extra case is alright.