JSON Merge
The mergeJson function performs a deep merge of two JavaScript objects using the defu library. It’s used whenever xtarterize needs to modify JSON configuration files like tsconfig.json, biome.json, or .vscode/settings.json.
import { mergeJson } from '@xtarterize/patchers'
const existing = { compilerOptions: { strict: true, target: "ES2022" } }const incoming = { compilerOptions: { incremental: true } }
const merged = mergeJson(existing, incoming)// { compilerOptions: { strict: true, target: "ES2022", incremental: true } }Parameters
Section titled “Parameters”| Parameter | Type | Description |
|---|---|---|
existing | object | The current configuration (takes precedence) |
incoming | object | The new configuration to merge in (fills gaps) |
Return Value
Section titled “Return Value”Returns the merged object. Existing keys always take precedence over incoming keys.
Deep Merge Behavior
Section titled “Deep Merge Behavior”Nested objects are merged recursively. Existing keys take precedence, missing keys are filled from incoming:
const existing = { compilerOptions: { strict: true, // https://www.typescriptlang.org/tsconfig/#strict target: "ES2022" // https://www.typescriptlang.org/tsconfig/#target }}
const incoming = { compilerOptions: { incremental: true, // https://www.typescriptlang.org/tsconfig/#incremental tsBuildInfoFile: ".tsbuildinfo" // https://www.typescriptlang.org/tsconfig/#tsBuildInfoFile }}
const merged = mergeJson(existing, incoming)// {// compilerOptions: {// strict: true, // preserved from existing// target: "ES2022", // preserved from existing// incremental: true, // added from incoming// tsBuildInfoFile: ".tsbuildinfo" // added from incoming// }// }Object vs Array Merge
Section titled “Object vs Array Merge”| Type | Behavior | Example |
|---|---|---|
| Objects | Deep merge, existing keys win | { a: { b: 1 } } + { a: { c: 2 } } → { a: { b: 1, c: 2 } } |
| Arrays | Replace entirely | { rules: ["a"] } + { rules: ["b"] } → { rules: ["b"] } |
Merge Flow
Section titled “Merge Flow”flowchart TD
E[Existing config] --> M{mergeJson}
I[Incoming config] --> M
M --> P{Key exists?}
P -->|Yes| K[Keep existing value]
P -->|No| A[Add incoming value]
P -->|Nested object| R[Recurse merge]
K --> O[Merged result]
A --> O
R --> O
style E fill:#6366f1,color:#fff
style I fill:#f59e0b,color:#fff
style O fill:#22c55e,color:#fff
Array Handling
Section titled “Array Handling”const existing = { plugins: ['pluginA'] }const incoming = { plugins: ['pluginB'] }
const merged = mergeJson(existing, incoming)// { plugins: ['pluginB'] } — incoming replaces existing arrayFor cases where arrays need to be combined (like VS Code extension recommendations), use a custom merge function instead of mergeJson.
patchJson: Surgical Text Editing
Section titled “patchJson: Surgical Text Editing”While mergeJson operates on objects, patchJson edits JSON text directly using jsonc-parser. It preserves:
- Existing comments (
// inlineand/* block */) - Key ordering
- Whitespace and indentation style
- Trailing commas (in JSONC)
import { patchJson } from '@xtarterize/patchers'
const existing = `{ // Keep this comment "strict": true, "target": "ES2022"}`
const incoming = { compilerOptions: { incremental: true } }const result = patchJson(existing, incoming)// {// // Keep this comment// "strict": true,// "target": "ES2022",// "incremental": true// }When to Use Each
Section titled “When to Use Each”| Function | Use When | Preservation |
|---|---|---|
mergeJson | Object-level logic, deep merging | None (returns new object) |
patchJson | Writing changes back to a file | Comments, formatting, key order |
createJsonMergeTask and createMultiFileJsonMergeTask use both: mergeJson computes the target state, then patchJson applies it surgically to the original text. In @xtarterize/tasks, this shared behavior is centralized in factory-config.ts.
Real-World Examples
Section titled “Real-World Examples”// Existingconst existing = { compilerOptions: { strict: true, target: "ES2022" } }// Incoming: add incremental buildsconst incoming = { compilerOptions: { incremental: true } }// Result: both preserved// Existingconst existing = { linter: { enabled: true } }// Incoming: add formatterconst incoming = { formatter: { enabled: true } }// Result: both sections present// Existingconst existing = { "editor.formatOnSave": true } // https://code.visualstudio.com/docs/editor/codebasics#_format-on-save// Incoming: add Biome formatterconst incoming = { "[typescript]": { "editor.defaultFormatter": "biomejs.biome" } } // https://code.visualstudio.com/docs/getstarted/settings#_language-specific-editor-settings// Result: both settings preservedReferences
Section titled “References”- defu GitHub Repository — Deep merge utility
- TypeScript tsconfig Reference — Compiler configuration options
- Biome Configuration Reference —
biome.jsonschema - VS Code Settings — Editor configuration documentation
- VS Code Extension Recommendations — Workspace recommended extensions