daq

package module
v0.6.0 Latest Latest
Warning

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

Go to latest
Published: Jan 6, 2024 License: MIT Imports: 6 Imported by: 7

README

daq – Data Queries

Access nested properties in Go data structures.

Go Reference

Go code for working with dynamic data structures can sometimes become somewhat verbose. E.g. like this:

var data any
json.Unmarshal([]byte(`{
	"glossary": {
		"title": "example glossary",
		"GlossDiv": {
			"title": "S",
			"GlossList": {
				"GlossEntry": {
					"ID": "SGML",
					"GlossTerm": "Standard Generalized Markup Language"
				}
			}
		}
	}
}`), &data)

title := "-no title-"
jObj, ok := data.(map[string]any)
if !ok {
	fmt.Println(title)
	return
}
if data = jObj["glossary"]; data == nil {
	fmt.Println(title)
	return
}
if jObj, ok = data.(map[string]any); !ok {
	fmt.Println(title)
	return
}
if data = jObj["title"]; data == nil {
	fmt.Println(title)
	return
}
title, _ = data.(string)

fmt.Println(title)
// Output:
// example glossary

Package daq helps for example to make it much more concise:

var data any
json.Unmarshal([]byte(`{
	"glossary": {
		"title": "example glossary",
		"GlossDiv": {
			"title": "S",
			"GlossList": {
				"GlossEntry": {
					"ID": "SGML",
					"GlossTerm": "Standard Generalized Markup Language"
				}
			}
		}
	}
}`), &data)

title := StringOr("-no title-")(Get(data, "glossary", "title"))

fmt.Println(title)
// Output:
// example glossary

In addition, daq can do even more, e.g. evaluate static data structures with the same API, iterate and … please consult the reference.

Documentation

Overview

Access nested properties in Go data structures with Data Queries

Concepts

  • A query result is (res any, stat Status, err error), where res is the queried value, stat says whether res was found and err says whether something went wrong.

  • Use assert functions or fallback functions to easily use query results with an expected type.

  • Use As to create an assertion function from a convert function.

  • Use Val to create a fallback function from a convert function.

  • The convert function for target type T is func(v any) (r T, err error). If conversion of v fails, err != nil and T's zero value. Otherwise, r is the conversion result.

  • By convention, convert functions for target type <T> are named To<T>(). Predefined assert functions are named As<T>(), predefined fallback functions <T>Or().

  • Use Get as the general way to perform queries. Read on to find more specific and efficient ways to query:

    Index, Field, Key to step with int, string and any.

    And also SliceAny, DictAny, MapAny to get query results that can be asserted from typical generic Go data.

  • To extend daq to your own types write convert functions and familiarize with Stepper, Slice, Dict and Map.

Query Results

A query result is a multi-value return of the form (res any, stat Status, err error). This triple is designed to be accepted by many of daq's functions to create a convenient syntax.

  • For successful queries – single or multi-level – res is the result value of the query.

  • For successful queries, stat is 0. Otherwise, stat indicates the number of steps that have not yet been successfully executed. For a valid query it is not considered an error when nothing is found.

  • Executing invalid queries, e.g. indexing a slice with a string, leads to err != nil. Then err describes the problem.

For queries, stat==0 => err==nil must apply. Assert or fallback functions may deviate from this.

Convert Functions for Go's numeric types

  • Signed and unsigned integer types are promoted to each other as long as the actual runtime value is in target type's range.

  • Float values are promoted to integer types if the actual float has no fraction, and it is in target type's range.

  • Complex values with imag()==0 are promoted to integer by promoting the real() part according to the float rules.

Example
var data any
json.Unmarshal([]byte(`{
		"a": 3.1415,
		"bs": [1, 2, 3]
	}`), &data)

prop, rest, _ := Get(data, "a") // Ignore error in example for brevity
fmt.Println(prop, rest)

prop, rest, err := Get(data, "b") // This will fail, no "b"
fmt.Println(prop, rest, err)

x, rest, _ := AsFloat64(Get(data, "bs", 1)) // Type assertion (encoding/json uses float64 for numbers)
fmt.Println(x, rest)

x, rest, _ = AsFloat64(Get(data, "bs", -1)) // Access backwards from end
fmt.Println(x, rest)

x = Float64Or(-1)(Get(data, "bs", 3)) // Fallback to -1 if Get fails
fmt.Println(x)
Output:

3.1415 0
map[a:3.1415 bs:[1 2 3]] 1 <nil>
2 0
3 0
-1
Example (Readme_daq)
var data any
json.Unmarshal([]byte(`{
		"glossary": {
			"title": "example glossary",
			"GlossDiv": {
				"title": "S",
				"GlossList": {
					"GlossEntry": {
						"ID": "SGML",
						"GlossTerm": "Standard Generalized Markup Language"
					}
				}
			}
		}
	}`), &data)

title := StringOr("-no title-")(Get(data, "glossary", "title"))

fmt.Println(title)
Output:

example glossary
Example (Readme_plainGo)
var data any
json.Unmarshal([]byte(`{
		"glossary": {
			"title": "example glossary",
			"GlossDiv": {
				"title": "S",
				"GlossList": {
					"GlossEntry": {
						"ID": "SGML",
						"GlossTerm": "Standard Generalized Markup Language"
					}
				}
			}
		}
	}`), &data)

title := "-no title-"
jObj, ok := data.(map[string]any)
if !ok {
	fmt.Println(title)
	return
}
if data = jObj["glossary"]; data == nil {
	fmt.Println(title)
	return
}
if jObj, ok = data.(map[string]any); !ok {
	fmt.Println(title)
	return
}
if data = jObj["title"]; data == nil {
	fmt.Println(title)
	return
}
title, _ = data.(string)

fmt.Println(title)
Output:

example glossary

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func As added in v0.4.1

func As[T any](conv func(any) (T, error)) func(any, Status, error) (T, Status, error)

As returns the assert function for the specified type conversion conv to type T. Assert functions take query results as input. In any case, assert functions

  • return the status of the query result unchanged.

  • never return error==nil if the query result status was not OK. If necessary, a [NoFound] error is used.

If the query result already had an error, As returns the null value of T and the error. Otherwise it perform the conversion – even if the input statis was not OK.

This means that error==nil is only returned if the conversion of a complete query is successfully converted.

Example
data := struct {
	Foo int
	Bar string
}{
	Foo: 4711,
	Bar: "baz",
}
fmt.Println(As(ToString)(Get(data, "Foo")))
fmt.Println(As(ToString)(Get(data, "Bar")))
fmt.Println(As(ToString)(Get(data, "void")))
Output:

0 cannot return int as string
baz 0 <nil>
 1 cannot return struct { Foo int; Bar string } as string

func AsDict added in v0.4.1

func AsDict(v any, stat Status, err error) (Dict, Status, error)

func AsMap

func AsMap(v any, stat Status, err error) (Map, Status, error)

func AsSlice

func AsSlice(v any, stat Status, err error) (Slice, Status, error)
Example
s := SliceOr(nil)(DictAny(testExample).Get("a"))
for i := 0; i < s.DaQLen(); i++ {
	res, rest, err := s.DaQGet(i)
	must.Do(err)
	fmt.Println(res, rest)
}
Output:

map[b:map[c:3] d:foo] 0
false 0

func BoolOr

func BoolOr(fallback bool) func(any, Status, error) bool

func Complex128Or added in v0.6.0

func Complex128Or(fallback complex128) func(any, Status, error) complex128

func Complex64Or added in v0.6.0

func Complex64Or(fallback complex64) func(any, Status, error) complex64

func DeepEqual added in v0.5.0

func DeepEqual(lhs, rhs any) bool

func DeepEqualFunc added in v0.5.0

func DeepEqualFunc(lhs, rhs any, eq func(any, any) bool) bool

func DictOr added in v0.4.1

func DictOr(fallback Dict) func(any, Status, error) Dict

func DurationOr

func DurationOr(fallback time.Duration) func(any, Status, error) time.Duration

func Float32Or

func Float32Or(fallback float32) func(any, Status, error) float32

func Float64Or

func Float64Or(fallback float64) func(any, Status, error) float64

func GetTrack

func GetTrack(data any, path ...any) (t Track, stat Status, err error)

GetTrack is like Get but additionally returns all intermediate results on the path.

func Int16Or

func Int16Or(fallback int16) func(any, Status, error) int16

func Int32Or

func Int32Or(fallback int32) func(any, Status, error) int32

func Int64Or

func Int64Or(fallback int64) func(any, Status, error) int64

func Int8Or

func Int8Or(fallback int8) func(any, Status, error) int8

func IntOr

func IntOr(fallback int) func(any, Status, error) int

func MapOr

func MapOr(fallback Map) func(any, Status, error) Map

func Must added in v0.6.0

func Must[T any](res T, stat Status, err error) T

Must checks stat to be OK using OK and panics if the resulting error is not nil. Otherwiese Must returns res.

func SliceOr

func SliceOr(fallback Slice) func(any, Status, error) Slice

func StepEach added in v0.3.0

func StepEach(data any, do func(kind reflect.Kind, key, value any) error) error
Example (Array)
data := [4]any{0, '1', "two", nil}
err := StepEach(data, testPrintStep)
fmt.Println(err)
Output:

kind 'array': key=0 / value=0
kind 'array': key=1 / value=49
kind 'array': key=2 / value="two"
kind 'array': key=3 / value=<nil>
<nil>
Example (Atom)
err := StepEach(5180, testPrintStep)
fmt.Println(err)
Output:

kind 'invalid': key=<nil> / value=5180
<nil>
Example (Map)
data := map[any]any{
	false: "foo",
	7:     nil,
	"bar": 4711,
}
err := StepEach(data, testPrintStep)
fmt.Println(err)
Output:

kind 'map': key=false / value="foo"
kind 'map': key=7 / value=<nil>
kind 'map': key="bar" / value=4711
<nil>
Example (Slice)
data := []any{0, '1', "two", nil}
err := StepEach(data, testPrintStep)
fmt.Println(err)
Output:

kind 'slice': key=0 / value=0
kind 'slice': key=1 / value=49
kind 'slice': key=2 / value="two"
kind 'slice': key=3 / value=<nil>
<nil>
Example (Struct)
type Tstnest struct {
	Foo int
}
data := struct {
	Tstnest
	Bar  string
	Nest Tstnest
}{
	Tstnest: Tstnest{Foo: 4711},
	Bar:     "baz",
	Nest:    Tstnest{Foo: -1},
}
err := StepEach(data, testPrintStep)
fmt.Println(err)
Output:

kind 'struct': key="Foo" / value=4711
kind 'struct': key="Bar" / value="baz"
kind 'struct': key="Nest" / value=daq.Tstnest{Foo:-1}
<nil>

func StringOr

func StringOr(fallback string) func(any, Status, error) string

func TestingDeepEqual added in v0.5.0

func TestingDeepEqual(t testing.TB, lhs, rhs any) (diffCount int)

func TestingDeepEqualFunc added in v0.5.0

func TestingDeepEqualFunc(t testing.TB, lhs, rhs any, eq func(any, any) bool) (diffCount int)

func TimeOr

func TimeOr(fallback time.Time) func(any, Status, error) time.Time

func ToBool added in v0.4.1

func ToBool(v any) (bool, error)

func ToComplex128 added in v0.6.0

func ToComplex128(v any) (complex128, error)

func ToComplex64 added in v0.6.0

func ToComplex64(v any) (complex64, error)

func ToDuration added in v0.4.1

func ToDuration(v any) (time.Duration, error)

func ToFloat32 added in v0.4.1

func ToFloat32(v any) (float32, error)

func ToFloat64 added in v0.4.1

func ToFloat64(v any) (float64, error)

func ToInt added in v0.4.1

func ToInt(v any) (int, error)

func ToInt16 added in v0.4.1

func ToInt16(v any) (int16, error)

func ToInt32 added in v0.4.1

func ToInt32(v any) (int32, error)

func ToInt64 added in v0.4.1

func ToInt64(v any) (int64, error)

func ToInt8 added in v0.4.1

func ToInt8(v any) (int8, error)

func ToString added in v0.4.1

func ToString(v any) (string, error)

func ToTime added in v0.4.1

func ToTime(v any) (time.Time, error)
Example
t := time.Date(2023, 12, 29, 2, 4, 0, 0, time.UTC)
fmt.Println(ToTime(t))
fmt.Println(ToTime(&t))
fmt.Println(ToTime((*time.Time)(nil)))
Output:

2023-12-29 02:04:00 +0000 UTC <nil>
2023-12-29 02:04:00 +0000 UTC <nil>
0001-01-01 00:00:00 +0000 UTC <nil>

func ToUint added in v0.4.1

func ToUint(v any) (uint, error)

func ToUint16 added in v0.4.1

func ToUint16(v any) (uint16, error)

func ToUint32 added in v0.4.1

func ToUint32(v any) (uint32, error)

func ToUint64 added in v0.4.1

func ToUint64(v any) (uint64, error)

func ToUint8 added in v0.4.1

func ToUint8(v any) (uint8, error)

func ToUintPtr added in v0.4.1

func ToUintPtr(v any) (uintptr, error)

func Uint16Or

func Uint16Or(fallback uint16) func(any, Status, error) uint16

func Uint32Or

func Uint32Or(fallback uint32) func(any, Status, error) uint32

func Uint64Or

func Uint64Or(fallback uint64) func(any, Status, error) uint64

func Uint8Or

func Uint8Or(fallback uint8) func(any, Status, error) uint8

func UintOr

func UintOr(fallback uint) func(any, Status, error) uint

func UintPtrOr

func UintPtrOr(fallback uintptr) func(any, Status, error) uintptr

func Val

func Val[T any](conv func(any) (T, error), fallback T) func(any, Status, error) T

Val returns a fallback function for the given type conversion conv. A fallback function always returns a value of expected type T. It returns the asserted value of As in case of success, i.e. status OK and err==nil. Otherwise it returns the fallback.

Example
data := struct {
	Foo int
	Bar string
}{
	Foo: 4711,
	Bar: "baz",
}
fmt.Println(Val(ToString, "fallback1")(Get(data, "Foo")))
fmt.Println(Val(ToString, "fallback2")(Get(data, "Bar")))
fmt.Println(Val(ToString, "fallback3")(Get(data, "void")))
Output:

fallback1
baz
fallback3

Types

type Alt

type Alt struct {
	S  any
	Or any
}
Example
data := []string{"foo", "bar", "baz"}
fmt.Println(Step(data, "bar"))
fmt.Println(Step(data, Alt{S: "bar", Or: 4711}))
Output:

<nil> 1 key lookup: cannot use []string as daq.Map
4711 0 <nil>

func (Alt) Step

func (a Alt) Step(data any) (any, Status, error)

type CollectionTypeError added in v0.5.0

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

func (CollectionTypeError) Error added in v0.5.0

func (e CollectionTypeError) Error() string

func (CollectionTypeError) Is added in v0.5.0

func (e CollectionTypeError) Is(err error) bool

type Delta added in v0.5.0

type Delta struct {
	AtomEqual   func(lhs, rhs any) bool
	OnNotEqual  func(path []any, left, right any) error
	OnLeftOnly  func(path []any, val any) error
	OnRightOnly func(path []any, val any) error
}

func (*Delta) Compare added in v0.5.0

func (cmpr *Delta) Compare(l, r any) error

type Dict added in v0.4.1

type Dict interface {
	DaQGet(name string) (res any, stat Status, err error)
	DaQEachKey(do func(key string) error) error
}

Dict is queried with a string as name.

func ToDict added in v0.4.1

func ToDict(v any) (Dict, error)

type DictAny added in v0.4.1

type DictAny map[string]any

func (DictAny) DaQEachKey added in v0.4.1

func (d DictAny) DaQEachKey(do func(key string) error) error

func (DictAny) DaQGet added in v0.4.1

func (d DictAny) DaQGet(n string) (any, Status, error)

func (DictAny) Get added in v0.6.0

func (d DictAny) Get(name string) (any, Status, error)

func (DictAny) GetOK added in v0.6.0

func (d DictAny) GetOK(n string) (any, Status, error)

type FirstIdxOf added in v0.5.0

type FirstIdxOf []int
Example
data := []string{"foo", "bar", "baz"}
fmt.Println(Step(data, FirstIdxOf{99, 1})) // more efficient
fmt.Println(Step(data, FirstOf{99, 1}))    // more general
Output:

bar 0 <nil>
bar 0 <nil>

func (FirstIdxOf) Step added in v0.5.0

func (idxs FirstIdxOf) Step(data any) (res any, stat Status, err error)

type FirstKeyOf added in v0.5.0

type FirstKeyOf []any
Example
data := map[any]any{
	"foo": 1,
	"bar": 2,
	"baz": 3,
}
fmt.Println(Step(data, FirstKeyOf{"quux", "bar"})) // more efficient
fmt.Println(Step(data, FirstOf{"quux", "bar"}))    // more general
Output:

2 0 <nil>
2 0 <nil>

func (FirstKeyOf) Step added in v0.5.0

func (ns FirstKeyOf) Step(data any) (res any, stat Status, err error)

type FirstNameOf

type FirstNameOf []string
Example
data := map[string]int{
	"foo": 1,
	"bar": 2,
	"baz": 3,
}
fmt.Println(Step(data, FirstNameOf{"quux", "bar"})) // more efficient
fmt.Println(Step(data, FirstOf{"quux", "bar"}))     // more general
Output:

2 0 <nil>
2 0 <nil>

func (FirstNameOf) Step

func (ns FirstNameOf) Step(data any) (res any, stat Status, err error)

type FirstOf

type FirstOf []any

func (FirstOf) Step

func (keys FirstOf) Step(data any) (res any, stat Status, err error)

type Map

type Map interface {
	DaQGet(key any) (res any, stat Status, err error)
	DaQEachKey(do func(key any) error) error
}

Map is queried with any comparable value as key.

func ToMap added in v0.4.1

func ToMap(v any) (Map, error)

type MapAny added in v0.4.1

type MapAny map[any]any

func (MapAny) DaQEachKey added in v0.4.1

func (m MapAny) DaQEachKey(do func(key any) error) error

func (MapAny) DaQGet added in v0.4.1

func (m MapAny) DaQGet(key any) (any, Status, error)

func (MapAny) Key added in v0.6.0

func (m MapAny) Key(key any) (any, Status, error)

func (MapAny) KeyOK added in v0.6.0

func (m MapAny) KeyOK(k any) (any, Status, error)

type NotFound added in v0.6.0

type NotFound Status

func (NotFound) Error added in v0.6.0

func (e NotFound) Error() string

func (NotFound) Is added in v0.6.0

func (e NotFound) Is(err error) bool

type Slice

type Slice interface {
	DaQGet(index int) (res any, stat Status, err error)
	DaQLen() int
}

Slice is queried with an int index. Negative indices access from the end with -1 being the last element and -DaQLen() the first.

func ToSlice added in v0.4.1

func ToSlice(v any) (Slice, error)

type SliceAny added in v0.4.1

type SliceAny []any

func (SliceAny) DaQGet added in v0.4.1

func (s SliceAny) DaQGet(i int) (any, Status, error)

func (SliceAny) DaQLen added in v0.4.1

func (s SliceAny) DaQLen() int

func (SliceAny) Get added in v0.6.0

func (s SliceAny) Get(index int) (any, Status, error)

func (SliceAny) GetOK added in v0.6.0

func (s SliceAny) GetOK(index int) (any, Status, error)

type Status added in v0.6.0

type Status int

func AsBool

func AsBool(v any, stat Status, err error) (bool, Status, error)
Example
fmt.Println(AsBool(Get(true)))
fmt.Println(BoolOr(false)(Get(4711)))
Output:

true 0 <nil>
false

func AsComplex128 added in v0.6.0

func AsComplex128(v any, stat Status, err error) (complex128, Status, error)

func AsComplex64 added in v0.6.0

func AsComplex64(v any, stat Status, err error) (complex64, Status, error)

func AsDuration

func AsDuration(v any, stat Status, err error) (time.Duration, Status, error)

func AsFloat32

func AsFloat32(v any, stat Status, err error) (float32, Status, error)

func AsFloat64

func AsFloat64(v any, stat Status, err error) (float64, Status, error)

func AsInt

func AsInt(v any, stat Status, err error) (int, Status, error)

func AsInt16

func AsInt16(v any, stat Status, err error) (int16, Status, error)

func AsInt32

func AsInt32(v any, stat Status, err error) (int32, Status, error)

func AsInt64

func AsInt64(v any, stat Status, err error) (int64, Status, error)

func AsInt8

func AsInt8(v any, stat Status, err error) (int8, Status, error)

func AsString

func AsString(v any, stat Status, err error) (string, Status, error)
Example
fmt.Println(StringOr("-")(DictAny(testExample).Get("a")))
fmt.Println(Val(ToString, "-")(Get(testExample, "a", 0, "d")))
Output:

-
foo

func AsTime

func AsTime(v any, stat Status, err error) (time.Time, Status, error)

func AsUint

func AsUint(v any, stat Status, err error) (uint, Status, error)

func AsUint16

func AsUint16(v any, stat Status, err error) (uint16, Status, error)

func AsUint32

func AsUint32(v any, stat Status, err error) (uint32, Status, error)

func AsUint64

func AsUint64(v any, stat Status, err error) (uint64, Status, error)

func AsUint8

func AsUint8(v any, stat Status, err error) (uint8, Status, error)

func AsUintPtr

func AsUintPtr(v any, stat Status, err error) (uintptr, Status, error)

func Field

func Field(data any, name string) (res any, stat Status, err error)

func Get

func Get(data any, path ...any) (res any, stat Status, err error)

Get queries data for an element res by using the path of steps. See Step and Stepper from details on steps. Get terminates the query if a step is not successful. Get returns the last obtained result, the number of pending steps and, if applicable, the error of the last step.

Example
data := map[string]any{
	"a": []any{
		map[string]any{
			"b": map[any]any{"c": 3},
			"d": "foo",
		},
		false,
	},
}
var i int = IntOr(-1)(Get(data, "a", 0, "b", "c"))
fmt.Println(i)
fmt.Println(Get(data, "a", 0, "b", "d"))
fmt.Println(Get(nil, "a", 0, "b", "d"))
Output:

3
map[c:3] 1 <nil>
<nil> 4 <nil>

func Index

func Index(data any, index int) (res any, stat Status, err error)

func Key added in v0.4.1

func Key(data, key any) (res any, stat Status, err error)

func OK added in v0.6.0

func OK(res any, stat Status, err error) (any, Status, error)

OK checks valid query results (err==nil) to be OK. If stat is not OK, it return res, stat and the corresponding NotFound error. Otherwise OK returns its input unchanged.

func Step

func Step(data, step any) (res any, stat Status, err error)

Step does the step from a data value to an element value res. The Go types int and string are always acceped as steps in a query.

- With int you can go one step from Slice or Map to an element.

- With string you can go one step from Dict or Map to an element.

It is not considered an error if data does not have an element matching step. For a successful step, where data has a matching element, Step returns stat==0, otherwise stat==1.

func (Status) OK added in v0.6.0

func (s Status) OK() bool

func (Status) SplitPath added in v0.6.0

func (s Status) SplitPath(p []any) (done, pending []any)

SplitPath may panic if p is shorter than the path that created s.

type StepError

type StepError struct {
	N    int
	Step any
	Err  error
}

func (StepError) Error

func (e StepError) Error() string

func (StepError) Unwrap

func (e StepError) Unwrap() error

type Stepper

type Stepper interface {
	// stat == 0 => err == nil
	Step(data any) (res any, stat Status, err error)
}

Stepper allows to define how Step does the step from a data value to an element value. Implement Stepper for your own strategy to navigate your data.

type Time

type Time string
Example
data := struct {
	Time string
}{
	Time: "2023-12-29T22:04:00Z",
}
// Asserting time.Time needs parsing, so we use Time() to create a parsing assertion
fmt.Println(Val(Time(time.RFC3339).To, time.Time{})(Get(data, "Time")))
fmt.Println(Val(Time(time.RFC3339).To, time.Time{})(Get(data, "void")))
Output:

2023-12-29 22:04:00 +0000 UTC
0001-01-01 00:00:00 +0000 UTC

func (Time) To added in v0.4.1

func (t Time) To(v any) (time.Time, error)

Convert function producing time.Time that also uses time.Parse() with layout t when v is string or fmt.Stringer. Otherwise it converts like ToTime.

type Track

type Track []any

func (Track) Step added in v0.5.0

func (t Track) Step(i int) any

Jump to

Keyboard shortcuts

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