A Git-aware conflict resolver for JSON-first structured data.
- 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-resolverautomates conflict handling with configurable strategies.- Non-JSON formats are internally normalized to JSON → resolved → converted back.
 
- ⚡ 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
 
pnpm add git-json-resolveror
npm install git-json-resolveror
yarn add git-json-resolver# 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-backupsAdd 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-resolverHow 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)
 
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",
});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) - 
{
  "$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"]
  }
}- No TypeScript intellisense for plugin strategies
 - Limited validation for custom strategy names
 - No compile-time type checking
 - Recommended: Use 
.jsor.tsconfig for better developer experience 
- 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
 
- Strategies marked with 
!(important) are applied first - Multiple strategies can be specified as fallbacks
 - Custom strategies can be defined via 
customStrategiesconfig 
- JSON (native)
 - JSON5 → via 
json5peer dependency - YAML → via 
yamlpeer dependency - TOML → via 
smol-tomlpeer dependency - XML → via 
fast-xml-parserpeer dependency 
All non-JSON formats are converted to JSON → resolved → converted back to original format.
# 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- 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
 
- Exact paths: 
"package.json","src.config.database.host" - Field matching: 
"[version]"→ matches anyversionfield - Glob patterns: 
"dependencies.*","**.config.**" - Wildcards: 
"*.json","src/**/*.config.js" 
For TypeScript/JavaScript configs, you can use either approach:
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"],
  },
});// Also works in .js/.ts configs
const config = {
  plugins: ["my-plugin"],
  pluginConfig: {
    "my-plugin": { option: "value" },
  },
  rules: {
    version: ["semantic-version", "theirs"],
  },
};{
  "plugins": ["my-plugin"],
  "pluginConfig": {
    "my-plugin": { "option": "value" }
  },
  "rules": {
    "version": ["semantic-version", "theirs"]
  }
}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 (object-based)
const plugin: StrategyPlugin = {
  strategies,
  init: async config => {
    console.log("Plugin initialized with:", config);
  },
};
export default plugin;
// Alternative: Function-based plugin (NEW)
export default async function createPlugin(config?: any): Promise<StrategyPlugin> {
  // Initialize with config if needed
  console.log("Plugin initialized with:", config);
  return {
    strategies,
    init: async initConfig => {
      // Additional initialization if needed
    },
  };
}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"],
  },
};- 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
 
See PLUGIN_GUIDE.md for detailed plugin development documentation.
Contributions welcome 🙌
- Fork, branch, PR — with tests (
vitestrequired) - Docs live in 
libDocs/(tutorials, guides, deep dives) - API reference generated into 
docs/via TypeDoc 
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

