« Back to Index

Dynamically Create Struct

View original Gist on GitHub

Tags: #go

1. Basic Example.go

// READ THIS FOR MANY MORE EXAMPLES!
// https://medium.com/capital-one-tech/learning-to-use-go-reflection-822a0aed74b7

package main

import (
	"fmt"
	"reflect"
)

type Foo struct {
	A int `tag1:"First Tag" tag2:"Second Tag"`
	B string
}

func main() {
	greeting := "hello"
	f := Foo{A: 10, B: "Salutations"}

	gVal := reflect.ValueOf(greeting)
	// not a pointer so all we can do is read it
	fmt.Println("ORIGINAL GREETING:", gVal.Interface())

	gpVal := reflect.ValueOf(&greeting)
	// it’s a pointer, so we can change it, and it changes the underlying variable
	gpVal.Elem().SetString("goodbye")
	fmt.Println("NEW GREETING:", greeting)

	fType := reflect.TypeOf(f)
	fVal := reflect.New(fType)
	fVal.Elem().Field(0).SetInt(20)
	fVal.Elem().Field(1).SetString("Greetings")
	f2 := fVal.Elem().Interface().(Foo)
	
	
	fmt.Printf("ORIGINAL struct: %+v, %d, %s\n", f, f.A, f.B)
	fmt.Printf("NEW struct: %+v, %d, %s\n", f2, f2.A, f2.B)
}

2. Golang Dynamically Create Struct.go

// https://play.golang.org/p/iIf7OF_lPBG
//
// all this good work is done by https://github.com/Ompluscator/dynamic-struct
// which offers much more functionality than I had stripped out from that library
//
// I was working on something where I wanted only standard library implementation code
// so I decided to just strip out the code I needed
//
package main

import (
	"encoding/json"
	"fmt"
	"reflect"
)

// DynamicStruct contains defined dynamic struct.
// This definition can't be changed anymore, once is built.
// It provides a method for creating new instances of same defintion.
type DynamicStruct interface {
	// New provides new instance of defined dynamic struct.
	//
	// value := dStruct.New()
	//
	New() interface{}
}

// Builder holds all fields' definitions for desired structs.
type Builder interface {
	// AddField creates new struct's field.
	// It expects field's name, type and string.
	// Type is provided as an instance of some golang type.
	// Tag is provided as classical golang field tag.
	//
	// builder.AddField("SomeFloatField", 0.0, `json:"boolean" validate:"gte=10"`)
	//
	AddField(name string, typ interface{}, tag string) Builder

	// Build returns definition for dynamic struct.
	// Definition can be used to create new instances.
	//
	// dStruct := builder.Build()
	//
	Build() DynamicStruct
}

type dynamicStructImpl struct {
	definition reflect.Type
}

type fieldConfigImpl struct {
	typ interface{}
	tag string
}

type builderImpl struct {
	fields map[string]*fieldConfigImpl
}

func (b *builderImpl) AddField(name string, typ interface{}, tag string) Builder {
	b.fields[name] = &fieldConfigImpl{
		typ: typ,
		tag: tag,
	}

	return b
}

func (b *builderImpl) Build() DynamicStruct {
	var structFields []reflect.StructField

	for name, field := range b.fields {
		structFields = append(structFields, reflect.StructField{
			Name: name,
			Type: reflect.TypeOf(field.typ),
			Tag:  reflect.StructTag(field.tag),
		})
	}

	return &dynamicStructImpl{
		definition: reflect.StructOf(structFields),
	}
}

func (ds *dynamicStructImpl) New() interface{} {
	return reflect.New(ds.definition).Interface()
}

// NewStruct returns new clean instance of Builder interface
// for defining fresh dynamic struct.
//
// builder := NewStruct()
//
func NewStruct() Builder {
	return &builderImpl{
		fields: map[string]*fieldConfigImpl{},
	}
}

func main() {
	instance := NewStruct().
		AddField("Integer", 0, `json:"int"`).
		AddField("Text", "", `json:"someText"`).
		AddField("Float", 0.0, `json:"double"`).
		AddField("Boolean", false, "").
		AddField("Slice", []int{}, "").
		AddField("Anonymous", "", `json:"-"`).
		Build().
		New()

	fmt.Printf("empty struct instance:\n%+v\n\n", instance)

	data := []byte(`
{
    "int": 123,
    "someText": "example",
    "double": 123.45,
    "Boolean": true,
    "Slice": [1, 2, 3],
    "Anonymous": "avoid to read"
}
`)

	json.Unmarshal(data, &instance)

	fmt.Printf("struct data:\n%+v\n", instance)
}

3. Golang Dynamically Create Struct - Separate Call.go

// https://play.golang.org/p/0cfY0LZ0NdL
//
// all this good work is done by https://github.com/Ompluscator/dynamic-struct
// which offers much more functionality than I had stripped out from that library
//
// I was working on something where I wanted only standard library implementation code
// so I decided to just strip out the code I needed
//
package main

import (
	"encoding/json"
	"fmt"
	"reflect"
)

// DynamicStruct contains defined dynamic struct.
// This definition can't be changed anymore, once is built.
// It provides a method for creating new instances of same defintion.
type DynamicStruct interface {
	// New provides new instance of defined dynamic struct.
	//
	// value := dStruct.New()
	//
	New() interface{}
}

// Builder holds all fields' definitions for desired structs.
type Builder interface {
	// AddField creates new struct's field.
	// It expects field's name, type and string.
	// Type is provided as an instance of some golang type.
	// Tag is provided as classical golang field tag.
	//
	// builder.AddField("SomeFloatField", 0.0, `json:"boolean" validate:"gte=10"`)
	//
	AddField(name string, typ interface{}, tag string) Builder

	// Build returns definition for dynamic struct.
	// Definition can be used to create new instances.
	//
	// dStruct := builder.Build()
	//
	Build() DynamicStruct
}

type dynamicStructImpl struct {
	definition reflect.Type
}

type fieldConfigImpl struct {
	typ interface{}
	tag string
}

type builderImpl struct {
	fields map[string]*fieldConfigImpl
}

func (b *builderImpl) AddField(name string, typ interface{}, tag string) Builder {
	b.fields[name] = &fieldConfigImpl{
		typ: typ,
		tag: tag,
	}

	return b
}

func (b *builderImpl) Build() DynamicStruct {
	var structFields []reflect.StructField

	for name, field := range b.fields {
		structFields = append(structFields, reflect.StructField{
			Name: name,
			Type: reflect.TypeOf(field.typ),
			Tag:  reflect.StructTag(field.tag),
		})
	}

	return &dynamicStructImpl{
		definition: reflect.StructOf(structFields),
	}
}

func (ds *dynamicStructImpl) New() interface{} {
	return reflect.New(ds.definition).Interface()
}

// NewStruct returns new clean instance of Builder interface
// for defining fresh dynamic struct.
//
// builder := NewStruct()
//
func NewStruct() Builder {
	return &builderImpl{
		fields: map[string]*fieldConfigImpl{},
	}
}

func main() {
	instance := NewStruct()

	// now you can loop a data structure to generate these calls...
	instance.AddField("Integer", 0, `json:"int"`)
	instance.AddField("Text", "", `json:"someText"`)
	instance.AddField("Float", 0.0, `json:"double"`)
	instance.AddField("Boolean", false, "")
	instance.AddField("Slice", []int{}, "")
	instance.AddField("Anonymous", "", `json:"-"`)

	value := instance.Build().New()

	data := []byte(`
{
    "int": 123,
    "someText": "example",
    "double": 123.45,
    "Boolean": true,
    "Slice": [1, 2, 3],
    "Anonymous": "avoid to read"
}
`)

	json.Unmarshal(data, &value)

	fmt.Printf("struct data:\n%+v\n", value)
}