vrp1609
vrp1609

Reputation: 61

Golang extract value from yaml file

jobs:
- name: test
 public: true
 plan:
 - try:
     task: task1
     file: test1.yaml
   on_success:
     in_parallel:
       steps:
       - task: task2
         file: test2.yaml
       - task: task3
         file: task3.yaml

I want to extract the value for task from this yaml. It's tricky since it can be at different paths within the file. These are a few more different paths in addition to the above that task can be found in the yaml file. Is there an easy way to extract all values for task? Should I convert to json?

Upvotes: 2

Views: 2169

Answers (1)

flyx
flyx

Reputation: 39668

go-yaml wants you to define a target type and an unmarshaler for that type.

If you want to extract all values in some map with the key task, your type would be a list of task names.

For that type, you then need to create an unmarshaler function which takes the YAML file's root node and extracts the task names into the object of the type you defined.

Here's a minimal working example:

package main

import (
    "errors"
    "fmt"
    "gopkg.in/yaml.v3"
)

type Tasks struct {
    items []string
}

// descend implements recursive descent into YAML mapping and sequence structures
func (t *Tasks) descend(node *yaml.Node) error {
    switch node.Kind {
    case yaml.SequenceNode:
        for _, item := range(node.Content) {
            t.descend(item)
        }
    case yaml.MappingNode:
        for i := 0; i < len(node.Content); i += 2 {
            key := node.Content[i]
            value := node.Content[i+1]
            if key.Kind != yaml.ScalarNode ||
                key.Value != "task" {
                t.descend(value)
                continue
            }
            if value.Kind != yaml.ScalarNode {
                return errors.New("encountered non-scalar task")
            }
            t.items = append(t.items, value.Value)
        }
    }
    return nil
}

// UnmarshalYAML is the unmarshaler that will be called by the YAML processor.
func (t *Tasks) UnmarshalYAML(value *yaml.Node) error {
    t.items = nil
    return t.descend(value)
}


func main() {
    var t Tasks
    // I fixed some whitespace issues in your YAML input
    if err := yaml.Unmarshal([]byte(`jobs:
- name: test
  public: true
  plan:
  - try:
      task: task1
      file: test1.yaml
    on_success:
      in_parallel:
        steps:
        - task: task2
          file: test2.yaml
        - task: task3
          file: task3.yaml`), &t); err != nil {
        panic(err)
    }
    for _, item := range(t.items) {
        fmt.Println(item)
    }
}

Output:

task1
task2
task3

Mind that this solution is not generally correct as a YAML node may contain cycles (due to YAML anchors/aliases) which would lead to a stack overflow because the code does not check against cycles.

Upvotes: 1

Related Questions