YAML support for the Go language https://goccy.github.io/go-yaml
Find a file
Masaaki Goshima 6e91dde8cc Add docs
2019-11-05 02:43:48 +09:00
.circleci Add config.yml for CirclCI 2019-10-16 18:39:50 +09:00
ast Fix reserved keywords 2019-10-31 17:23:56 +09:00
cmd/ycat small fixes here and there, documentation 2019-10-24 15:28:41 +09:00
docs Add docs 2019-11-05 02:43:48 +09:00
internal/errors small fixes here and there, documentation 2019-10-24 15:28:41 +09:00
lexer Add document 2019-10-21 12:53:30 +09:00
parser Integrate FlowSequenceNode into SequenceNode 2019-10-31 13:06:35 +09:00
printer Support gopkg.in/go-playground/validator.v9 based validation for YAML document 2019-10-26 10:08:44 +09:00
scanner Supports decoding for null value 2019-10-30 16:57:59 +09:00
testdata Support reference to anchor defined by the other file 2019-10-18 15:20:09 +09:00
token Add timestamp tag 2019-11-01 14:22:06 +09:00
.codecov.yml Modify codecov patch settings 2019-11-01 14:32:05 +09:00
decode.go Add timestamp tag 2019-11-01 14:22:06 +09:00
decode_test.go Add timestamp tag 2019-11-01 14:22:06 +09:00
encode.go Add error handling 2019-11-01 14:27:03 +09:00
encode_test.go Add Flow option for Encoder 2019-10-31 13:29:43 +09:00
go.mod Add prototype of yaml.Decoder 2019-10-18 00:01:25 +09:00
go.sum Add prototype of yaml.Decoder 2019-10-18 00:01:25 +09:00
LICENSE Create LICENSE 2019-10-16 18:53:18 +09:00
Makefile Add docs 2019-11-05 02:43:48 +09:00
option.go Add Flow option for Encoder 2019-10-31 13:29:43 +09:00
README.md documentation about he (Un)?Marshalers 2019-10-26 18:39:15 +09:00
struct.go Supports tag without name 2019-10-31 12:48:31 +09:00
validate.go Support gopkg.in/go-playground/validator.v9 based validation for YAML document 2019-10-26 10:08:44 +09:00
validate_test.go Fix error handling 2019-10-26 10:42:54 +09:00
yaml.go Rename Marshaler and Unmarshaler 2019-10-26 07:19:18 +09:00
yaml_test.go Support Marshaler interface compatible with encoding/json package 2019-10-25 02:38:32 +09:00

YAML support for the Go language

GoDoc CircleCI codecov Go Report Card

Why a new library?

As of this writing, there already exists a defacto standard library for YAML processing Go: https://github.com/go-yaml/yaml. However we feel that some features are lacking, namely:

  • Pretty format for error notifacations
  • Directly manipulate the YAML abstract syntax tree
  • Support Anchor and Alias when marshaling
  • Allow referencing elements declared in another file via anchors

Status

  • This library should be considered alpha grade. API may still change.

Features ( Goals )

  • Pretty format for error notifacations
  • Support Scanner or Lexer or Parser as public API
  • Support Anchor and Alias to Marshaler
  • Allow referencing elements declared in another file via anchors

Synopsis

1. Simple Encode/Decode

Support compatible interface to go-yaml/yaml by using reflect

var v struct {
	A int
	B string
}
v.A = 1
v.B = "hello"
bytes, err := yaml.Marshal(v)
if err != nil {
	...
}
fmt.Println(string(bytes)) // "a: 1\nb: hello\n"
	yml := `
%YAML 1.2
---
a: 1
b: c
`
var v struct {
	A int
	B string
}
if err := yaml.Unmarshal([]byte(yml), &v); err != nil {
	...
}

For custom marshal/unmarshaling, implement one of Bytes or Interface Marshaler/Unmarshaler. The difference is that while BytesMarshaler/BytesUnmarshaler behave like encoding.json, InterfaceMarshaler/InterfaceUnmarshaler behave like gopkg.in/yaml.v2.

Semantically both are the same, but they differ in performance. Because indentation matter in YAML, you cannot simply accept a valid YAML fragment from a Marshaler, and expect it to work when it is attached to the parent container's serialized form. Therefore when we receive use the BytesMarshaler, which returns []byte, we must decode it once to figure out how to make it work in the given context. If you use the InterfaceMarshaler, we can skip the decoding.

If you are repeatedly marshaling complex objects, the latter is always better performance wise. But if you are, for example, just providing a choice between a config file format that is read only once, the former is probably easier to code.

2. Reference elements in declared in another file

testdata directory includes anchor.yml file

├── testdata
   └── anchor.yml

And anchor.yml is defined the following.

a: &a
  b: 1
  c: hello

Then, if yaml.ReferenceDirs("testdata") option passed to yaml.Decoder, Decoder try to find anchor definition from YAML files the under testdata directory.

buf := bytes.NewBufferString("a: *a\n")
dec := yaml.NewDecoder(buf, yaml.ReferenceDirs("testdata"))
var v struct {
	A struct {
		B int
		C string
	}
}
if err := dec.Decode(&v); err != nil {
	...
}
fmt.Printf("%+v\n", v) // {A:{B:1 C:hello}}

3. Encode with Anchor and Alias

3.1. Explicitly declaration Anchor name and Alias name

If you want to use anchor or alias, you can define it as a struct tag.

type T struct {
  A int
  B string
}
var v struct {
  C *T `yaml:"c,anchor=x"`
  D *T `yaml:"d,alias=x"`
}
v.C = &T{A: 1, B: "hello"}
v.D = v.C
bytes, err := yaml.Marshal(v)
if err != nil {
  panic(err)
}
fmt.Println(string(bytes))
/*
c: &x
  a: 1
  b: hello
d: *x
*/

3.2. Implicitly declared Anchor and Alias names

If you do not explicitly declare the anchor name, the default behavior is to use the equivalent of strings.ToLower($FieldName) as the name of the anchor.

If you do not explicitly declare the alias name AND the value is a pointer to another element, we look up the anchor name by finding out which anchor field the value is assigned to by looking up its pointer address.

type T struct {
	I int
	S string
}
var v struct {
	A *T `yaml:"a,anchor"`
	B *T `yaml:"b,anchor"`
	C *T `yaml:"c,alias"`
	D *T `yaml:"d,alias"`
}
v.A = &T{I: 1, S: "hello"}
v.B = &T{I: 2, S: "world"}
v.C = v.A // C has same pointer address to A
v.D = v.B // D has same pointer address to B
bytes, err := yaml.Marshal(v)
if err != nil {
	...
}
fmt.Println(string(bytes)) 
/*
a: &a
  i: 1
  s: hello
b: &b
  i: 2
  s: world
c: *a
d: *b
*/

3.3 MergeKey and Alias

Merge key and alias ( <<: *alias ) can be used by embedding a structure with the inline,alias tag .

type Person struct {
	*Person `yaml:",omitempty,inline,alias"` // embed Person type for default value
	Name    string `yaml:",omitempty"`
	Age     int    `yaml:",omitempty"`
}
defaultPerson := &Person{
	Name: "John Smith",
	Age:  20,
}
people := []*Person{
	{
		Person: defaultPerson, // assign default value
		Name:   "Ken",         // override Name property
		Age:    10,            // override Age property
	},
	{
		Person: defaultPerson, // assign default value only
	},
}
var doc struct {
	Default *Person   `yaml:"default,anchor"`
	People  []*Person `yaml:"people"`
}
doc.Default = defaultPerson
doc.People = people
bytes, err := yaml.Marshal(doc)
if err != nil {
	...
}
fmt.Println(string(bytes))
/*
default: &default
  name: John Smith
  age: 20
people:
- <<: *default
  name: Ken
  age: 10
- <<: *default
*/

4. Pretty Formatted Errors

Error values produced during parsing has two extra features over regular error values.

First by default they contain extra information on the location of the error from the source YAML document, to make it easier finding the error location.

Second, the error messages can optionally be colorized.

If you would like to control exactly how the output looks like, consider using yaml.FormatError, which accepts two boolean values to control turning on/off these features

Installation

$ go get -u github.com/goccy/go-yaml

Tools

ycat

print yaml file with color

ycat

Install

$ go get -u github.com/goccy/go-yaml/cmd/ycat

License

MIT