Skip to content

Go package for accessing (using dot and array notation) and converting any kind of data.

License

Notifications You must be signed in to change notification settings

moukoublen/pick

Folders and files

NameName
Last commit message
Last commit date
Mar 14, 2025
Mar 4, 2025
Feb 12, 2025
Mar 4, 2025
Mar 14, 2025
Apr 16, 2025
Oct 10, 2024
Nov 10, 2024
Feb 13, 2025
Oct 10, 2024
Nov 24, 2024
Mar 4, 2025
Mar 4, 2025
Feb 6, 2025
Mar 14, 2025
Mar 4, 2025
Mar 4, 2025
Mar 4, 2025
Mar 4, 2025
Mar 4, 2025
Mar 4, 2025
Mar 4, 2025
Mar 4, 2025
Mar 4, 2025
Mar 4, 2025
Mar 4, 2025
Mar 4, 2025
Mar 4, 2025
Mar 14, 2025
Mar 4, 2025
Mar 4, 2025
Mar 4, 2025
Feb 1, 2024
Feb 6, 2025
Mar 4, 2025
Mar 4, 2025
Feb 13, 2025
Feb 6, 2025
Nov 10, 2024
Mar 4, 2025
Mar 4, 2025
Mar 4, 2025
Mar 4, 2025
Mar 4, 2025
Mar 4, 2025
Mar 4, 2025

Repository files navigation

Pick

CI Status codecov Go Report Card PkgGoDev

Pick is a go package to access (using dot and array notation) and convert any kind of data, using best effort performance and best effort convert. It is an alternative to stretchr/objx aiming to provide three main things:

  1. Modular approach regarding the converter, traverser and selector format
  2. Best effort performance using reflect as last resort.
  3. Best effort convert aiming to cast and convert between types as much as possible.

Install

go get -u github.com/moukoublen/pick

Examples

j := `{
    "item": {
        "one": 1,
        "two": "ok",
        "three": ["element 1", 2, "element 3"]
    },
    "float": 2.12,
    "floatDec": 2
}`

p1, _ := WrapJSON([]byte(j))
got, err := p1.String("item.three[1]")  // "2", nil
got, err := p1.Uint64("item.three[1]")  // uint64(2), nil
got, err := p1.String("item.three[-1]") // "element 3", nil | (access the last element)element
got, err := p1.Float32("float")         // float32(2.12), nil
got, err := p1.Int64("float")           // int64(2), ErrConvertLostDecimals
got, err := p1.Int64("floatDec")        // int64(2), nil
got, err := p1.Int32("non-existing")    // 0, ErrFieldNotFound

// Relaxed API (no error in return)
got := p1.Relaxed().Int32("item.one")  // int32(1)
got := p1.Relaxed().Int32("none")      // int32(0)

// Relaxed with errors sink
sink := ErrorsSink{}
sm2 := p1.Relaxed(&sink)
got := sm2.String("item.three[1]") // "2"
got := sm2.Uint64("item.three[1]") // uint64(2)
got := sm2.Int32("item.one")       // int32(1)
got := sm2.Float32("float")        // float32(2.12)
got := sm2.Int64("float")          // int64(2) | error lost decimals
got := sm2.String("item.three")    // ""       | error field not found
err := sink.Outcome()              // joined error

Generics functions

got, err := Get[int64](p1, "item.three[1]")  // (int64(2), nil)
got, err := Get[string](p1, "item.three[1]") // ("2", nil)

m := p1.Relaxed()
got := RelaxedGet[string](m, "item.three[1]") // "2"

got, err := Path[string](p1, Field("item"), Field("three"), Index(1)) // ("2", nil)
got := RelaxedPath[float32](m, Field("item"), Field("one")          // float32(1)

Map functions

j2 := `{
    "items": [
        {"id": 34, "name": "test1", "array": [1,2,3]},
        {"id": 35, "name": "test2", "array": [4,5,6]},
        {"id": 36, "name": "test3", "array": [7,8,9]}
    ]
}`
p2, _ := WrapJSON([]byte(j2))

got, err := Map(p2, "items", func(p Picker) (int16, error) {
    n, _ := p.Int16("id")
    return n, nil
}) // ( []int16{34, 35, 36}, nil )

got, err := FlatMap(p2, "items", func(p Picker) ([]int16, error) {
    return p.Int16Slice("array")
}) // ( []int16{1, 2, 3, 4, 5, 6, 7, 8, 9}, nil )

got3, err3 := MapFilter(p2, "items", func(p Picker) (int32, bool, error) {
    i, err := p.Int32("id")
    return i, i%2 == 0, err
}) // ( []int32{34, 36}, nil )

Each/EachField functions

j := `{
    "2023-01-01": [1,2,3],
    "2023-01-02": [1,2,3,4],
    "2023-01-03": [1,2,3,4,5]
}`
p, _ := WrapJSON([]byte(j))

lens := map[string]int{}
err := EachField(p, "", func(field string, value Picker, numOfFields int) error {
    l, _ := value.Len("")
    lens[field] = l
    return nil
}) // nil
// lens == map[string]int{"2023-01-01": 3, "2023-01-02": 4, "2023-01-03": 5})


sumEvenIndex := 0
err = Each(p, "2023-01-03", func(index int, item Picker, totalLength int) error {
    if index%2 == 0 {
        sumEvenIndex += item.Relaxed().Int("")
    }
    return nil
}) // nil
// sumEvenIndex == 6

API

As an API we define a set of functions like this Bool(T) Output for all basic types. There are 2 different APIs for a picker.

  • Default API, the default one that is embedded in Picker.
    E.g. Picker.Bool(selector string) (bool, error)
  • Relaxed API that can be accessed by calling Picker.Relaxed().
    E.g. Picker.Relaxed().Bool(selector string) bool. Relaxed API does not return error neither it panics in case of one. It just ignores any possible error and returns the default zero value in case of one. Errors can be optionally gathered using an ErrorGatherer.

Supported Types in API

  • bool / []bool
  • byte / []byte
  • float{32,64} / []float{32,64}
  • int{,8,16,32,64} / []int{,8,16,32,64}
  • uint{,8,16,32,64} / []uint{,8,16,32,64}
  • string / []string
  • time.Time / []time.Time
  • time.Duration / []time.Duration

Relaxed API

p1.Relaxed().String("item.three[1]") // == "2"
sm := p1.Relaxed()
sm.Uint64("item.three[1]") // == uint64(2)

Optionally an ErrorGatherer could be provided to .Relaxed() initializer to receive and handle each error produced by the operations.

A default implementation of ErrorGatherer is the ErrorsSink, which gathers all errors into a single one.


Pick is currently in a alpha stage, a lot of changes going to happen both to api and structure.

More technical details about pick internals, in architecture documentation.


Acknowledgements

Special thanks to Konstantinos Pittas (@kostaspt) for helping me ... pick the name of the library.