Traversing Values



There are several ways to traverse values from looking up based on path to iteration and walking the value tree. Some of these are configurable, which is tied to the different labels like regular, hidden, optional, and definitions.

We will use the following Value for the examples in this section.

value.cue

obj: {
	sub: {
		foo: "bar"
		n:   int
	}
	list: [1, 2, {a: 3}]
	any: _
	#def: val: string
	_hid: 42
	opt?: bool
	labels: [string]: string
}

Selectors and MakePath

We saw how to use LookupPath with ParsePath in a previous section. We can programmatically construct paths with Selectors and MakePath. We’ll also use this to reconstruct the label for the current value.

selectors.go

package main

import (
	"fmt"
	"os"

	"cuelang.org/go/cue"
	"cuelang.org/go/cue/cuecontext"
)

func main() {
	c := cuecontext.New()
	d, _ := os.ReadFile("value.cue")
	val := c.CompileBytes(d)

	// printing the label vs path
	sub := val.LookupPath(cue.ParsePath("obj.sub"))
	fmt.Println(sub.Path(), getLabel(sub))
}

// helper function for getting the label for a value
func getLabel(val cue.Value) cue.Selector {
	ss := val.Path().Selectors()
	s := ss[len(ss)-1]
	return s
}

go run selectors.go

Docs: MakePath, Selectors

List Iteration

list.go

package main

import (
	"fmt"
	"os"

	"cuelang.org/go/cue"
	"cuelang.org/go/cue/cuecontext"
)

func main() {
	c := cuecontext.New()
	d, _ := os.ReadFile("value.cue")
	val := c.CompileBytes(d)

	// lookup a list value
	val = val.LookupPath(cue.ParsePath("obj.list"))

	// we use iterators to traverse a list
	// List() returns an iterator
	iter, _ := val.List()

	// This pattern is standard iteration
	// We get the current element and nil at the end
	for iter.Next() {
		fmt.Println(iter.Value())
	}
}

go run list.go

1
2
{
	a: 3
}

Docs: List

Field Iteration

fields.go

package main

import (
	"fmt"
	"os"

	"cuelang.org/go/cue"
	"cuelang.org/go/cue/cuecontext"
)

func main() {
	c := cuecontext.New()
	d, _ := os.ReadFile("value.cue")
	val := c.CompileBytes(d)
	val = val.LookupPath(cue.ParsePath("obj"))

	// without options
	fmt.Println("Without\n---------")
	printFields(val.Fields())

	// default options
	fmt.Println("Default\n---------")
	printFields(val.Fields(defaultOptions...))

	// custom options
	fmt.Println("Custom\n---------")
	printFields(val.Fields(customOptions...))

}

func printFields(iter *cue.Iterator, err error) {
	for iter.Next() {
		fmt.Printf("%v: %v\n", iter.Selector(), iter.Value())
	}
	fmt.Println()
}

// Cue's default
var defaultOptions = []cue.Option{
	cue.Attributes(true),
	cue.Concrete(false),
	cue.Definitions(false),
	cue.DisallowCycles(false),
	cue.Docs(false),
	cue.Hidden(false),
	cue.Optional(false),
	cue.ResolveReferences(false),
	// The following are not set
	// nor do they have a bool arg
	// cue.Final(),
	// cue.Raw(),
	// cue.Schema(),
}

// Our custom options
var customOptions = []cue.Option{
	cue.Definitions(true),
	cue.Hidden(true),
	cue.Optional(true),
}

go run fields.go

Without
---------
sub: {
	foo: "bar"
	n:   int
}
list: [1, 2, {
	a: 3
}]
any: _
labels: {}

Default
---------
sub: {
	foo: "bar"
	n:   int
}
list: [1, 2, {
	a: 3
}]
any: _
labels: {}

Custom
---------
sub: {
	foo: "bar"
	n:   int
}
list: [1, 2, {
	a: 3
}]
any: _
#def: {
	val: string
}
_hid: 42
opt: bool
labels: {}

Docs: Fields, Options

Type Switching

You will likely want to make choices based on the type of a value. Use a switch statement on val.IncompleteKind().

switch.go

package main

import (
	"fmt"
	"os"

	"cuelang.org/go/cue"
	"cuelang.org/go/cue/cuecontext"
)

func main() {
	c := cuecontext.New()
	d, _ := os.ReadFile("value.cue")
	val := c.CompileBytes(d)
	val = val.LookupPath(cue.ParsePath("obj"))

	fmt.Println("obj:")
	iter, _ := val.Fields()
	for iter.Next() {
		printNodeType(iter.Value())
	}

	fmt.Println("\nsub:")
	val = val.LookupPath(cue.ParsePath("sub"))
	iter, _ = val.Fields()
	for iter.Next() {
		printNodeType(iter.Value())
	}

}

func printNodeType(val cue.Value) {
	switch val.IncompleteKind() {
	case cue.StructKind:
		fmt.Println("struct")

	case cue.ListKind:
		fmt.Println("list")

	default:
		printLeafType(val)
	}
}

func printLeafType(val cue.Value) {
	fmt.Println(val.IncompleteKind())
}

go run switch.go

obj:
struct
list
_
struct

sub:
string
int

Walking a Value

walk.go

package main

import (
	"fmt"
	"os"

	"cuelang.org/go/cue"
	"cuelang.org/go/cue/cuecontext"
)

func main() {
	c := cuecontext.New()
	d, _ := os.ReadFile("value.cue")
	val := c.CompileBytes(d)

	// before (pre-order) traversal
	preprinter := func(v cue.Value) bool {
		fmt.Printf("%v\n", v)
		return true
	}

	// after (post-order) traversal
	cnt := 0
	postcounter := func(v cue.Value) {
		cnt++
	}

	// walk the value
	val.Walk(preprinter, postcounter)

	// print count
	fmt.Println("\n\nCount:", cnt)
}

go run walk.go

{
	obj: {
		sub: {
			foo: "bar"
			n:   int
		}
		list: [1, 2, {
			a: 3
		}]
		any: _
		labels: {}
	}
}
{
	sub: {
		foo: "bar"
		n:   int
	}
	list: [1, 2, {
		a: 3
	}]
	any: _
	labels: {}
}
{
	foo: "bar"
	n:   int
}
"bar"
int
[1, 2, {
	a: 3
}]
1
2
{
	a: 3
}
3
_
{}


Count: 12

Docs: Walk

Custom Walk

In the previous example for default walk, some of the fields were not traversed. This is because CUE’s default Walk() uses the same default Field() options on a value. In order to walk all fields, we need to write a custom walk function where we can pass in the options for Field().

custom.go

package main

import (
	"fmt"
	"os"

	"cuelang.org/go/cue"
	"cuelang.org/go/cue/cuecontext"
)

const input = `
a: {
	i: int
	j: int | *i
}
`

func main() {
	c := cuecontext.New()
	d, _ := os.ReadFile("value.cue")
	val := c.CompileBytes(d)

	// before (pre-order) traversal
	preprinter := func(v cue.Value) bool {
		fmt.Printf("%v\n", v)
		return true
	}

	// after (post-order) traversal
	cnt := 0
	postcounter := func(v cue.Value) {
		cnt++
	}

	Walk(val, preprinter, postcounter, customOptions...)

}

// Walk is an alternative to cue.Value.Walk which handles more field types
// You can customize this with your own options
func Walk(v cue.Value, before func(cue.Value) bool, after func(cue.Value), options ...cue.Option) {

	// call before and possibly stop recursion
	if before != nil && !before(v) {
		return
	}

	// possibly recurse
	switch v.IncompleteKind() {
	case cue.StructKind:
		if options == nil {
			options = defaultOptions
		}
		s, _ := v.Fields(options...)

		for s.Next() {
			Walk(s.Value(), before, after, options...)
		}

	case cue.ListKind:
		l, _ := v.List()
		for l.Next() {
			Walk(l.Value(), before, after, options...)
		}

		// no default (basic lit types)

	}

	if after != nil {
		after(v)
	}

}

// Cue's default
var defaultOptions = []cue.Option{
	cue.Attributes(true),
	cue.Concrete(false),
	cue.Definitions(false),
	cue.DisallowCycles(false),
	cue.Docs(false),
	cue.Hidden(false),
	cue.Optional(false),
	cue.ResolveReferences(false),
	// The following are not set
	// nor do they have a bool arg
	// cue.Final(),
	// cue.Raw(),
	// cue.Schema(),
}

// Our custom options
var customOptions = []cue.Option{
	cue.Definitions(true),
	cue.Hidden(true),
	cue.Optional(true),
}

go run custom.go

{
	obj: {
		sub: {
			foo: "bar"
			n:   int
		}
		list: [1, 2, {
			a: 3
		}]
		any: _
		labels: {}
	}
}
{
	sub: {
		foo: "bar"
		n:   int
	}
	list: [1, 2, {
		a: 3
	}]
	any: _
	labels: {}
}
{
	foo: "bar"
	n:   int
}
"bar"
int
[1, 2, {
	a: 3
}]
1
2
{
	a: 3
}
3
_
{
	val: string
}
string
42
bool
{}
We'll never share your email with anyone else.
2024 Hofstadter, Inc