brynbellomy / go-structomancer

Golang struct reflection package

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

structomancer

Golang struct reflection package. Primarily aimed at package authors who want to add struct tag functionality and/or struct serialization and deserialization to their packages.

fast

structomancer is pretty fast. A lot of the reflection calls it makes are only performed once per type, and are fetched from a sync.RWMutex-protected cache on subsequent lookups.

what you can do

import "github.com/brynbellomy/go-structomancer"

type Blah struct {
    Name      string      `api:"name"                 db:"name"`
    Token     int         `api:"token"                db:"-"`
    SessionID string      `api:"-"                    db:"-"`
    Inner     InnerStruct `api:"inner, @tag=weezy"    db:"inner_struct"`
}

type InnerStruct struct {
    Quux string `weezy:"quux"`
}

func main() {
    z := structomancer.New(&Blah{}, "api") // provide a specimen struct and the struct tag

    //
    // deserialize a map to a struct
    //
    s, err := z.MapToStruct(map[string]interface{}{
        "name": "xyzzy",
        "token": 123,
        "inner": map[string]interface{}{
            "quux": "asdf",
        },
    })

    //
    // serialize a struct to a map
    //
    m, err := z.StructToMap(&Blah{
        Name: "xyzzy",
        Token: 123,
        Inner: InnerStruct{Quux: "asdf"},
    })

    //
    // generate empty, addressable instances of a type
    //
    x := z.MakeEmpty()                           // returns a &Blah{}
    structomancer.New(Blah{}, "api").MakeEmpty() // also returns a &Blah

    //
    // validate fields based on struct tags
    //
    z.IsKnownField("inner")        // returns true
    z.IsKnownField("sessionID")    // returns false

    //
    // get field values
    //
    blah := &Blah{Name: "foobar"}
    v, err := z.GetFieldValue(blah, "name")  // returns "foobar", nil

    //
    // set field values
    //
    err := z.SetFieldValue(blah, "name", "qaax")
}

custom decoding/encoding

You might find that you need to set up custom serializer/deserializer functions for individual fields (for example, fields with interface types, which cannot be automatically deserialized by structomancer).

Doing so is easy:

type IFooer interface{ Foo() }

type Blah struct {
    Fooers []IFooer `api:"fooers"`
}

func main() {
    z := structomancer.New(&Blah{}, "api")

    z.SetFieldEncoder("fooers", func(x interface{}) (interface{}, error) {
        fooers := x.([]IFooer)
        // ... convert from []IFooer to []interface{} ...
        return fs, nil
    })

    z.SetFieldDecoder("fooers", func(x interface{}) (interface{}, error) {
        fs := x.([]interface{})
        // ... convert from []interface{} to []IFooer ...
        return fooers, nil
    })

    // now, your (de)serializers will run when you call the following methods:
    z.StructToMap(...)
    z.MapToStruct(...)
}

reflect package compatibility

If you're working with lots of reflect.Values already, you probably want to avoid creating even more of them (reflection is apparently expensive because of allocations, although I forget where I read that).

To avoid unnecessary boxing/unboxing of your values, you can access all of structomancer's methods via an alternate reflect.Value-compatible interface:

.MakeEmptyV() reflect.Value
.GetFieldValueV(v reflect.Value, fnickname string) (reflect.Value, error)
.SetFieldValueV(sv reflect.Value, fname string, value reflect.Value) error
.StructToMapV(aStruct reflect.Value) (map[string]interface{}, error)
.MapToStructV(fields map[string]interface{}) (reflect.Value, error)

Should be substantially faster, but I haven't profiled it yet.

authors/contributors

About

Golang struct reflection package

License:ISC License


Languages

Language:Go 100.0%