jsondiff

module
v1.1.3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 7, 2025 License: Apache-2.0

README

jsondiff

A powerful Go library and CLI tool for comparing JSON files with colored diff output, line numbers, context lines, and advanced field filtering capabilities.

Features

  • Visual Diff Output: Line-by-line comparison with line numbers from both files
  • Multiple Display Modes: Standard unified diff or side-by-side comparison
  • Smart Highlighting: Color-coded output with inline change highlighting
  • Field Filtering: Include or exclude specific fields from comparison
  • Nested Field Support: Filter nested fields using dot notation
  • Context Control: Configurable context lines around changes
  • JSON Normalization: Optional key sorting before comparison
  • Customizable Colors: Full color customization via configuration files
  • Ignored Field Marking: Excluded fields shown with ~ prefix in blue

Installation

Using Go Install
go install github.com/ravinald/jsondiff/cmd/jsondiff@latest
Build from Source
git clone https://github.com/ravinald/jsondiff.git
cd jsondiff
make build
make install
Using Makefile
# Show all available targets
make help

# Build and test
make all

# Install to GOPATH/bin
make install

# Run tests with coverage
make test-coverage

CLI Usage

Basic Usage
jsondiff file1.json file2.json
Command-Line Options
jsondiff [flags] file1.json file2.json

Flags:
  -c, --context int      Number of context lines to show (default 3)
  -s, --sort            Sort JSON keys before comparing
  -y, --side-by-side    Display side-by-side diff
  --include strings     Fields to include in comparison (comma-separated)
  --exclude strings     Fields to exclude from comparison (comma-separated)
  --config string       Path to configuration file
  -1 string             Marker for lines from first file (default: filename)
  -2 string             Marker for lines from second file (default: filename)
  -b string             Marker for lines in both files (default "B")
  -h, --help           Help for jsondiff
Field Filtering Examples
Include Specific Fields

Only compare name and email fields:

jsondiff --include name,email user1.json user2.json

Output (with file markers):

user1.json {
user1.json -   "email": "alice@example.com",
user1.json -   "name": "Alice Smith",
user2.json +   "email": "alice.j@example.com",
user2.json +   "name": "Alice Johnson",
         B ~  "~age": 30,
         B ~  "~address": {...}
Exclude Specific Fields

Compare everything except timestamp and metadata:

jsondiff --exclude timestamp,metadata data1.json data2.json
Nested Field Filtering

Include only specific nested fields:

# Include only address.city field
jsondiff --include address.city user1.json user2.json

# Include all fields under user.profile
jsondiff --include user.profile config1.json config2.json

# Exclude sensitive nested data
jsondiff --exclude user.password,user.token auth1.json auth2.json
Combined Filters

Use both include and exclude filters:

# Include user fields but exclude user.internal
jsondiff --include user --exclude user.internal data1.json data2.json
Display Options
Custom Source Markers

By default, jsondiff uses the filenames as markers to show which file each line comes from. You can customize these markers:

# Default behavior - uses filenames as markers
jsondiff config.json intent.json
# Output will show:
# config.json - "removed": "value"
#  intent.json + "added": "value" 
#            B ~ "unchanged": "same"

# Use custom markers instead of filenames
jsondiff -1 API -2 Config -b BOTH data1.json data2.json

# Shorter custom markers
jsondiff -1 A -2 B file1.json file2.json

# Use symbols
jsondiff -1 "←" -2 "→" -b "=" left.json right.json

The markers are automatically right-justified and padded to align properly:

       API - "old": "value"
    Config + "new": "value"
      BOTH ~ "unchanged": "same"

In side-by-side view, the markers are used as headers:

# Default - uses filenames as headers
jsondiff -y config.json intent.json

# Custom headers
jsondiff -1 "API Cache" -2 "Site Config" -y api.json site.json
Side-by-Side Comparison
jsondiff -y file1.json file2.json

Output:

file1.json                              | file2.json
----------------------------------------|----------------------------------------
{                                       | {
  "name": "Alice"                       |   "name": "Bob"
  "age": 30                             |   "age": 31
}                                       | }
Custom Context Lines
# Show 5 lines of context
jsondiff -c 5 file1.json file2.json

# Show no context (only changes)
jsondiff -c 0 file1.json file2.json
Sort Keys Before Comparison
jsondiff -s file1.json file2.json
Configuration File

Create a custom color configuration:

{
  "version": 1,
  "colors": {
    "add": {
      "foreground": {
        "line": {
          "hex": "#00ff00",
          "ansi256": 10,
          "ansi": 10
        },
        "inline": {
          "hex": "#008000",
          "ansi256": 2,
          "ansi": 2
        }
      },
      "background": {}
    },
    "remove": {
      "foreground": {
        "line": {
          "hex": "#ff0000",
          "ansi256": 9,
          "ansi": 9
        },
        "inline": {
          "hex": "#800000",
          "ansi256": 1,
          "ansi": 1
        }
      },
      "background": {}
    },
    "ignored": {
      "foreground": {
        "hex": "#0000ff",
        "ansi256": 12,
        "ansi": 12
      },
      "background": {}
    }
  }
}

Use the configuration:

jsondiff --config colors.json file1.json file2.json

Library Usage

Basic Example
package main

import (
    "fmt"
    "log"
    "github.com/ravinald/jsondiff/pkg/jsondiff"
)

func main() {
    json1 := []byte(`{"name": "Alice", "age": 30}`)
    json2 := []byte(`{"name": "Bob", "age": 31}`)
    
    opts := jsondiff.DiffOptions{
        ContextLines: 3,
        SortJSON:     false,
    }
    
    diffs, err := jsondiff.Diff(json1, json2, opts)
    if err != nil {
        log.Fatal(err)
    }
    
    // Enhance with inline changes
    diffs = jsondiff.EnhanceDiffsWithInlineChanges(diffs)
    
    // Format and display
    formatter := jsondiff.NewFormatter(jsondiff.DefaultStyles())
    output := formatter.Format(diffs)
    
    fmt.Print(output)
}
Custom Markers Example
package main

import (
    "fmt"
    "log"
    "github.com/ravinald/jsondiff/pkg/jsondiff"
)

func main() {
    json1 := []byte(`{"name": "Alice", "age": 30}`)
    json2 := []byte(`{"name": "Bob", "age": 31}`)
    
    opts := jsondiff.DiffOptions{
        ContextLines: 3,
    }
    
    diffs, err := jsondiff.Diff(json1, json2, opts)
    if err != nil {
        log.Fatal(err)
    }
    
    // Format with custom markers
    formatter := jsondiff.NewFormatter(jsondiff.DefaultStyles())
    formatter.SetMarkers("API", "Config", "BOTH")
    
    output := formatter.Format(diffs)
    fmt.Print(output)
}
Field Filtering Example
package main

import (
    "fmt"
    "log"
    "os"
    "github.com/ravinald/jsondiff/pkg/jsondiff"
)

func main() {
    // Read JSON files
    json1, _ := os.ReadFile("user1.json")
    json2, _ := os.ReadFile("user2.json")
    
    // Configure diff with field filtering
    opts := jsondiff.DiffOptions{
        ContextLines:  3,
        SortJSON:      true,
        IncludeFields: []string{"name", "email", "address.city"},
        ExcludeFields: []string{"timestamp", "internal"},
    }
    
    // Generate diff
    diffs, err := jsondiff.Diff(json1, json2, opts)
    if err != nil {
        log.Fatal(err)
    }
    
    // Format output
    formatter := jsondiff.NewFormatter(jsondiff.DefaultStyles())
    output := formatter.Format(diffs)
    
    fmt.Print(output)
}
Custom Styling Example
package main

import (
    "encoding/json"
    "fmt"
    "github.com/ravinald/jsondiff/pkg/jsondiff"
)

func main() {
    // Create custom color configuration
    configJSON := `{
        "version": 1,
        "colors": {
            "add": {
                "foreground": {
                    "line": {"hex": "#00ff00", "ansi256": 10, "ansi": 10}
                }
            },
            "remove": {
                "foreground": {
                    "line": {"hex": "#ff0000", "ansi256": 9, "ansi": 9}
                }
            },
            "ignored": {
                "foreground": {"hex": "#0000ff", "ansi256": 12, "ansi": 12}
            }
        }
    }`
    
    var config jsondiff.ColorConfig
    json.Unmarshal([]byte(configJSON), &config)
    
    // Create styles from config
    styles := jsondiff.StylesFromConfig(&config)
    
    // Use custom styles
    formatter := jsondiff.NewFormatter(styles)
    
    // ... rest of diff logic
}
Inline Diff Highlighting

jsondiff automatically detects and highlights inline changes within modified lines. When a JSON field value changes but the key remains the same, the tool will:

  • Match removed and added lines by their JSON key (e.g., both have key "name")
  • Apply bold formatting to the specific changed portion within the value
  • Apply faint formatting to unchanged portions of the value

Note: For inline diff highlighting to be applied, lines must meet a similarity threshold - they must be at least 30% similar in character content and their lengths cannot differ by more than 50%. This prevents unrelated lines from being incorrectly paired for inline comparison.

Example:

- "name": "Alice Smith"     # "Smith" will be bold, rest will be faint
+ "name": "Alice Johnson"   # "Johnson" will be bold, rest will be faint
Side-by-Side Display Example
package main

import (
    "fmt"
    "github.com/ravinald/jsondiff/pkg/jsondiff"
)

func main() {
    json1 := []byte(`{"name": "Alice", "city": "NYC"}`)
    json2 := []byte(`{"name": "Bob", "city": "LA"}`)
    
    opts := jsondiff.DiffOptions{
        ContextLines: 3,
    }
    
    diffs, _ := jsondiff.Diff(json1, json2, opts)
    diffs = jsondiff.EnhanceDiffsWithInlineChanges(diffs)
    
    formatter := jsondiff.NewFormatter(jsondiff.DefaultStyles())
    
    // Use FormatSideBySide for side-by-side output
    output := formatter.FormatSideBySide(diffs, "file1.json", "file2.json")
    
    fmt.Print(output)
}

API Reference

Types
// DiffOptions configures the diff behavior
type DiffOptions struct {
    ContextLines  int      // Number of context lines to show
    SortJSON      bool     // Sort JSON keys before comparison
    IncludeFields []string // Fields to include (empty = all)
    ExcludeFields []string // Fields to exclude
}

// DiffLine represents a single line in the diff
type DiffLine struct {
    Type        DiffType // Equal, Added, or Removed
    LineNum1    int      // Line number in first file
    LineNum2    int      // Line number in second file
    Content     string   // Line content
    InlineStart int      // Start of inline change
    InlineEnd   int      // End of inline change
    IsIgnored   bool     // Field is excluded from comparison
}

// DiffType indicates the type of difference
type DiffType int

const (
    DiffTypeEqual   DiffType = iota
    DiffTypeAdded
    DiffTypeRemoved
)
Functions
// Diff compares two JSON byte arrays
func Diff(json1, json2 []byte, opts DiffOptions) ([]DiffLine, error)

// EnhanceDiffsWithInlineChanges adds inline change markers
func EnhanceDiffsWithInlineChanges(diffs []DiffLine) []DiffLine

// NewFormatter creates a formatter with the given styles
func NewFormatter(styles *Styles) *Formatter

// SetMarkers sets custom markers for file1, file2, and both
func (f *Formatter) SetMarkers(file1Marker, file2Marker, bothMarker string)

// Format generates standard diff output
func (f *Formatter) Format(diffs []DiffLine) string

// FormatSideBySide generates side-by-side diff output
func (f *Formatter) FormatSideBySide(diffs []DiffLine, file1Path, file2Path string) string

// DefaultStyles returns the default color styles
func DefaultStyles() *Styles

// StylesFromConfig creates styles from a configuration
func StylesFromConfig(config *ColorConfig) *Styles

Examples

The examples/ directory contains sample JSON files and configurations:

# Run various examples
make example           # Basic diff
make example-sort      # With sorting
make example-config    # With custom colors
make example-side      # Side-by-side display
make example-include   # Field inclusion
make example-exclude   # Field exclusion
make example-nested    # Nested field filtering

Development

Running Tests
# Run all tests
make test

# Run with coverage
make test-coverage

# Run with race detector
make test-race

# Run benchmarks
make bench
Code Quality
# Format code
make fmt

# Run go vet
make vet

# Run linter (requires golangci-lint)
make lint

# Run all checks
make ci
Building
# Build binary
make build

# Clean build artifacts
make clean

# Install to GOPATH/bin
make install

Dependencies

License

MIT License - see LICENSE file for details

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

Author

Created by @ravinald with modern Go best practices and comprehensive feature set for JSON comparison.

Directories

Path Synopsis
cmd
jsondiff command
pkg

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL