golymorph

package module
v0.2.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 28, 2023 License: MIT Imports: 8 Imported by: 0

README

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

Documentation

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func Decode

func Decode(resolver TypeResolver, source map[string]any, output any) error

Decode the given source map into the given output object using the given TypeResolver and mapstructure. The output object must be a pointer.

func NewPolymorphismBuilder

func NewPolymorphismBuilder() polymorphismBuilderEmpty

NewPolymorphismBuilder creates a new polymorphism builder that is used in a human readable way to create a polymorphism. It only allows a valid combination of rules and type maps.

func NewRuleBuilder

func NewRuleBuilder() ruleBuilderBase

NewRuleBuilder creates a new ruleBuilder. It enables a fluent interface for building a Rule.

func UnmarshalJSON

func UnmarshalJSON(resolver TypeResolver, data []byte, output any) error

UnmarshalJSON unmarshals the given JSON data into the given output object using the given TypeResolver.

Example

ExampleUnmarshalJSON demonstrates how to use the polymorpher to unmarshal a JSON into a struct with a polymorphic field.

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

}
Output:

event: {Timestamp:2023-11-27T22:14:09+00:00 Payload:{Type:alert Message:something is broken!}}
event payload: golymorph_test.AlertPayload {Type:alert Message:something is broken!}

Types

type Polymorphism

type Polymorphism struct {
	// TargetPath is the path to the object to assign the new type to
	TargetPath objectpath.ObjectPath
}

Polymorphism is the base struct for all polymorphism mappers. It contains the target path to assign the new type to.

type Rule

type Rule struct {
	// ValuePath is the path to the value in the source to compare.
	ValuePath objectpath.ObjectPath
	// ComparatorFunction is the function to use to compare the value at ValuePath to.
	ComparatorFunction func(any) bool
	// NewType is the type to assign to the target if the rule matches.
	NewType reflect.Type
}

Rule is a rule for a polymorphism mapper.

func (*Rule) Matches

func (r *Rule) Matches(source any) (error, bool)

Matches returns true if the source matches the rule.

type RulePolymorphism

type RulePolymorphism struct {
	Polymorphism

	// Rules is a list of Rules to apply. The first rule that matches is used to determine the target type.
	Rules []Rule
}

RulePolymorphism is a mapper that assigns a target type based on the given Rules

func (*RulePolymorphism) AssignTargetType

func (p *RulePolymorphism) AssignTargetType(source any, target any) error

type TypeMap

type TypeMap map[any]reflect.Type

TypeMap is a map of values to types.

type TypeMapPolymorphism

type TypeMapPolymorphism struct {
	Polymorphism

	// DiscriminatorPath is the path to the discriminator value
	DiscriminatorPath objectpath.ObjectPath

	// TypeMap is a map of discriminator values to types
	TypeMap TypeMap
}

TypeMapPolymorphism is a mapper that assigns a target type based on a discriminator value and a type map

func (*TypeMapPolymorphism) AssignTargetType

func (p *TypeMapPolymorphism) AssignTargetType(source any, target any) error
Example

ExampleTypeMapPolymorphism_AssignTargetType demonstrates how to use the polymorpher to assign the type of a polymorphic field in an existing struct instance.

package main

import (
	"encoding/json"
	"fmt"
	"github.com/SoulKa/golymorph"
	"github.com/mitchellh/mapstructure"
	"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!" } }`

	type Event struct {
		Timestamp string
		Payload   any
	}

	type AlertPayload struct {
		Type    string
		Message string
	}

	type PingPayload struct {
		Type string
		Ip   string
	}

	typeMap := golymorph.TypeMap{
		"alert": reflect.TypeOf(AlertPayload{}),
		"ping":  reflect.TypeOf(PingPayload{}),
	}

	// parse the JSON into a map
	var jsonMap map[string]any
	if err := json.Unmarshal([]byte(alertEventJson), &jsonMap); err != nil {
		panic(fmt.Sprintf("error unmarshalling JSON: %s", err))
	}

	// create a polymorpher that assigns the type of the payload based on the type field
	err, polymorpher := 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 := polymorpher.AssignTargetType(&jsonMap, &event); err != nil {
		panic(fmt.Sprintf("error assigning target type: %s", err))
	}

	// use mapstructure to unmarshal the payload into the event
	if err := mapstructure.Decode(jsonMap, &event); err != nil {
		panic(fmt.Sprintf("error decoding JSON map: %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))

}
Output:

event: {Timestamp:2023-11-27T22:14:09+00:00 Payload:{Type:alert Message:something is broken!}}
event payload: golymorph_test.AlertPayload {Type:alert Message:something is broken!}

type TypeResolver

type TypeResolver interface {
	// AssignTargetType assigns the determined type to target based on the polymorphism rules. The matching rule with the
	// highest priority is used. If no rule matches, the target type is not changed. The source and target must be pointers.
	// If no matching type can be determined, an error.UnresolvedTypeError is returned.
	AssignTargetType(source any, target any) error
}

TypeResolver is an interface that can resolve the type of a target based on the values of a source.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL