yay
Working with YAML in Go can be fun. :yay:
This project provides some utilities to make it slightly more fun by wrapping yaml.v3 and YAML JSONPath.
Features
- A visitor allowing user defined handlers for standard yaml.v3
- A ConditionalHandler allowing to define YAML JSONPath preconditions to visitor methods
Examples
Standard Handler
First, create a handler which satisfies one or more of the interfaces:
- VisitsYaml
- VisitsDocumentNode
- VisitsSequenceNode
- VisitsMappingNode
- VisitsScalarNode
- VisitsAliasNode
type myHandler struct{}
func (m *myHandler) VisitScalarNode(ctx context.Context, key *yaml.Node, value *yaml.Node) error {
fmt.Printf("found key=%s value=%s\n", key.Value, value.Value)
return nil
}
Then, pass create a new visitor with this handler and run against the target document node. Error handling omitted from the below example:
document := &yaml.Node{}
// parse your document
visitor, _ := yay.NewVisitor(myHandler{})
_ = visitor.Visit(context.TODO(), document)
Conditional Handler
Suppose you have a complex YAML document, and you only want to parse nodes based on some condition. You could use the standard handler and apply those checks on the key/value nodes. This is fine, but may lead to unexpected bugs/behaviors. You can also apply a selector as a precondition using a conditional handler.
Consider this YAML document used in conditional_handler_test.go:
---
store:
book:
- author: Ernest Hemingway
title: The Old Man and the Sea
- author: Fyodor Mikhailovich Dostoevsky
title: Crime and Punishment
- author: Jane Austen
title: Sense and Sensibility
- author: Kurt Vonnegut Jr.
title: Slaughterhouse-Five
- author: J. R. R. Tolkien
title: The Lord of the Rings
IF you only want to process titles beginning with S
, you could use the selector syntax:
$.store.book[?(@.title=~/^S.*$/)]
This will select each mapping node matching that criteria. Rather than your visitor processing 5 items, you will process 2 items. For example:
document := &yaml.Node{}
_ = yaml.Unmarshal([]byte(input), document)
count := 0
handler, _ := yay.NewConditionalHandler(
yay.OnVisitMappingNode("$.store.book[?(@.title=~/^S.*$/)]",
func(ctx context.Context, key *yaml.Node, value *yaml.Node) error {
fmt.Printf("processed item at index %d\n", count)
count += 1
return nil
}))
visitor, _ := yay.NewVisitor(handler)
_ = visitor.Visit(context.TODO(), document)
This will output:
processed item at index 0
processed item at index 1
If you want to process the item for which your conditional applies, you can continue chaining dot-notation. For example, to process just the scalar title
:
handler, _ := yay.NewConditionalHandler(
yay.OnVisitScalarNode("$.store.book[?(@.title=~/^S.*$/)].title",
func(ctx context.Context, key *yaml.Node, value *yaml.Node) error {
fmt.Printf("processed item at index %d\n", count)
count += 1
return nil
}))
Notice the use of the functional OnVisitScalarNode
and the matcher is now $.store.book[?(@.title=~/^S.*$/)].title
.
Caveats
Note that key
may be nil if the node type you're processing exists within a sequence in the original document. That is, items within sequences don't have keys.
Install
go get -u github.com/jimschubert/yay
Build/Test
go test -v -race -cover ./...
License
This project is licensed under Apache 2.0.