Skip to content

The golymorph module enables resolving polymorphic typing at runtime. It's usually used during JSON parsing.

License

Notifications You must be signed in to change notification settings

SoulKa/golymorph

Repository files navigation

golymorph Pipeline Tests Status Godoc

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.

Installation

Standard go get:

go get github.com/SoulKa/golymorph

Docs

The docs are hosted on Godoc.

Use Case

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:

  1. Look at the value of a defined field anywhere in the given map or JSON
  2. Find the correct type using the given pairs of value ==> reflect.Type
  3. Assign the correct type at the given position anywhere in the "parent" struct
  4. Fully decode the given JSON or map, now with a concrete struct type as payload

Example

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))
}

Contributing

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

About

The golymorph module enables resolving polymorphic typing at runtime. It's usually used during JSON parsing.

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages