FMap is a simple library for working with structs as a storage of fields. Switch case and reflect based. This is unsafe library, be careful while use.
Install via go get. Note that Go 1.18 or newer is required.
go get github.com/insei/fmap/v3@latestfmap.GetFrom(obj any) and fmap.Get[T any]() creates new fmap.Storage. This storage manage access to fmap.Field by field path like in struct.
type Storage interface {
// Find returns the Field object and a boolean value indicating if the field with the given path was found.
// The path parameter represents the path of the field in the struct.
// If the field is found, the method returns the Field object and true.
// If the field is not found, the method returns a nil Field object and false.
Find(path string) (Field, bool)
// MustFind returns the Field object for the field with the given path in the struct.
// If the Field is not found, MustFind panics.
MustFind(path string) Field
// GetAllPaths returns a slice containing all paths of fields in the struct.
GetAllPaths() []string
}fmap.Field is an advanced abstraction level for reflect.StructField with some advanced methods:
type Field interface {
// GetName returns the name of the field.
GetName() string
// GetPkgPath returns the package import path of the Field struct type.
GetPkgPath() string
// GetType returns the reflect.Type of the field.
GetType() reflect.Type
// GetTag returns the reflect.StructTag of the field. The reflect.StructTag is a string.
GetTag() reflect.StructTag
// GetOffset returns the offset of the field in memory relative to the start of the struct.
GetOffset() uintptr
// GetIndex returns the index of the field within its containing struct as a slice of integers.
GetIndex() []int
// GetAnonymous returns a boolean value indicating whether the field is anonymous.
GetAnonymous() bool
// IsExported checks if a field is exported by checking its PkgPath property.
IsExported() bool
// Extended Methods
// Get returns the value of the storage in the provided object.
// It takes a parameter `obj` of type `interface{}`, representing the object.
// It returns the value of the storage as an `interface{}`.
Get(obj any) any
// GetPtr returns the pointer to the field's value in the provided object.
// It takes a parameter `obj` of type `any`, representing the pointer to object.
// It returns the pointer to the field's value as an `any`.
GetPtr(obj any) any
// Set updates the value of the storage in the provided object with the provided value.
// It takes two parameters:
// - obj: interface{}, representing the object pointer containing the field.
// - val: interface{}, representing the new value for the field.
Set(obj any, val any)
// GetStructPath returns the struct path of the field.
// It returns the struct path as a string.
GetStructPath() string
// GetTagPath returns the path of the field's tag value with the given tag name.
// It takes two parameters:
// - tag: string, representing the tag name.
// - ignoreParentTagMissing: bool, representing whether to ignore the missing parent tags or not.
// It returns the tag value path as a string.
// Embedded structs without the name in the tag are transparent: their fields are on the level of the embedding struct,
// like in encoding/json, so they are not missing parents.
GetTagPath(tag string, ignoreParentTagMissing bool) string
// GetParent returns the parent field of the current field, if not exist return nil.
GetParent() Field
// GetDereferencedType returns the dereferenced type of the field.
// It returns the dereferenced type as a reflect.Type.
GetDereferencedType() reflect.Type
// GetDereferenced - uses reflect package for casting field value from obj to direct field value, i.e. dereferenced value.
GetDereferenced(obj any) (any, bool)
}package main
import (
"time"
"fmt"
"github.com/insei/fmap/v3"
)
type City struct {
Name string `json:"name"`
}
type People struct {
Name string
Age uint8
Birthday time.Time
City City `json:"city"`
}
func main() {
p := &People{}
fields := fmap.Get[People]() // or fmap.GetFrom(p)
fields.MustFind("Name").Set(p, "Test")
fields.MustFind("Age").Set(p, uint8(5))
fields.MustFind("Birthday").Set(p, time.Now())
fields.MustFind("City.Name").Set(p, "DefaultCity")
jsonPath := fields.MustFind("City.Name").GetTagPath("json", false) // city.name
cityField := fields.MustFind("City.Name").GetParent()
cityStruct := cityField.Get(p)
fmt.Print(*p, jsonPath, cityStruct)
}More examples in field_test.go, like slice fields, nested structs, pointers etc.
Fields of the struct stored by pointer are available by the path, like fields of the struct stored by value:
type TLS struct {
Cert string
}
type Server struct {
Host string
TLS *TLS
}
type Config struct {
Backup *Server
}
func main() {
conf := &Config{}
fields, _ := fmap.GetFrom(conf) // paths: Backup, Backup.Host, Backup.TLS, Backup.TLS.Cert
cert := fields.MustFind("Backup.TLS.Cert")
cert.Get(conf) // nil, conf.Backup is nil and it is not changed
cert.Set(conf, "/etc/cert.pem") // conf.Backup and conf.Backup.TLS are created
cert.Get(conf) // "/etc/cert.pem"
}Getreturns nil andGetPtrreturns typed nil pointer if the struct of the field is not created, the object is not changed.Setcreates all structs in the path of the field.GetOffsetreturns the offset from the start of the struct stored by pointer, not from the root struct.- Recursive types are not traversed again:
type Node struct { Next *Node }has only theNextfield. - Only single pointers are traversed,
**Structis a field without nested fields.
fmap.GetFrom(obj any) map[string]Field
BenchmarkGetFrom
BenchmarkGetFrom-16 93002347 12.62 ns/op 0 B/op 0 allocs/op
Field.Get(obj any) any
BenchmarkFieldGet
BenchmarkFieldGet-16 88818492 14.05 ns/op 0 B/op 0 allocs/op
Raw access to field from struct :)
BenchmarkRawFieldGet
BenchmarkRawFieldGet-16 1000000000 0.2350 ns/op 0 B/op 0 allocs/op