Skip to content

A rules-based JSON conflict resolver that parses Git conflict markers, reconstructs ours/theirs, and merges with deterministic strategies — beyond line-based merges.

License

Notifications You must be signed in to change notification settings

react18-tools/git-json-resolver

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Git Json Resolver

test Maintainability codecov Version Downloads npm bundle size

A Git-aware conflict resolver for JSON-first structured data.

Why?

  • Git merge conflicts in structured files (JSON, YAML, XML, TOML) are painful.
  • Manual resolution is error-prone, time-consuming, and breaks CI/CD pipelines.
  • git-json-resolver automates conflict handling with configurable strategies.
  • Non-JSON formats are internally normalized to JSON → resolved → converted back.

Features

  • Primary focus on JSON (first-class support)
  • 🔄 YAML, XML, TOML, JSON5 supported via conversion
  • 🧩 Rule-based strategies with path/pattern matching
  • 📁 Multiple file parallel processing with include/exclude patterns
  • 🔌 Pluggable matcher abstraction (picomatch, micromatch, or custom)
  • 🛠️ CLI and programmatic API support
  • 📝 Conflict sidecar files for unresolved conflicts
  • 🔄 Backup and restore functionality
  • 📊 Configurable logging (memory or file-based)
  • 🔀 Git merge driver support for seamless Git integration
  • 🔧 Plugin system for custom strategies with JSON config support

Installation

pnpm add git-json-resolver

or

npm install git-json-resolver

or

yarn add git-json-resolver

Quick Start

CLI Usage

# Initialize config file
npx git-json-resolver --init

# Run with default config
npx git-json-resolver

# Run with options
npx git-json-resolver --include "**/*.json" --debug --sidecar

# Restore from backups
npx git-json-resolver --restore .merge-backups

Git Integration

Add a custom merge driver to your Git config:

git config merge.json-resolver.name "Custom JSON merge driver"
git config merge.json-resolver.driver "npx git-json-resolver %A %O %B"

Update .gitattributes to use it for JSON files:

*.json merge=json-resolver
*.yaml merge=json-resolver
*.yml merge=json-resolver
*.toml merge=json-resolver
*.xml merge=json-resolver

How it works:

  • Git automatically calls the merge driver during conflicts
  • Uses same configuration and strategies as CLI mode
  • Supports 3-way merge (ours, base, theirs)
  • Returns proper exit codes (0 = success, 1 = conflicts)

Configuration

Programmatic API

import { resolveConflicts } from "git-json-resolver";

await resolveConflicts({
  defaultStrategy: ["merge", "ours"],
  rules: {
    "dependencies.*": ["ours"],
    version: ["theirs!"], // ! marks as important
    "scripts.build": ["skip"],
  },
  include: ["**/*.json", "**/*.yaml"],
  exclude: ["**/node_modules/**"],
  matcher: "picomatch",
  debug: true,
  writeConflictSidecar: true,
  backupDir: ".merge-backups",
});

Config File

JavaScript Config (git-json-resolver.config.js)

module.exports = {
  defaultStrategy: ["merge", "ours"],
  rules: {
    // Exact path matching
    "package.json": {
      version: ["theirs!"],
      dependencies: ["ours"],
    },
    // Pattern matching
    "*.config.json": {
      "*": ["merge"],
    },
  },
  // Alternative: byStrategy format
  byStrategy: {
    ours: ["dependencies.*", "devDependencies.*"],
    "theirs!": ["version", "name"],
  },
  include: ["**/*.json", "**/*.yaml", "**/*.yml"],
  exclude: ["**/node_modules/**", "**/dist/**"],
  matcher: "picomatch",
  debug: false,
  writeConflictSidecar: false,
  loggerConfig: {
    mode: "memory", // or "stream"
    logDir: "logs",
    levels: {
      stdout: ["warn", "error"],
      file: ["info", "warn", "error"],
    },
  },
};

JSON Config (git-json-resolver.config.json) - ⚠️ Experimental

{
  "$schema": "https://cdn.jsdelivr.net/npm/git-json-resolver@latest/schema/config.schema.json",
  "defaultStrategy": ["merge", "ours"],
  "plugins": ["my-plugin"],
  "pluginConfig": {
    "my-plugin": {
      "option": "value"
    }
  },
  "rules": {
    "package.json": {
      "version": ["semantic-version", "theirs"],
      "dependencies": ["ours"]
    }
  },
  "byStrategy": {
    "ours": ["dependencies.*", "devDependencies.*"],
    "theirs!": ["version", "name"]
  }
}

⚠️ JSON Config Limitations:

  • No TypeScript intellisense for plugin strategies
  • Limited validation for custom strategy names
  • No compile-time type checking
  • Recommended: Use .js or .ts config for better developer experience

Supported Strategies

  • merge → deep merge objects/arrays where possible
  • ours → take current branch value
  • theirs → take incoming branch value
  • base → revert to common ancestor
  • skip → leave unresolved (creates conflict entry)
  • drop → remove the field entirely
  • non-empty → prefer non-empty value (ours > theirs > base)
  • update → update with theirs if field exists in ours
  • concat → concatenate arrays from both sides
  • unique → merge arrays and remove duplicates
  • custom → user-defined resolver functions

Strategy Priority

  • Strategies marked with ! (important) are applied first
  • Multiple strategies can be specified as fallbacks
  • Custom strategies can be defined via customStrategies config

Supported Formats

  • JSON (native)
  • JSON5 → via json5 peer dependency
  • YAML → via yaml peer dependency
  • TOML → via smol-toml peer dependency
  • XML → via fast-xml-parser peer dependency

All non-JSON formats are converted to JSON → resolved → converted back to original format.

CLI Options

# File patterns
--include "**/*.json,**/*.yaml"  # Comma-separated patterns
--exclude "**/node_modules/**"   # Exclusion patterns

# Matcher selection
--matcher picomatch              # picomatch, micromatch, or custom

# Debug and logging
--debug                          # Enable verbose logging
--sidecar                        # Write conflict sidecar files

# Utilities
--init                           # Create starter config file
--restore .merge-backups         # Restore from backup directory

Architecture

  • Modular design: Separate concerns (parsing, merging, serialization)
  • Reusable utilities: Common merge logic extracted for maintainability
  • Optimized bundle: Constants over enums for better minification
  • Comprehensive testing: Full test coverage with vitest
  • Type-safe: Full TypeScript support with proper type inference

Advanced Features

Pattern Matching

  • Exact paths: "package.json", "src.config.database.host"
  • Field matching: "[version]" → matches any version field
  • Glob patterns: "dependencies.*", "**.config.**"
  • Wildcards: "*.json", "src/**/*.config.js"

Plugin System

For TypeScript/JavaScript configs, you can use either approach:

1. Direct Import (Recommended)

import { strategies } from "my-plugin";
// or import { semanticVersion, timestampLatest } from "my-plugin";
import { resolveConflicts } from "git-json-resolver";

await resolveConflicts({
  customStrategies: {
    ...strategies,
    // or "semantic-version": semanticVersion,
  },
  rules: {
    version: ["semantic-version", "theirs"],
  },
});

2. Dynamic Loading

// Also works in .js/.ts configs
const config = {
  plugins: ["my-plugin"],
  pluginConfig: {
    "my-plugin": { option: "value" },
  },
  rules: {
    version: ["semantic-version", "theirs"],
  },
};

3. JSON Config (Dynamic Loading Only)

{
  "plugins": ["my-plugin"],
  "pluginConfig": {
    "my-plugin": { "option": "value" }
  },
  "rules": {
    "version": ["semantic-version", "theirs"]
  }
}

⚠️ If plugin doesn't provide global types:

import type { Config } from "git-json-resolver";

const config: Config<AllStrategies | "semantic-version" | "timestamp-latest"> = {
  // ... your config
};

Creating a Plugin

import { StrategyPlugin, StrategyStatus, StrategyFn } from "git-json-resolver";

// Augment types for TypeScript support
declare module "git-json-resolver" {
  interface PluginStrategies {
    "semantic-version": string;
    "timestamp-latest": string;
  }
}

// Individual strategy functions (can be imported directly)
export const semanticVersion: StrategyFn = ({ ours, theirs }) => {
  if (isNewerVersion(theirs, ours)) {
    return { status: StrategyStatus.OK, value: theirs };
  }
  return { status: StrategyStatus.CONTINUE };
};

export const timestampLatest: StrategyFn = ({ ours, theirs }) => {
  const oursTime = new Date(ours as string).getTime();
  const theirsTime = new Date(theirs as string).getTime();
  return {
    status: StrategyStatus.OK,
    value: oursTime > theirsTime ? ours : theirs,
  };
};

// Export strategies object for direct import
export const strategies = {
  "semantic-version": semanticVersion,
  "timestamp-latest": timestampLatest,
};

// Plugin interface for dynamic loading
const plugin: StrategyPlugin = {
  strategies,
  init: async config => {
    console.log("Plugin initialized with:", config);
  },
};

export default plugin;

Custom Strategies (Inline)

import { StrategyStatus } from "git-json-resolver";

const config = {
  customStrategies: {
    "semantic-version": ({ ours, theirs }) => {
      // Custom logic for semantic version resolution
      if (isNewerVersion(theirs, ours)) {
        return { status: StrategyStatus.OK, value: theirs };
      }
      return { status: StrategyStatus.CONTINUE };
    },
  },
  rules: {
    version: ["semantic-version", "theirs"],
  },
};

Logging & Debugging

  • Memory mode: Fast, in-memory logging
  • Stream mode: File-based logging for large operations
  • Per-file logs: Separate log files for each processed file
  • Debug mode: Detailed conflict information and strategy traces

Plugin Development

See PLUGIN_GUIDE.md for detailed plugin development documentation.

Contributing

Contributions welcome 🙌

  • Fork, branch, PR — with tests (vitest required)
  • Docs live in libDocs/ (tutorials, guides, deep dives)
  • API reference generated into docs/ via TypeDoc

License

This library is licensed under the MPL-2.0 open-source license.

Please enroll in our courses or sponsor our work.


with 💖 by Mayank Kumar Chaudhari

About

A rules-based JSON conflict resolver that parses Git conflict markers, reconstructs ours/theirs, and merges with deterministic strategies — beyond line-based merges.

Resources

License

Code of conduct

Contributing

Security policy

Stars

Watchers

Forks

Sponsor this project

  •  
  •  

Packages

No packages published