A Go library for calculating differences between JSON documents and reconstructing original JSON from diffs. This implementation treats array changes as complete units rather than element-by-element differences.
Installation
go get github.com/raystack/salt/jsondiff
Features
- JSON Comparison: Generate detailed diffs between two JSON documents
- Reconstruction: Reverse diffs to reconstruct the original JSON
- Array Handling: Arrays are compared as complete units for cleaner diffs
- Type Safety: Preserves data types during comparison and reconstruction
- Zero Dependencies: Uses only Go standard library
Data Structures
DiffEntry
Represents a single difference between two JSON documents.
type DiffEntry struct {
FieldName string `json:"field_name"`
ChangeType string `json:"change_type"`
FromValue *string `json:"from_value,omitempty"`
ToValue *string `json:"to_value,omitempty"`
FullPath string `json:"full_path"`
ValueType string `json:"value_type"`
}
Name of the changed field (last segment of the path)
Type of change: "added", "removed", or "modified"
Original value (for removed/modified changes)
New value (for added/modified changes)
Full JSON path to the field (e.g., /fruits/apple)
Data type: "string", "number", "boolean", "array", "object", or "null"
JSONDiffer
Custom JSON differ implementation that treats arrays as complete units.
NewJSONDiffer
Creates a new JSONDiffer instance.
func NewJSONDiffer() *JSONDiffer
A new JSONDiffer instance
Compare
Compares two JSON strings and returns the differences.
func (jd *JSONDiffer) Compare(json1, json2 string) ([]DiffEntry, error)
The current JSON string to compare against
Array of diff entries describing all changes
Error if JSON parsing fails
Example:
import "github.com/raystack/salt/jsondiff"
originalJSON := `{
"name": "John",
"fruits": {
"apple": 1,
"arr": [1,2,3]
}
}`
currentJSON := `{
"name": "John Doe",
"age": 30,
"fruits": {
"apple": 1,
"arr": [1,3,4]
}
}`
differ := jsondiff.NewJSONDiffer()
diffs, err := differ.Compare(originalJSON, currentJSON)
if err != nil {
panic(err)
}
fmt.Printf("Generated %d diff entries\n", len(diffs))
WI2LDiffer
Alternative JSON differ implementation using the wI2L/jsondiff library.
NewWI2LDiffer
Creates a new WI2LDiffer instance.
func NewWI2LDiffer() *WI2LDiffer
A new WI2LDiffer instance
Compare
Compares two JSON strings using the wI2L/jsondiff library.
func (w *WI2LDiffer) Compare(json1, json2 string) ([]DiffEntry, error)
The current JSON string to compare against
Array of diff entries describing all changes
Error if JSON parsing fails
Example:
import "github.com/raystack/salt/jsondiff"
differ := jsondiff.NewWI2LDiffer()
diffs, err := differ.Compare(originalJSON, currentJSON)
if err != nil {
panic(err)
}
JSONReconstructor
Reconstructs original JSON from current JSON and diff entries.
NewJSONReconstructor
Creates a new JSONReconstructor instance.
func NewJSONReconstructor() *JSONReconstructor
A new JSONReconstructor instance
ReverseDiff
Reconstructs the original JSON from the current JSON and diff entries.
func (jr *JSONReconstructor) ReverseDiff(currentJSON string, diffs []DiffEntry) (string, error)
Array of diff entries to reverse
The reconstructed original JSON string
Error if reconstruction fails
Example:
import (
"encoding/json"
"reflect"
"github.com/raystack/salt/jsondiff"
)
reconstructor := jsondiff.NewJSONReconstructor()
reconstructed, err := reconstructor.ReverseDiff(currentJSON, diffs)
if err != nil {
panic(err)
}
// Verify reconstruction accuracy
var originalObj, reconstructedObj interface{}
json.Unmarshal([]byte(originalJSON), &originalObj)
json.Unmarshal([]byte(reconstructed), &reconstructedObj)
if reflect.DeepEqual(originalObj, reconstructedObj) {
fmt.Println("✓ Reconstruction successful")
}
Array Handling
Unlike element-by-element array diffs, this library treats entire arrays as single units. When an array changes, the entire array is marked as “modified” with the complete old and new values.
Example:
// Original: [1,2,3]
// Current: [1,3,4]
// Result: One "modified" entry with full array values
This approach provides cleaner, more predictable diffs for complex nested structures.
Error Handling
The library returns detailed error messages for:
- Invalid JSON syntax
- Malformed diff entries
- Path resolution issues during reconstruction
Complete Example
package main
import (
"encoding/json"
"fmt"
"reflect"
"github.com/raystack/salt/jsondiff"
)
func main() {
originalJSON := `{
"name": "John",
"matrix4D": [[[[1,2,3],[4,5,6],[4,5,6]]]],
"old_param": "value",
"fruits": {
"apple": 1,
"pears": 2,
"arr": [1,2,3]
}
}`
currentJSON := `{
"name": "John Doe",
"matrix4D": [[[[1,2,3],[4,5,6,7]]]],
"age": 30,
"fruits": {
"apple": 1,
"beans": 2,
"arr": [1,3,4]
},
"new_object": {
"a":1,
"b":[1,2]
}
}`
// Generate diff
differ := jsondiff.NewJSONDiffer()
diffs, err := differ.Compare(originalJSON, currentJSON)
if err != nil {
panic(err)
}
fmt.Printf("Generated %d diff entries:\n", len(diffs))
diffJSON, _ := json.MarshalIndent(diffs, "", " ")
fmt.Println(string(diffJSON))
// Reconstruct original
reconstructor := jsondiff.NewJSONReconstructor()
reconstructed, err := reconstructor.ReverseDiff(currentJSON, diffs)
if err != nil {
panic(err)
}
// Verify
var originalObj, reconstructedObj interface{}
json.Unmarshal([]byte(originalJSON), &originalObj)
json.Unmarshal([]byte(reconstructed), &reconstructedObj)
if reflect.DeepEqual(originalObj, reconstructedObj) {
fmt.Println("✓ Reconstruction successful")
} else {
fmt.Println("✗ Reconstruction failed")
}
}