A Go library to get and set (almost) anything using simple notation
go get github.com/flusflas/dipper
dipper is a simple library that let you use dot notation (or any other delimiter-separated attribute notation) to access values in an object, both for getting and setting, even if they are deeply nested. You can use it with structs, maps and slices.
You can create a Dipper
instance to customize the access options:
library := Library{
Address: "123 Fake Street",
Books: []Book{
{
Title: "Dune",
Year: 1965,
Genres: []string{"Novel", "Science fiction", "Adventure"},
},
{
Title: "Il nome della rosa",
Year: 1980,
Genres: []string{"Novel", "Mystery"},
},
},
}
d := dipper.New(dipper.Options{Separator: "->"})
book := d.Get(library, "Books->1")
if err := dipper.Error(book); err != nil {
return err
}
Or you can use the default functions if you just need dot notation access. This is an example of how to get a nested value from an object:
field := dipper.Get(library, "Books[0].Genres[1]") // "Science fiction"
if err := dipper.Error(field); err != nil {
return err
}
You can also get multiple attributes at once:
fields := dipper.GetMany(library, []string{
"Address",
"Books[1].Year",
"Books[0].Author",
})
// fields => map[string]interface{}{
// "Address": "123 Fake Street",
// "Books.1.Year": 1980,
// "Books.0.Author": dipper.ErrNotFound,
// }
if err := fields.FirstError(); err != nil {
//return err // Returns "dipper: not found"
}
Finally, you can also set values in addressable objects:
// Replace book
err := dipper.Set(&library, "Books.0", Book{Title: "1984", Year: 1949})
There are two special values that can be used in Set()
:
Zero
, to set the attribute to its zero value.Delete
, to delete a map key. If the attribute is not a map value, the value will be zeroed.
To access map values, use the map key directly with the separator notation:
BookMap.Dune
to access the value associated with the key"Dune"
in a map.
To access struct fields, use the separator notation:
Library.Address
to access theAddress
field of theLibrary
struct.
To access slice elements, you can use either the slice notation with square brackets or the separator notation:
Books[0]
orBooks.0
to access the first element of theBooks
slice.
Filter expressions allow you to query slices for elements that match specific conditions. They can be used both to get and set the value of the first matching element. The syntax is:
// Get value with filter
book = dipper.Get(library, "Books[Title='Il nome della rosa']")
if err := dipper.Error(book); err != nil {
return err
}
// Get publication year
year := dipper.Get(library, "Books[Title='Il nome della rosa'].Year")
if err := dipper.Error(year); err != nil {
return err
}
Any primitive types can be used: string (using simple quotes), integer, float and boolean. Some examples:
Books[Title='Dune']
Books[Year=1949]
Books[Available=true]
Filter expressions currently only support the equality operator (=
or ==
).
- This library works with reflection. It has been designed to have a good trade-off between features and performance.
- The only supported type for map keys is
string
.map[interface{}]
is also allowed if the underlying value is astring
. - Errors are not returned explicitly in
Get()
andGetMany()
to support accessing multiple attributes at a time and getting a clear result. Instead, error handling functions are provided. - Struct fields have to be exported, both for getting and setting. Trying to
access an unexported struct field will return
ErrUnexported
. - Using maps with keys containing your Dipper delimiter (or
.
if using the convenience functions) is not supported for obvious reasons. If you're trying to access a map with conflicting characters, use a customDipper
with a different field separator.
- Case sensitivity option.
- Tag option for struct fields.
- Attribute expansion (e.g.
Books.*.Title
). - Custom object parser.
- Option to access unexported fields.