Home > Software design >  Unmarshal yaml map dict key to struct property
Unmarshal yaml map dict key to struct property

Time:12-29

I really searched a while here, but didn't found an adequate answer:

I am trying to unmarshall yaml dict keys onto a property of a struct rather than the key of a map. Given this yaml

commands:
    php:
        service: php
        bin: /bin/php
    node:
        service: node
        bin: /bin/node

I am able to unmarshall this into a struct like this:

type Config struct {
    Commands map[string]struct {
        Service string
        Bin     string
    }
}

But how am I able to unmarshall it into a struct like this:

type Config struct {
    Commands []struct {
        Name    string    // <-- this should be key from the yaml (i.e. php or node)
        Service string
        Bin     string
    }
}

Thx in advance for the help

CodePudding user response:

You can write a custom unmarshaler, like this (on Go playground):

package main

import (
    "fmt"

    "gopkg.in/yaml.v3"
)

var input []byte = []byte(`
commands:
    php:
        service: php
        bin: /bin/php
    node:
        service: node
        bin: /bin/node
`)

type Command struct {
    Service string
    Bin     string
}

type NamedCommand struct {
    Command
    Name string
}

type NamedCommands []NamedCommand

type Config struct {
    Commands NamedCommands
}

func (p *NamedCommands) UnmarshalYAML(value *yaml.Node) error {
    if value.Kind != yaml.MappingNode {
        return fmt.Errorf("`commands` must contain YAML mapping, has %v", value.Kind)
    }
    *p = make([]NamedCommand, len(value.Content)/2)
    for i := 0; i < len(value.Content); i  = 2 {
        var res = &(*p)[i/2]
        if err := value.Content[i].Decode(&res.Name); err != nil {
            return err
        }
        if err := value.Content[i 1].Decode(&res.Command); err != nil {
            return err
        }
    }
    return nil
}

func main() {
    var f Config
    var err error
    if err = yaml.Unmarshal(input, &f); err != nil {
        panic(err)
    }
    for _, cmd := range f.Commands {
        fmt.Printf("% v\n", cmd)
    }
}

I have split the command data into Command and NamedCommand to make the code simpler, since you can just call Decode giving the embedded Command struct for the values. If everything was in the same struct, you'd need to manually map keys to struct fields.

  • Related