goyaml

Streaming yaml parsing, but Decode twice


I use that to parse yaml with several docs in one file:

package main

import (
    "errors"
    "fmt"
    "io"
    "os"

    "gopkg.in/yaml.v3"
)

type Spec struct {
    Name string `yaml:"name"`
}

func main() {
    f := io.Reader(os.Stdin)
    d := yaml.NewDecoder(f)
    for {
        var spec Spec
        err := d.Decode(&spec)
        if errors.Is(err, io.EOF) {
            break
        }
        if err != nil {
            panic(err)
        }
        fmt.Printf("name is '%s'\n", spec.Name)
    }
}

Now I want to decode the current object twice.

Depending on the name in the current doc has, I want to parse that:

type Special struct {
    Integer     string    `yaml:"integer"`
}

but if I use d.Decode() I would parse the next doc ...

How to solve that?


Solution

  • Unmarshal the doc to a Node first, and then decode the resulting node as much times as you want using the Node's Decode method (see the docs).

    A modified example would look like this:

    package main
    
    import (
        "errors"
        "fmt"
        "io"
        "strings"
    
        "gopkg.in/yaml.v3"
    )
    
    const data = `
    ---
    name: "foo"
    integer: 42
    ---
    name: "bar"
    integer: 12
    `
    
    type Spec struct {
        Name string `yaml:"name"`
    }
    
    type Special struct {
        Integer string `yaml:"integer"`
    }
    
    func main() {
        dec := yaml.NewDecoder(strings.NewReader(data))
    
        for {
            var node yaml.Node
    
            err := dec.Decode(&node)
            if errors.Is(err, io.EOF) {
                break
            }
            if err != nil {
                panic(err)
            }
    
            spec := new(Spec)
    
            err = node.Decode(&spec)
            if err != nil {
                panic(err)
            }
    
            // check it was parsed
            if spec == nil {
                fmt.Println("# spec is nil")
                continue
            }
            fmt.Printf("name is '%s'\n", spec.Name)
    
            var si Special
            err = node.Decode(&si)
            if err != nil {
                panic(err)
            }
    
            fmt.Println("integer is: ", si.Integer)
        }
    }
    

    Playground.