⚠️ Notice
- Please do not install any of these versions: v1.1.1 v1.1.0 v1.0.0 v0.1.2 v0.1.1 as these were removed from the repo - (but are still available at pkg.go.dev).
- When installing please explicitly install the actual latest version of dstruct which is currently v0.5.0-beta.
A golang package that allows one to create, modify and generate structs dynamically.
Features:
- Building structs at runtime
- Extending existing struct at runtime
- Merging multiple structs at runtime
- Adding new fields into struct
- Removing existing fields from struct
- Modifying field values in structs
- Reading field values in structs
- Generating struct values
Limitations:
- You cannot extend structs with unexported embedded fields.
- If a struct pointer cannot be fully dereferenced then the struct's subfields won't be added to the dynamic struct. This is done mainly to avoid self-referencing structs as these will create infinite node trees.
- Dynamic structs with struct fields of type
any (interface {})
cannot be created. If you try extend or merge structs which have struct fields of typeany
their value must be set to a concrete type.
- Install
- How it works?
- Using the builder
- Using the modifier
- Using the struct generator
- Extending a struct
- Merging structs
go get github.com/MartinSimango/dstruct@v0.5.0-beta
Dstruct uses a tree to represent dynamic structs which allows these structs to easily to be manipulated. Nodes and their children represent struct fields and their subfields respectively. Once the tree structure of the struct is created the tree is converted into a dynamic struct using the reflect package.
Dstruct has 3 main interfaces that are implemented in order to allow these features:
dstruct.Builder
is responsible for adding and removing fields from a struct.
type Builder interface {
AddField(name string, value interface{}, tag string) Builder
AddEmbeddedField(value interface{}, tag string) Builder
Build() DynamicStructModifier
GetField(name string) Builder
GetFieldCopy(field string) Builder
NewBuilderFromField(field string) Builder
RemoveField(name string) Builder
}
dstruct.DynamicStructModifier
is responsible for reading and editing fields with the struct as well as storing the actual struct.
type DynamicStructModifier interface {
Instance() any
New() any
Get(field string) (any, error)
Set(field string, value any) error
GetFields() map[string]field
}
dstruct.GeneratedStruct
is responsible for generating struct values and is an extension of the DynamicStructModifier. A generated struct values are randomly generation based on Generation functions.
type GeneratedStruct interface {
DynamicStructModifier
Generate()
GetFieldGenerationConfig(field string) *generator.GenerationConfig
SetFieldGenerationConfig(field string, generationConfig *generator.GenerationConfig) error
}
type Person struct {
Name string
Age int
}
func main() {
structBuilder := dstruct.NewBuilder().
AddField("Person", Person{Name: "Martin", Age: 25}, `json:"person"`).
AddField("Job", "Software Developer", "").
RemoveField("Person.Age")
fmt.Printf("Struct: %+v\n", structBuilder.Build().Instance())
}
Output
$ Struct: {Person:{Name:Martin} Job:Software Developer}
type Person struct {
Name string
Age int
}
func main() {
structBuilder := dstruct.NewBuilder().
AddField("Person", Person{Name: "Martin", Age: 25}, `json:"person"`).
AddField("Job", "Software Developer", "")
structModifier := structBuilder.Build()
structModifier.Set("Person.Name", "Martin Simango")
structModifier.Set("Job", "Software Engineer")
name, _ := structModifier.Get("Person.Name")
fmt.Printf("New name: %s\n", name.(string))
fmt.Printf("Struct: %+v\n", structModifier.Instance())
}
Output
$ New name: Martin Simango
$ Struct: {Person:{Name:Martin Simango Age:25} Job:Software Engineer}
type Person struct {
Name string
Age int
}
func main() {
structBuilder := dstruct.NewBuilder().
AddField("Person", Person{Name: "Martin", Age: 25}, `json:"person"`).
AddField("Job", "Software Developer", "")
strct := structBuilder.Build().Instance()
generatedStruct := dstruct.NewGeneratedStruct(strct)
// change the age to be between 50 and 60
generatedStruct.GetFieldGenerationConfig("Person.Age").SetIntMin(50).SetIntMax(60)
generatedStruct.Generate()
fmt.Printf("Struct with age between 50 and 60: %+v\n", generatedStruct.Instance())
// change the age to be between 10 and 20
generatedStruct.GetFieldGenerationConfig("Person.Age").SetIntMin(10).SetIntMax(20)
generatedStruct.Generate()
fmt.Printf("Struct with age between 10 and 20: %+v\n", generatedStruct.Instance())
}
Output:
$ Struct with age between 50 and 60: {Person:{Name:string Age:59} Job:string}
$ Struct with age between 10 and 20: {Person:{Name:string Age:16} Job:string}
type Address struct {
Street string
}
type Person struct {
Name string
Age int
Address Address
}
func main() {
strct := dstruct.ExtendStruct(Person{
Name: "Martin",
Age: 25,
Address: Address{
Street: "Alice Street",
},
})
strct.GetField("Address").AddField("StreetNumber", 1, "")
fmt.Printf("Extended struct: %+v\n", strct.Build().Instance())
}
Output:
$ Extended struct: {Name:Martin Age:25 Address:{Street:Alice Street StreetNumber:1}}
type PersonDetails struct {
Age int
Height float64
}
type PersonName struct {
Name string
Surname string
}
func main() {
strct, _ := dstruct.MergeStructs(PersonDetails{Age: 0, Height: 190.4}, PersonName{Name: "Martin", Surname: "Simango"})
fmt.Printf("Merged structs: %+v\n", strct)
}
Output:
$ Merged structs: {Age:0 Height:190.4 Name:Martin Surname:Simango}