How to extend go-yaml to support custom tags

拈花ヽ惹草 提交于 2021-02-10 22:18:26

问题


I have spent some time reading the code and docs of go-yaml, but I have not found any way to do this, except forking the project..

I want to extend the YAML unmarshaller so that it can accept a custom YAML tag (!include <file> in this case), which in turn would allow me to add support for including files. This is easily implemented with other YAML libraries, like in this answer.

Is there any way to accomplish this, using the public interface of the library (or another yaml library)?


回答1:


Yes, this is possible (since v3). You can load the whole YAML file into a yaml.Node and then walk over the structure. The trick is that yaml.Node is an intermediate representation which you can only access if you define an unmarshaler. For example:

package main

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

// used for loading included files
type Fragment struct {
  content *yaml.Node
}

func (f *Fragment) UnmarshalYAML(value *yaml.Node) error {
  var err error
  // process includes in fragments
  f.content, err = resolveIncludes(value)
  return err
}

type IncludeProcessor struct {
  target interface{}
}

func (i *IncludeProcessor) UnmarshalYAML(value *yaml.Node) error {
  resolved, err := resolveIncludes(value)
  if err != nil {
    return err
  }
  return resolved.Decode(i.target)
}

func resolveIncludes(node *yaml.Node) (*yaml.Node, error) {
  if node.Tag == "!include" {
    if node.Kind != yaml.ScalarNode {
      return nil, errors.New("!include on a non-scalar node")
    }
    file, err := ioutil.ReadFile(node.Value)
    if err != nil {
      return nil, err
    }
    var f Fragment
    err = yaml.Unmarshal(file, &f)
    return f.content, err
  }
  if node.Kind == yaml.SequenceNode || node.Kind == yaml.MappingNode {
    var err error
    for i := range node.Content {
      node.Content[i], err = resolveIncludes(node.Content[i])
      if err != nil {
        return nil, err
      }
    }
  }
  return node, nil
}

type MyStructure struct {
  // this structure holds the values you want to load after processing
  // includes, e.g.
  Num int
}

func main() {
  var s MyStructure
  yaml.Unmarshal([]byte("!include foo.yaml"), &IncludeProcessor{&s})
  fmt.Printf("Num: %v", s.Num)
}

Code prints Num: 42 when a file foo.yaml exists with the content num: 42.



来源:https://stackoverflow.com/questions/63567945/how-to-extend-go-yaml-to-support-custom-tags

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!