A fluent and powerful YAML query language that can be embedded into any Go library or application.
This language takes heavy inspiration from the JSONPath query language, but features changes to the specification that simplify traversal and make the language more fluent and readable.
This is built on top of the gopkg.in/yaml.v3
library in Go by querying
[*yaml.Node
] objects directly. This allows for preserving source-location
information -- which is powerful for providing diagnostics and error messages
when validating YAML documents.
To install the package, use the following go get
command:
go get rodusek.dev/pkg/yamlpath
This library has full feature-parity with JSONPath, although not all features are provided in the same way. The follow features are supported:
- ✅ Root and Current node selection with
$
and@
1 - ✅ Child node selection with
.<name>
- ✅ Recursive descent with
descendants()
2 - ✅ Array index selection with
[<number>]
3 - ✅ Array slice selection with
[<start>:<end>:<step>]
- ✅ Union of multiple selections with
<path> | <path>
- ✅ Filtering with the
where
function, e.g.$.people.where(name == "bitwizeshift")
- ✅ Comparison operators with
==
,!=
,<
,>
,<=
,>=
- ✅ Containment operators with
in
,nin
,subsetof
(e.g.age in [1, 2, 3]
) - ✅ Regular expression operator with
=~
(e.g.name =~ /^b.*shift$/i
) - ✅ Logical expression operator with
&&
,||
- ✅ Arithmetic operators with
+
,-
,*
,/
,%
- ✅ String concatenation with
key + "string"
- ✅ Function support (including custom user-defined functions!)
- ✅ Dynamic subexpressions; any expression can be used as inputs to functions4
- ✅ External constants that can be provided at compile-time
Note
For more examples, see the examples directory.
This illustrates a simple example using this library to validate configurations,
with output diagnostics appearing in GitHub annotation format. This validates
some criteria on a book
yaml object:
path := yamlpath.MustCompile("store.book.where(price < 10.00)")
filepath := "bookstore.yaml" // Some filepath to the YAML file
file, err := os.Open(filepath) // some file handle to the YAML file
if err != nil {
log.Fatal(err)
}
defer file.Close()
var node yaml.Node
if err := yaml.NewDecoder(file).Decode(&node); err != nil {
log.Fatal(err)
}
result, err := path.Match(&node)
if err != nil {
log.Fatal(err)
}
for _, node := range result {
if err := validateBook(result); err != nil {
// This is in GitHub annotation format
fmt.Println("::error file=%s,line=%d,title=Book validation failed::%v",
filepath,
result.Line,
err,
)
}
}
Footnotes
-
These are optional in YAMLPath definitions. The path is always assumed to be the "current" context path if unspecified; but can be provided for disambiguation. ↩
-
The
descendants()
function provides feature-parity with JSONPath's recursive descent operator..
. This is a more explicit way to select descendants, and is more readable. ↩ -
In YAMLPath, only indices are selected with the index operator. Fields are selected with the
.
operator. To select fields with a string value, theselect
function may be used (e.g.$.select("some key")
). ↩ -
Dynamic subexpressions along with external constants provide rough feature-parity with JSONPath's "script" functinality, since it enables the calling language to provide data dynamically to YAMLPath expressions. ↩