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) (any, error)
- func DiffMaps(old, new map[string]any) (map[string]any, error)
- func DiffStructs(old, new any) (map[string]any, error)
- 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, nil) if both values are nil or if there are no differences. Returns (result, nil) on success, or (nil, error) if an error occurs during diffing.
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. Returns (result, nil) on success, or (nil, error) if an error occurs during diffing.
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. Returns (result, nil) on success, or (nil, error) if an error occurs during diffing.
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.