Documentation
¶
Index ¶
- func Apply(target any, patch map[string]any) error
- func ApplyToMap(original map[string]any, patch map[string]any) map[string]any
- func ApplyToStruct(target any, patch map[string]any) error
- func Diff(old, new any) map[string]any
- func DiffMaps(old, new map[string]any) map[string]any
- func DiffStructs(old, new any) map[string]any
- func ToMap(v any) map[string]any
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func Apply ¶
Apply applies a patch to a target, which can be either a struct or a map. For structs: the target must be a pointer to a struct, and the struct is modified in-place. For maps: the target must be a pointer to a map[string]any, and the map is replaced with the patched result.
The patch should be generated by Diff, DiffMaps, or follow the same format: - Keys with values: set/update the key/field to that value - Keys with nil values: delete the key or zero the field if possible - Nested maps/structs: recursively apply patches
Returns an error if the patch cannot be applied due to type incompatibilities or structural constraints.
Example ¶
Example function showing how to use the unified Apply function
// Example 1: Applying patch to a struct
type User struct {
Name string `json:"name"`
Age int `json:"age"`
Email string `json:"email"`
}
user := &User{
Name: "John",
Age: 30,
Email: "john@example.com",
}
patch := map[string]any{
"name": "Jane",
"age": 31,
}
Apply(user, patch)
fmt.Printf("Struct result: %+v\n", *user)
// Example 2: Applying patch to a map
data := &map[string]any{
"user": "John",
"settings": map[string]any{
"theme": "dark",
},
}
mapPatch := map[string]any{
"user": "Jane",
"settings": map[string]any{
"theme": "light",
"notifications": true,
},
}
Apply(data, mapPatch)
fmt.Printf("Map result: %+v\n", *data)
Output: Struct result: {Name:Jane Age:31 Email:john@example.com} Map result: map[settings:map[notifications:true theme:light] user:Jane]
func ApplyToMap ¶
ApplyToMap applies a diff/patch to a starting map to produce a new map. The patch should be generated by Diff or DiffMaps, or follow the same format:
- Keys with values: set/update the key to that value - Keys with nil values: delete the key from the result - Nested maps: recursively apply patches to nested maps - Struct values: if original value is a struct and patch is a map, apply patch to struct using ApplyToStruct
The original map is not modified; a new map is returned.
Example ¶
package main
import (
"fmt"
"github.com/tsarna/go-structdiff"
)
func main() {
original := map[string]any{"x": 1, "y": 2}
patch := map[string]any{"y": 3, "z": 4, "x": nil} // x deleted
result := structdiff.ApplyToMap(original, patch)
fmt.Printf("Result: %+v\n", result)
}
Output: Result: map[y:3 z:4]
func ApplyToStruct ¶
ApplyToStruct applies a patch map to a struct, modifying the struct in-place. The patch should be generated by Diff or follow the same format.
Rules: - nil values in patch: delete/zero the field if possible, error if field is not nillable - Type mismatches: attempt conversion for compatible types, error otherwise - JSON tags: honored for field mapping - any fields: accept any value type - Numeric conversions: attempted (like JSON deserialization)
Returns an error if the patch cannot be applied due to type incompatibilities or structural constraints.
Example ¶
package main
import (
"fmt"
"github.com/tsarna/go-structdiff"
)
func main() {
type User struct {
Name string `json:"name"`
Age int `json:"age"`
}
var user User
patch := map[string]any{
"name": "Alice",
"age": "25", // string converted to int
}
err := structdiff.ApplyToStruct(&user, patch)
if err != nil {
panic(err)
}
fmt.Printf("User: %+v\n", user)
}
Output: User: {Name:Alice Age:25}
func Diff ¶
Diff computes a diff/patch between two values that can be any combination of structs and maps. This is a unified function that automatically handles: - struct vs struct: uses DiffStructs - map vs map: uses DiffMaps - struct vs map: converts struct to map using ToMap, then uses DiffMaps - map vs struct: converts struct to map using ToMap, then uses DiffMaps
The resulting map contains only the changes needed to transform old into new: - Keys with same values: omitted - Keys with different values: included with new value - Keys only in new: included with new value - Keys only in old: included with nil value (indicates deletion) - Nested structures: recursively diffed
Returns nil if both values are nil or if there are no differences.
func DiffMaps ¶
DiffMaps computes a diff/patch from old map to new map. The resulting map contains only the changes needed to transform old into new:
- Keys with same values in both maps: omitted - Keys with different values: included with new value - Keys only in new: included with new value - Keys only in old: included with nil value (indicates deletion) - Nested maps: recursively diffed using DiffMaps - Struct values: compared using the unified Diff function for any combination of structs and maps
Applying all changes in the result to the old map would produce the new map.
func DiffStructs ¶
DiffStructs compares two structs and returns a patch map containing only the differences.
The function performs direct struct diffing without creating intermediate maps, providing significant performance improvements for nested structures: - 75% less memory usage - 35% faster execution - 40% fewer allocations
Rules: - Keys with same values: omitted from result - Keys with different values: included with new value - Keys only in new: included with new value - Keys only in old: included with nil value (indicates deletion) - Nested structs and maps: compared using the unified Diff function for any combination of structs and maps
The resulting patch can be applied using ApplyToStruct or ApplyToMap.
Example ¶
package main
import (
"fmt"
"github.com/tsarna/go-structdiff"
)
func main() {
type User struct {
Name string `json:"name"`
Age int `json:"age"`
Email string `json:"email"`
}
oldUser := User{Name: "John", Age: 30, Email: "john@old.com"}
newUser := User{Name: "John", Age: 31, Email: "john@new.com"}
diff := structdiff.DiffStructs(oldUser, newUser)
fmt.Printf("Changes: %+v\n", diff)
}
Output: Changes: map[age:31 email:john@new.com]
Example (Nested) ¶
package main
import (
"fmt"
"github.com/tsarna/go-structdiff"
)
func main() {
type Address struct {
Street string `json:"street"`
City string `json:"city"`
}
type Employee struct {
Name string `json:"name"`
Address Address `json:"address"`
}
old := Employee{
Name: "Alice",
Address: Address{Street: "123 Main St", City: "NYC"},
}
new := Employee{
Name: "Alice",
Address: Address{Street: "456 Oak Ave", City: "NYC"},
}
diff := structdiff.DiffStructs(old, new)
fmt.Printf("Changes: %+v\n", diff)
}
Output: Changes: map[address:map[street:456 Oak Ave]]
Example (RoundTrip) ¶
package main
import (
"fmt"
"time"
"github.com/tsarna/go-structdiff"
)
func main() {
type Person struct {
Name string `json:"name"`
Born time.Time `json:"born"`
}
alice := Person{
Name: "Alice",
Born: time.Date(1990, 1, 1, 0, 0, 0, 0, time.UTC),
}
bob := Person{
Name: "Bob",
Born: time.Date(1985, 5, 15, 0, 0, 0, 0, time.UTC),
}
// Compute diff
diff := structdiff.DiffStructs(alice, bob)
// Apply diff to transform alice into bob
result := alice
err := structdiff.ApplyToStruct(&result, diff)
if err != nil {
panic(err)
}
// Verify the transformation worked
fmt.Printf("Original: %s, born %s\n", alice.Name, alice.Born.Format("2006-01-02"))
fmt.Printf("Result: %s, born %s\n", result.Name, result.Born.Format("2006-01-02"))
fmt.Printf("Matches target: %t\n", result.Name == bob.Name && result.Born.Equal(bob.Born))
}
Output: Original: Alice, born 1990-01-01 Result: Bob, born 1985-05-15 Matches target: true
func ToMap ¶
ToMap converts a struct to a map[string]any representation. It follows JSON struct tag conventions and handles nested structures, slices, maps, and special types like time.Time.
Rules: - Only exported fields are included - JSON tags are honored for field naming - Fields tagged with `json:"-"` are excluded - Nil pointers are omitted - Empty values (0, "", false, []) are included
Example ¶
package main
import (
"fmt"
"github.com/tsarna/go-structdiff"
)
func main() {
type Config struct {
Host string `json:"host"`
Port int `json:"port"`
Password *string `json:"password,omitempty"`
Debug bool `json:"debug"`
}
config := Config{Host: "localhost", Port: 8080, Debug: false}
m := structdiff.ToMap(config)
fmt.Printf("Map: %+v\n", m)
}
Output: Map: map[debug:false host:localhost port:8080]
Types ¶
This section is empty.