The golymorph module enables resolving polymorphic typing at runtime. It's usually used in
conjunction with JSON parsing and the mapstructure
module. In fact, this module takes the use case
of mapstructure
a step further by allowing the user to define a custom type resolver.
Standard go get
:
go get github.com/SoulKa/golymorph
The docs are hosted on Godoc.
Use this module to resolve polymorphic types at runtime. An example would be a struct that contains a payload field which can be of different struct types not known at compile time:
// the parent type that contains the polymorphic payload
type Event struct {
Timestamp string
Payload any // <-- AlertPayload or PingPayload?
}
// the polymorphic child types
type AlertPayload struct {
Type string
Message string
}
type PingPayload struct {
Type string
Ip string
}
If, for example, you have a JSON that you decode into the Event
struct, it is cumbersome to parse the JSON into a map, look into the type
field of the payload
and after that select and parse the map into the correct type at the payload
field of the event
struct.
golymorph does exactly this:
- Look at the value of a defined field anywhere in the given
map
or JSON - Find the correct type using the given pairs of
value ==> reflect.Type
- Assign the correct type at the given position anywhere in the "parent" struct
- Fully decode the given JSON or
map
, now with a concrete struct type aspayload
package main
import (
"fmt"
"github.com/SoulKa/golymorph"
"reflect"
)
func main() {
// get a JSON that contains a payload with a type field that determines the type of the payload
alertEventJson := `{ "timestamp": "2023-11-27T22:14:09+00:00", "payload": { "type": "alert", "message": "something is broken!" } }`
// the parent type that contains the polymorphic payload
type Event struct {
Timestamp string
Payload any
}
// the polymorphic child types
type AlertPayload struct {
Type string
Message string
}
type PingPayload struct {
Type string
Ip string
}
// define a mapping from the type value to the type of the payload
typeMap := golymorph.TypeMap{
"alert": reflect.TypeOf(AlertPayload{}),
"ping": reflect.TypeOf(PingPayload{}),
}
// create a TypeResolver that assigns the type of the payload based on the type field
err, resolver := golymorph.NewPolymorphismBuilder().
DefineTypeAt("payload").
UsingTypeMap(typeMap).
WithDiscriminatorAt("type").
Build()
if err != nil {
panic(fmt.Sprintf("error building polymorpher: %s", err))
}
// create a new event
var event Event
if err := golymorph.UnmarshalJSON(resolver, []byte(alertEventJson), &event); err != nil {
panic(fmt.Sprintf("error unmarshalling event: %s", err))
}
// continue to work with the event
fmt.Printf("event: %+v\n", event)
fmt.Printf("event payload: %T %+v\n", event.Payload, event.Payload.(AlertPayload))
}
I am very happy for contributions or feature suggestions. As long as this module is not stable released (version 1.0.0) I am also open for refactorings.
How to:
- Fork it
- Create your feature branch (git checkout -b my-new-feature)
- Commit your changes (git commit -am 'Added some feature')
- Push to the branch (git push origin my-new-feature)
- Create new Pull Request