mikefarah/yq · error

cannot get keys of %v, keys only works for maps and arrays

Error message

cannot get keys of %v, keys only works for maps and arrays

What it means

The `keys` (and `keys_unsorted`) operator returns the keys of a map or the indices of an array. For any other node kind (scalar or null), there is no meaningful key set, so the operator fails with this error, including the offending tag in the message. It exists to catch expressions applied to the wrong node type.

Source

Thrown at pkg/yqlib/operator_keys.go:54

}

func keysOperator(_ *dataTreeNavigator, context Context, _ *ExpressionNode) (Context, error) {
	log.Debugf("keysOperator")

	var results = list.New()

	for el := context.MatchingNodes.Front(); el != nil; el = el.Next() {
		candidate := el.Value.(*CandidateNode)

		var targetNode *CandidateNode
		switch candidate.Kind {
		case MappingNode:
			targetNode = getMapKeys(candidate)
		case SequenceNode:
			targetNode = getIndices(candidate)
		default:
			return Context{}, fmt.Errorf("cannot get keys of %v, keys only works for maps and arrays", candidate.Tag)
		}

		results.PushBack(targetNode)
	}

	return context.ChildContext(results), nil
}

func getMapKeys(node *CandidateNode) *CandidateNode {
	contents := make([]*CandidateNode, 0)
	for index := 0; index < len(node.Content); index = index + 2 {
		contents = append(contents, node.Content[index])
	}

	seq := &CandidateNode{Kind: SequenceNode, Tag: "!!seq"}
	seq.AddChildren(contents)
	return seq
}

View on GitHub (pinned to 8b5af0694b)

Solutions

  1. Verify the node kind first and branch: `select(kind == "map") | keys`
  2. Provide defaults for missing data: `.cfg // {} | keys`
  3. Select the actual map: `.settings | keys` instead of running keys on the root
  4. Convert scalars to maps upstream if keys of a wrapper object were intended

Example fix

// before
"hello" | keys
// error: cannot get keys of !!str, keys only works for maps and arrays

// after
{a: 1, b: 2} | keys   # => ["a", "b"]
Defensive patterns

Strategy: type-guard

Validate before calling

yq 'select(kind == "map" or kind == "seq")' input.yaml

Type guard

def keys_of(v):
    return list(v.keys()) if isinstance(v, dict) else list(range(len(v))) if isinstance(v, list) else None

Try / catch

out=$(yq '.cfg // {} | keys' f.yaml 2>&1) || {
  echo "keys applied to non-map/non-array: $out" >&2
}

Prevention

When it happens

Trigger: Running `keys` on a scalar (`"hello" | keys`), on null (empty document or missing field), or on a selected field that turned out not to be a map/array.

Common situations: Assuming a YAML field is a map when it's a scalar; empty files producing null documents; optional config sections absent in some files; applying keys at the document root of a scalar-valued JSON.

Related errors


AI-assisted analysis of mikefarah/yq@8b5af0694b (2026-09-05). Data as JSON: /api/errors/9dbe01131791ea3b. Report an issue: GitHub.