query

package module
v2.0.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 22, 2026 License: MIT Imports: 9 Imported by: 0

README

query-go

Go Reference coverage ratio Go Report Card LICENSE

This is a Go package to extract element from a Go value by a query string like $.key[0].key['key']. See usage and example in the API reference.

Basic Usage

ParseString parses a query string and returns the query which extracts the value.

import query "github.com/zoncoen/query-go/v2"

q, err := query.ParseString(`$.key[0].key['key']`)
v, err := q.Extract(ctx, target)

When the queried element is absent, the returned error matches query.ErrNotFound via errors.Is (and errors.As yields a *query.NotFoundError carrying the failed position); any other error is an extraction failure reported by an extractor, such as a context cancellation that interrupted a blocking extractor.

Migrating from v1

  • The module path is github.com/zoncoen/query-go/v2.

  • Query.Extract takes a context.Context; ExtractContext is gone.

  • The extractor interfaces are consolidated: KeyExtractor and IndexExtractor now take a context and return (any, error) — return query.ErrNotFound for an absent element instead of false. The ...Context interface variants are gone.

  • ExtractFunc is func(ctx context.Context, v reflect.Value) (reflect.Value, error).

  • Extractors can now report failures distinct from absence: any non-ErrNotFound error aborts the extraction and is returned to the caller.

  • A type that is not migrated silently stops satisfying the interfaces and falls back to reflection-based extraction. Add a compile-time assertion to each implementation to catch this:

    var _ query.KeyExtractor = (*MyType)(nil)
    

Query Syntax

The query syntax understood by this package when parsing is as follows.

$           the root element
.key        extracts by a key of map or field name of struct ("." can be omitted if the head of query)
['key']     same as the ".key" (if the key contains "\" or "'", these characters must be escaped like "\\", "\'")
[0]         extracts by an index of array or slice (a negative index counts from the end: [-1] is the last element), or by an integer key of map

Documentation

Overview

Package query provides to extract the element from a Go value.

ParseString parses a query string and returns the query which extracts the value.

q, err := query.ParseString(`$.key[0].key['key']`)
v, err := q.Extract(ctx, target)

Query Syntax

The query syntax understood by this package when parsing is as follows.

$           the root element
.key        extracts by a key of map or field name of struct ("." can be omitted if the head of query)
['key']     same as the ".key" (if the key contains "\" or "'", these characters must be escaped like "\\", "\'")
[0]         extracts by an index of array or slice (a negative index counts from the end: [-1] is the last element), or by an integer key of map

Index

Examples

Constants

This section is empty.

Variables

View Source
var ErrNotFound = errors.New("not found")

ErrNotFound is the sentinel error that reports the queried element as absent. Extractor implementations return it (optionally wrapped) when the key or index does not match anything; any other error is treated as an extraction failure and aborts the query.

Callers can test a *Query.Extract error with errors.Is(err, ErrNotFound) to tell a genuinely absent value apart from a failure such as a context cancellation that interrupted a blocking extractor.

Functions

func IsCaseInsensitive

func IsCaseInsensitive(ctx context.Context) bool

IsCaseInsensitive reports whether case-insensitive querying is enabled or not.

Types

type ExtractFunc

type ExtractFunc func(ctx context.Context, v reflect.Value) (reflect.Value, error)

ExtractFunc is the type of the extraction function customized by the CustomExtractFunc option.

type Extractor

type Extractor interface {
	Extract(ctx context.Context, v reflect.Value) (reflect.Value, error)
	String() string
}

An Extractor interface is used by a query to extract the element from a value. Extract returns the extracted value, or ErrNotFound (optionally wrapped) when the element is absent; any other error is treated as an extraction failure and aborts the query.

type Index

type Index struct {
	// contains filtered or unexported fields
}

Index represents an extractor to access the value by index.

For slices and arrays, a negative index accesses the sequence from the end: -1 is the last element, -2 is the second to last, and so on, following the convention of RFC 9535 (JSONPath). An index that remains out of range after this normalization is reported as absent.

For maps with an integer-kinded key type (and interface-keyed maps holding int keys), the index is looked up as the literal map key with no normalization: maps have no order, so -1 means the key -1. Maps with a floating-point key type are deliberately not supported, matching the map key policies of protobuf and CEL: key lookup by equality is unreliable for floating-point values.

Note that a value implementing IndexExtractor receives the index as given (possibly negative); handling negative indices is up to the implementation.

func (*Index) Extract

func (e *Index) Extract(ctx context.Context, v reflect.Value) (reflect.Value, error)

Extract extracts the value from v by index, passing ctx to v.ExtractByIndex if v implements the IndexExtractor interface. It returns ErrNotFound (possibly wrapped) when the index is absent.

func (*Index) String

func (e *Index) String() string

String returns e as string.

type IndexExtractor

type IndexExtractor interface {
	ExtractByIndex(ctx context.Context, index int) (any, error)
}

IndexExtractor is the interface that wraps the ExtractByIndex method.

ExtractByIndex extracts the value by index. It returns the found value, or ErrNotFound (optionally wrapped) when the index is absent; any other error is treated as an extraction failure and aborts the whole query.

type Key

type Key struct {
	// contains filtered or unexported fields
}

Key represents an extractor to access the value by key.

func (*Key) Extract

func (e *Key) Extract(ctx context.Context, v reflect.Value) (reflect.Value, error)

Extract extracts the value from v by key, passing ctx (extended with the query options) to v.ExtractByKey if v implements the KeyExtractor interface. It returns ErrNotFound (possibly wrapped) when the key is absent.

func (*Key) String

func (e *Key) String() string

String returns e as string. The result is parseable: keys that would be tokenized differently in the selector notation (e.g. an empty key, or a key containing "$" or "]") are rendered in the quoted form.

type KeyExtractor

type KeyExtractor interface {
	ExtractByKey(ctx context.Context, key string) (any, error)
}

KeyExtractor is the interface that wraps the ExtractByKey method.

ExtractByKey extracts the value by key. It returns the found value, or ErrNotFound (optionally wrapped) when the key is absent; any other error is treated as an extraction failure and aborts the whole query.

Example
package main

import (
	"context"
	"fmt"

	"github.com/zoncoen/query-go/v2"
)

type orderedMap struct {
	elems []*elem
}

type elem struct {
	k, v any
}

func (m *orderedMap) ExtractByKey(_ context.Context, key string) (any, error) {
	for _, e := range m.elems {
		if k, ok := e.k.(string); ok {
			if k == key {
				return e.v, nil
			}
		}
	}
	return nil, query.ErrNotFound
}

func main() {
	q := query.New().Key("key")
	v, _ := q.Extract(context.Background(), &orderedMap{
		elems: []*elem{{k: "key", v: "value"}},
	})
	fmt.Println(v)
}
Output:
value

type NotFoundError

type NotFoundError struct {
	// Query is the string representation of the whole query.
	Query string
	// FailedAt is the prefix of the query up to and including the extractor
	// that did not match, e.g. ".a.b" when ".a.b.c" failed at "b".
	FailedAt string
	// Err is the error reported by the extractor that did not match. It
	// preserves the diagnostic of an extractor that wrapped ErrNotFound
	// (e.g. "stream ended after 3 messages: not found") and, when the
	// extractor ran a sub-query, the inner *NotFoundError. It is exposed
	// via Unwrap, not via Error, so the message stays stable.
	Err error
}

NotFoundError is the error returned by Query.Extract when the queried element is absent. It matches ErrNotFound with errors.Is and carries the position information of the failure.

func (*NotFoundError) Error

func (e *NotFoundError) Error() string

Error implements the error interface.

func (*NotFoundError) Is

func (e *NotFoundError) Is(target error) bool

Is reports whether target is ErrNotFound, so that errors.Is(err, ErrNotFound) matches a *NotFoundError.

func (*NotFoundError) Unwrap

func (e *NotFoundError) Unwrap() error

Unwrap returns the extractor's original error.

type Option

type Option func(*Query)

Option represents an option for Query.

func CaseInsensitive

func CaseInsensitive() Option

CaseInsensitive returns the Option to match case insensitivity.

Example
package main

import (
	"context"
	"fmt"

	"github.com/zoncoen/query-go/v2"
)

// Person represents a person.
type Person struct {
	Name string `json:"name,omitempty"`
}

func main() {
	person := Person{
		Name: "Alice",
	}
	q := query.New(query.CaseInsensitive()).Key("NAME")
	name, _ := q.Extract(context.Background(), person)
	fmt.Println(name)
}
Output:
Alice

func CustomExtractFunc

func CustomExtractFunc(f func(ExtractFunc) ExtractFunc) Option

CustomExtractFunc returns the Option to customize the behavior of extractors.

Example
package main

import (
	"context"
	"fmt"
	"reflect"

	"github.com/zoncoen/query-go/v2"
)

// Person represents a person.
type Person struct {
	Name string `json:"name,omitempty"`
}

func main() {
	person := Person{
		Name: "Alice",
	}
	q := query.New(
		query.CustomExtractFunc(func(f query.ExtractFunc) query.ExtractFunc {
			return func(ctx context.Context, v reflect.Value) (reflect.Value, error) {
				return reflect.ValueOf("Bob"), nil
			}
		}),
	).Key("name")
	name, _ := q.Extract(context.Background(), person)
	fmt.Println(name)
}
Output:
Bob

func CustomIsInlineStructFieldFunc

func CustomIsInlineStructFieldFunc(f func(reflect.StructField) bool) Option

CustomIsInlineStructFieldFunc returns the Option to customize the behavior of extractors.

func CustomStructFieldNameGetter deprecated

func CustomStructFieldNameGetter(f func(f reflect.StructField) string) Option

CustomStructFieldNameGetter returns the Option to set f as custom function which gets struct field name. f is called by Key.Extract to get struct field name, if the target value is a struct.

Deprecated: Use CustomExtractFunc instead.

Example
package main

import (
	"context"
	"fmt"
	"reflect"
	"strings"

	"github.com/zoncoen/query-go/v2"
)

// Person represents a person.
type Person struct {
	Name string `json:"name,omitempty"`
}

// getFieldNameByJSONTag returns the JSON field tag as field name if exists.
func getFieldNameByJSONTag(field reflect.StructField) string {
	tag, ok := field.Tag.Lookup("json")
	if ok {
		strs := strings.Split(tag, ",")
		return strs[0]
	}
	return field.Name
}

func main() {
	person := Person{
		Name: "Alice",
	}

	q := query.New(
		query.CustomStructFieldNameGetter(getFieldNameByJSONTag),
	).Key("name")
	name, _ := q.Extract(context.Background(), person)
	fmt.Println(name)
}
Output:
Alice

func ExtractByStructTag

func ExtractByStructTag(tagNames ...string) Option

ExtractByStructTag returns the Option to allow extracting by struct tag. The tag names are copied, so later mutation of a slice passed with ... has no effect on the option.

Example
package main

import (
	"context"
	"fmt"

	"github.com/zoncoen/query-go/v2"
)

// Person represents a person.
type Person struct {
	Name string `json:"name,omitempty"`
}

func main() {
	person := Person{
		Name: "Alice",
	}
	q := query.New(query.ExtractByStructTag("json")).Key("name")
	name, _ := q.Extract(context.Background(), person)
	fmt.Println(name)
}
Output:
Alice

func OptionsFromContext

func OptionsFromContext(ctx context.Context) []Option

OptionsFromContext returns the options of the query that initiated the current extraction, or nil outside an extraction. An extractor implementation that builds a sub-query — e.g. a KeyExtractor delegating into a nested structure — can pass them to New so that the caller's configuration (struct tags, custom extract funcs, ...) applies to the nested extraction as well, without any global registry:

q := query.New(query.OptionsFromContext(ctx)...).Key("nested")

The returned slice is a copy: appending to it or mutating it in place cannot affect other extractors in the same extraction.

type Query

type Query struct {
	// contains filtered or unexported fields
}

Query represents a query to extract the element from a value.

func New

func New(opts ...Option) *Query

New returns a new query.

func Parse

func Parse(r io.Reader, opts ...Option) (*Query, error)

Parse parses a query string via r and returns the corresponding Query.

func ParseString

func ParseString(s string, opts ...Option) (*Query, error)

ParseString parses a query string s and returns the corresponding Query.

Example
package main

import (
	"context"
	"fmt"

	"github.com/zoncoen/query-go/v2"
)

type S struct {
	Maps []map[string]map[string]string
}

func main() {
	q, err := query.ParseString(`$.Maps[0].key['.key\'']`)
	if err == nil {
		v, _ := q.Extract(context.Background(), &S{
			Maps: []map[string]map[string]string{
				{"key": map[string]string{
					".key'": "value",
				}},
			},
		})
		fmt.Println(v)

	}
}
Output:
value

func (Query) Append

func (q Query) Append(es ...Extractor) *Query

Append appends extractor to q and returns updated q.

func (*Query) Extract

func (q *Query) Extract(ctx context.Context, target any) (any, error)

Extract extracts the value by q from target, passing ctx to each extractor (e.g. a value implementing KeyExtractor or IndexExtractor).

When the queried element is absent, the returned error is a *NotFoundError matching ErrNotFound via errors.Is. Any other error reported by an extractor — e.g. a context cancellation that interrupted a blocking extractor — aborts the extraction and is returned wrapped with the position of the failing extractor.

func (*Query) Extractors

func (q *Query) Extractors() []Extractor

Extractors returns a copy of the query extractors of q, so that mutating the returned slice cannot corrupt q. Note that extractors carry a snapshot of the options they were created with, but a query rebuilt from them via Append does not: OptionsFromContext inside such a query reflects the new query's own options only.

func (Query) Index

func (q Query) Index(i int) *Query

Index is shorthand method to create Index and appends it. For slices and arrays, a negative i accesses the sequence from the end (-1 is the last element); for integer-keyed maps, i is the literal map key. See Index for the exact semantics.

func (Query) Key

func (q Query) Key(k string) *Query

Key is shorthand method to create Key and appends it.

func (Query) Root

func (q Query) Root() *Query

Root marks that q has an explicit root operator $.

func (*Query) String

func (q *Query) String() string

String returns q as string.

Directories

Path Synopsis
Package ast declares the types used to represent syntax trees.
Package ast declares the types used to represent syntax trees.
Package parser implements a parser for a query string.
Package parser implements a parser for a query string.
Package token defines constants representing the lexical tokens.
Package token defines constants representing the lexical tokens.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL