DynamicStruct is a Go package that enables runtime creation and manipulation of struct types. It provides a clean API to dynamically create, modify, and access struct fields, which is particularly useful when dealing with dynamic data structures or when the structure of your data is not known at compile time.
- Create struct types dynamically at runtime
- Add and remove fields with type safety
- Thread-safe operations with mutex protection
- Access field values with type checking
- Works seamlessly with Go's standard library, including JSON encoding/decoding
go get github.com/gosmos-space/dynamicstruct
// Create a new builder
builder := dynamicstruct.New()
// Add fields with their types
_ = builder.AddField("Name", "") // String field
_ = builder.AddField("Age", int(0)) // Integer field
_ = builder.AddField("Active", false) // Boolean field
// Build the struct instance
instance, err := builder.Build()
if err != nil {
panic(err)
}
// Remove a field
err := builder.RemoveField("Age")
if err != nil {
// Handle error
// Possible errors: ErrInstanceAlreadyBuilt
}
// Get field values
var name string
err := builder.GetFieldValue("Name", &name)
if err != nil {
// Handle error
// Possible errors:
// - ErrInstanceNotBuilt
// - ErrValueMustBePointer
// - ErrValueCannotBeNil
// - ErrFieldNotFound
// - ErrIncompatibleTypes
}
// Reset the builder to reuse it
builder.Reset()
// Now you can add new fields and build again
_ = builder.AddField("ID", int(0))
DynamicStruct works well with Go's standard JSON encoding/decoding:
// Create a struct dynamically
builder := dynamicstruct.New()
_ = builder.AddField("ID", int(0))
_ = builder.AddField("Name", "")
_ = builder.AddField("Email", "")
// Build it
instance, _ := builder.Build()
// Convert to a pointer for JSON operation
instancePtr := reflect.New(reflect.TypeOf(instance)).Interface()
instanceElem := reflect.ValueOf(instancePtr).Elem()
// Set some values
idField := instanceElem.FieldByName("ID")
if idField.IsValid() && idField.CanSet() {
idField.SetInt(42)
}
nameField := instanceElem.FieldByName("Name")
if nameField.IsValid() && nameField.CanSet() {
nameField.SetString("Alice")
}
// Marshal to JSON
jsonData, _ := json.Marshal(instancePtr)
fmt.Println(string(jsonData))
// Output: {"ID":42,"Name":"Alice","Email":""}
// Unmarshal JSON back into a dynamic struct
newData := []byte(`{"ID":123,"Name":"Bob","Email":"[email protected]"}`)
json.Unmarshal(newData, instancePtr)
The package provides specific error types:
ErrFieldAlreadyExists
: When trying to add a field with a name that already existsErrInstanceAlreadyBuilt
: When trying to modify an already built instanceErrInstanceNotBuilt
: When trying to access an instance that hasn't been builtErrValueMustBePointer
: When passing a non-pointer to GetFieldValueErrValueCannotBeNil
: When passing a nil pointer to GetFieldValueErrFieldNotFound
: When the requested field doesn't existErrIncompatibleTypes
: When the field type doesn't match the pointer type
Use errors.Is()
to check for these specific errors:
err := builder.AddField("Name", "")
if errors.Is(err, dynamicstruct.ErrFieldAlreadyExists) {
// Handle duplicate field
}
All operations in DynamicStruct are protected by a mutex, making it safe to use from multiple goroutines.
- Currently, there's no direct support for JSON tags or other struct tags
- Field visibility is limited (all fields are exported)
-
Type Safety: Using dynamic structs bypasses Go's compile-time type checking, which can lead to runtime errors instead of compile-time errors.
-
Performance Impact:
- Dynamic struct creation has overhead compared to using static structs
- Each reflection operation is significantly slower than direct field access
- Heavy use in performance-critical paths can cause noticeable slowdowns
-
Code Readability: Code using dynamic structs is typically harder to understand, maintain, and debug than code using static types.
-
IDE Support: You lose IDE features like autocomplete and refactoring support when working with dynamic fields.
-
Error Prone: It's easy to make mistakes when accessing fields by string names, which won't be caught until runtime.
Use DynamicStruct when:
- Working with truly dynamic data where the structure isn't known until runtime
- Implementing generic data processing pipelines
- Adapting to external systems with changing schemas
- Building tools for data conversion or mapping
- Creating mock objects for testing
Do not use when:
- The struct structure is known at compile time
- Performance is critical
- Type safety is a primary concern
- Code clarity and maintenance are priorities
- Simple maps or existing structs would suffice
Consider these alternatives before reaching for dynamic structs:
- Use
map[string]interface{}
for simple key-value storage - Define interfaces for polymorphic behavior
- Use code generation for known schema variations
- Define a union type that covers all possible fields