Search code examples
goyamlkeyuppercase

Go, to unmarshal into uppercase keys


Following up on Keys are always lowercase? #923,

I'm looking for the easiest way for me to unmarshal an unstructured yaml into GolangCasing keys. Details --

Take a look at
https://go.dev/play/p/nIhDqoRpeK1

I.e.,

package main

import (
    "fmt"
    "log"

    "gopkg.in/yaml.v3"
)

var data = `
a: Easy!
b:
  c: 2
  d: [3, 4]
`

// Note: struct fields must be public in order for unmarshal to
// correctly populate the data.
type T struct {
    A string
    B struct {
        RenamedC int   `yaml:"c"`
        D        []int `yaml:",flow"`
    }
}

func main() {
    m := make(map[interface{}]interface{})

    err := yaml.Unmarshal([]byte(data), &m)
    if err != nil {
        log.Fatalf("error: %v", err)
    }
    fmt.Printf("--- m:\n%v\n\n", m)

    d, err := yaml.Marshal(&m)
    if err != nil {
        log.Fatalf("error: %v", err)
    }
    fmt.Printf("--- m dump:\n%s\n\n", string(d))
}
  • The data on line 10, are always unmarshal as lowercase keys in m.
  • In order to unmarshal then as uppercase keys, I have to pre-define a struct, like on line 19.
  • However, the challenge I'm facing is that I'm dealing with unstructured yaml, i.e., I don't know their struct ahead of time.

One follow up comment says that they "are switching from gopkg.in/yaml.v3 to sigs.k8s.io/yaml", but reading the docs of sigs.k8s.io/yaml, it looks to me that they still need to know the structure of yaml, and have also need to define the translation mechanism ahead of the time.

Is there any easy way to unmarshal unstructured yaml as uppercase / GolangCasing keys? What's the easiest way to do that? If no Go packages are providing such functionality out of the box, then is any of them have plugin/callbacks that allow me to easily do the translation myself?


Solution

  • You can declare a custom key type that implements the yaml.Unmarshaler interface.

    Something like this:

    type MyKey string
    
    func (k *MyKey) UnmarshalYAML(n *yaml.Node) error {
        var s string
        if err := n.Decode(&s); err != nil {
            return err
        }
        *k = MyKey(strings.ToUpper(s))
        return nil
    }
    

    And then use it as the map's key.

    m := make(map[MyKey]any)
    

    https://go.dev/play/p/k9r98EPQcy4