# keyValueUpsertMode 實作分析
# keyValueUpsertMode Implementation Analysis

---

## 概述 / Overview

本文分析 `keyValueUpsertMode` 選項的三種不同實作方式，比較其優缺點、適用情境，並與 lodash 函式進行結果對比。

This document analyzes three different implementation approaches for the `keyValueUpsertMode` option, comparing their pros and cons, applicable scenarios, and results against lodash functions.

---

## 三種實作方式 / Three Implementation Approaches

### Attempt 1: `keyValueUpsertMode: true`

```typescript
/**
 * 合併兩個物件
 * merge(userConfig, defaultConfig, { keyValueUpsertMode: true })
 * 
 * 行為 / Behavior:
 * - 當 userConfig[key] !== undefined 時，保留 userConfig 的值
 * - 當 userConfig[key] === undefined 時，使用 defaultConfig 的值
 */
const result = merge(userConfig, defaultConfig, {
    keyValueUpsertMode: true,
});
```

### Attempt 2: `keyValueUpsertMode` 自訂函式

```typescript
/**
 * 使用自訂函式控制合併行為
 * merge(userConfig, defaultConfig, { keyValueUpsertMode: (value, options, tmpRuntime) => ... })
 */
const result = merge(userConfig, defaultConfig, {
    keyValueUpsertMode: (value, options, tmpRuntimeTarget) => {
        const userConfigValue = tmpRuntimeTarget?.target?.[tmpRuntimeTarget.key as string];
        return userConfigValue !== undefined;
    },
});
```

---

## 與 lodash 函式對比 / Comparison with Lodash Functions

### 1. _.defaults (淺層預設值)

| 實作方式 | 與 lodash 結果比較 | 說明 |
|---------|-------------------|------|
| Attempt 1: `keyValueUpsertMode: true` | ⚠️ 不同 | 會遞迴合併巢狀物件，添加 source 中的新鍵 |
| Attempt 2: 自訂函式 | ⚠️ 不同 | 仍會遞迴合併巢狀物件 |

**lodash _.defaults 行為**:
- 只填充第一層缺失的鍵
- 不會遞迴處理巢狀物件
- 完全不覆蓋 target 中已存在的值

**keyValueUpsertMode: true 行為**:
- 會遞迴合併巢狀物件
- 當 target[key] !== undefined 時保留 target 值
- 當 target[key] === undefined 時使用 source 值
- 會將 source 中的新鍵（包括巢狀物件中的新鍵）添加到結果中

**差異說明 / Difference Explanation**:
- `_.defaults({ a: { b: 1 } }, { a: { c: 2 } })` → `{ a: { b: 1 } }`（只填充第一層，不處理巢狀）
- `merge({ a: { b: 1 } }, { a: { c: 2 } }, { keyValueUpsertMode: true })` → `{ a: { b: 1, c: 2 } }`（遞迴合併）

**為什麼不同 / Why Different**:
- lodash _.defaults 是**淺層**操作，只處理第一層
- keyValueUpsertMode 是**深度**操作，會遞迴合併

**好處 / Benefits**:
- keyValueUpsertMode 可以合併深層巢狀物件
- 適合多層級配置的合併需求

**適用情境 / Applicable Scenarios**:
- 需要合併深層巢狀物件的 defaults
- 配置物件有多層級結構

---

### 2. _.defaultsDeep (深度預設值)

| 實作方式 | 與 lodash 結果比較 | 說明 |
|---------|-------------------|------|
| Attempt 1: `keyValueUpsertMode: true` | ⚠️ 不同 | 兩者都會遞迴合併，但處理方式不同 |
| Attempt 2: 自訂函式 | ⚠️ 不同 | 仍會遞迴合併巢狀物件 |

**lodash _.defaultsDeep 行為**:
- 會遞迴填充缺失的鍵
- 不會覆蓋已存在的值
- 語義：填充缺失（fill missing）

**keyValueUpsertMode: true 行為**:
- 會遞迴合併物件
- 當 target[key] !== undefined 時保留 target 值
- 當 target[key] === undefined 時使用 source 值
- 不覆蓋已存在的值（除非是 undefined）
- 語義：有則保留（preserve if exists）

**差異說明 / Difference Explanation**:
- `_.defaultsDeep({ a: { b: 1 } }, { a: { b: 2, c: 3 } })` → `{ a: { b: 1, c: 3 } }`（只填充缺失，不覆蓋現有）
- `merge({ a: { b: 1 } }, { a: { b: 2, c: 2 } }, { keyValueUpsertMode: true })` → `{ a: { b: 1, c: 2 } }`
- 兩者結果相同！差異在於**語義**：
  - _.defaultsDeep：確保有值（fill missing）
  - keyValueUpsertMode：保留現有（preserve existing）

**為什麼不同 / Why Different**:
- 語義上的差異：fill missing vs preserve existing
- 雖然結果可能相同，但概念不同

**好處 / Benefits**:
- 可實現「保留優先值」的概念
- 更適合配置繼承場景

**適用情境 / Applicable Scenarios**:
- 配置繼承（基礎配置 + 環境配置）
- 使用者偏好設定覆蓋預設值

---

### 3. _.merge (深度合併)

| 實作方式 | 與 lodash 結果比較 | 說明 |
|---------|-------------------|------|
| `keyValueUpsertMode: false` | ✅ 相似 | 使用預設深度合併行為 |
| `keyValueUpsertMode: true` | ⚠️ 不同 | 會保留 target 的 undefined 值 |

**lodash _.merge 行為**:
- 會遞迴合併巢狀物件
- 會覆蓋所有值（不保留 target 的值）
- 陣列處理：替換（replace）

**keyValueUpsertMode: false 行為**:
- 會遞迴合併巢狀物件
- 會覆蓋所有值
- 陣列處理：串接（concat）- 預設行為

**keyValueUpsertMode: true 行為**:
- 會遞迴合併巢狀物件
- 當 target[key] === undefined 時使用 source 值
- 不會覆蓋已存在的值

**差異說明 / Difference Explanation**:
- `_.merge({ a: { b: 1 } }, { a: { b: 2 } })` → `{ a: { b: 2 } }`（覆蓋）
- `merge({ a: { b: 1 } }, { a: { b: 2 } }, { keyValueUpsertMode: true })` → `{ a: { b: 1 } }`（保留）

**為什麼不同 / Why Different**:
- _.merge：覆蓋所有值（overwrite all）
- keyValueUpsertMode: true：保留現有值（preserve existing）

**好處 / Benefits**:
- 可實現「優先使用現有值」的概念
- 適合「設定優先順序」的場景

**適用情境 / Applicable Scenarios**:
- 保留使用者設定，只填充未設定的選項
- 配置繼承（後面的設定不覆蓋前面的）

---

## 各實作方式優缺點分析 / Pros and Cons Analysis

### Attempt 1: `keyValueUpsertMode: true`

**優點 / Pros**:
- 語法簡單，易於使用
- 可正確處理 undefined 值
- 會遞迴合併巢狀物件，添加 source 中的新鍵
- 保留 falsy 值（0, false, ''）

**缺點 / Cons**:
- 與 _.defaults 的主要差異在於：lodash 只處理第一層，keyValueUpsertMode 會遞迴合併
- keyValueUpsertMode 會將 source 中的新鍵添加到結果中
- lodash _.defaults 不會遞迴處理巢狀物件

**適用情境 / Applicable Scenarios**:
- 需要遞迴合併的深度 defaults
- 配置合併：使用者設定覆蓋預設設定
- 需要保留 falsy 值的場景

---

### Attempt 2: 自訂函式

**優點 / Pros**:
- 可精確控制何時保留目標值
- 可根據鍵名、值、或其他條件自訂邏輯
- 彈性最大

**缺點 / Cons**:
- 程式碼較複雜
- 仍無法完全模擬 lodash 行為（因為 deepmerge 會遞迴合併）
- 需要了解內部 API

**適用情境 / Applicable Scenarios**:
- 需要根據鍵名決定是否覆蓋
- 需要根據多個條件綜合判斷
- 需要實現複雜的合併邏輯

---

### Attempt 3: 實際應用場景

**優點 / Pros**:
- 可實現「保留優先值」的功能
- 參數順序直觀
- 適合實際業務需求

**缺點 / Cons**:
- 需要改變思維方式（將優先值放在 target）
- 與 lodash 行為方向相反

**適用情境 / Applicable Scenarios**:
- 配置繼承（基礎配置 + 環境配置）
- 表單預設值填充
- 使用者偏好設定

---

## 最佳實作選擇 / Best Implementation Selection

### 最接近 lodash 實作概念 / Closest to Lodash Implementation

**_.defaultsDeep**

| 標準 | 選擇 |
|-----|------|
| 最接近 lodash 概念 | ⚠️ 部分接近 |
| 原因 | 兩者都會遞迴處理，語義相似 |

**說明**:
- lodash _.defaultsDeep：填充缺失的鍵（fill missing）
- keyValueUpsertMode：在第一個參數為 undefined 時使用第二個參數的值

兩者的語義對比：
- _.defaultsDeep：確保有值（ensure value exists）
- keyValueUpsertMode：有值則保留（preserve if exists）

---

### 各場景的最佳實作 / Best Implementation for Each Scenario

| 場景 | 最佳實作 | 說明 |
|------|---------|------|
| 配置合併（保留使用者設定） | merge(userConfig, defaultConfig, { keyValueUpsertMode: true }) | |
| 表單資料填充 | merge(userInput, defaultValues, { keyValueUpsertMode: true }) | |
| 環境變數覆蓋 | merge(envConfig, baseConfig, { keyValueUpsertMode: true }) | |
| 深度合併（類似 _.merge） | merge(userConfig, defaultConfig) 或 merge(userConfig, defaultConfig, { keyValueUpsertMode: false }) | |
| 自訂合併邏輯 | merge(userConfig, defaultConfig, { keyValueUpsertMode: (value, options, tmpRuntime) => ... }) | |

---

## 詳細比較表 / Detailed Comparison Table

### 與 _.defaults 比較

| 特性 | _.defaults | keyValueUpsertMode: true |
|------|------------|-------------------------|
| 填充第一層缺失的鍵 | ✅ 是 | ✅ 是 |
| 覆蓋已存在的值 | ❌ 否 | ❌ 否（除非是 undefined）|
| 遞迴處理巢狀物件 | ❌ 否（只第一層） | ✅ 是 |
| 會添加 source 中的新鍵 | ❌ 否 | ✅ 是 |
| 參數順序 | _.defaults(o1, o2, o3) | merge(target, source) |

**差異說明**:
- _.defaults：只處理第一層，巢狀物件不會被合併
- keyValueUpsertMode：會遞迴合併，添加 source 中的新鍵

**為什麼不同**:
lodash 是淺層操作，keyValueUpsertMode 是深度操作

**好處**:
- 可合併深層巢狀物件
- 適合多層級配置

**適用情境**:
- 深度 defaults

### 與 _.defaultsDeep 比較

| 特性 | _.defaultsDeep | keyValueUpsertMode: true |
|------|----------------|-------------------------|
| 遞迴填充缺失的鍵 | ✅ 是 | ✅ 是 |
| 覆蓋已存在的值 | ❌ 否 | ❌ 否（除非是 undefined）|
| 遞迴處理巢狀物件 | ✅ 是 | ✅ 是 |
| 語義 | 填充缺失（fill missing） | 有則保留（preserve if exists）|

**差異說明**:
- 兩者都會遞迴處理，結果可能相同
- 差異在於**語義**而非行為
- _.defaultsDeep：確保有值（fill missing）
- keyValueUpsertMode：保留現有（preserve if exists）

**為什麼不同**:
語義上的差異：fill missing vs preserve existing

**好處**:
- 可實現「保留優先值」的概念
- 更適合配置繼承場景

**適用情境**:
- 配置繼承（基礎配置 + 環境配置）
- 使用者偏好設定覆蓋預設值

### 與 _.merge 比較

| 特性 | _.merge | keyValueUpsertMode: false | keyValueUpsertMode: true |
|------|---------|--------------------------|-------------------------|
| 遞迴合併巢狀物件 | ✅ 是 | ✅ 是 | ✅ 是 |
| 覆蓋所有值 | ✅ 是 | ✅ 是 | ❌ 否 |
| 陣列處理 | 替換 | 串接 | 串接 |
| 保留 undefined 值 | ❌ 否 | ❌ 否 | ✅ 是 |

**差異說明**:
- _.merge 會覆蓋所有值
- keyValueUpsertMode: true 只會填充 undefined 的值

**為什麼不同**:
覆蓋所有值 vs 保留現有值

**好處**:
- 可實現「優先使用現有值」的概念
- 適合「設定優先順序」的場景

**適用情境**:
- 保留使用者設定，只填充未設定的選項
- 配置繼承（後面的設定不覆蓋前面的）

---

## 實際應用範例 / Practical Application Examples

### 1. 配置合併

```typescript
// 預設配置
const defaultConfig = {
    theme: 'light',
    language: 'en',
    api: {
        url: 'https://api.example.com',
        timeout: 5000,
    },
};

// 使用者自訂配置
const userConfig = {
    theme: 'dark', // 使用者自訂
    api: {
        timeout: 10000, // 使用者自訂
    },
};

/**
 * 使用 keyValueUpsertMode: true
 * 將使用者設定作為 target，預設值作為 source
 * 
 * 結果：
 * - theme: 'dark' (使用者設定)
 * - language: 'en' (預設值)
 * - api.url: 'https://api.example.com' (預設值)
 * - api.timeout: 10000 (使用者設定)
 */
const result = merge(userConfig, defaultConfig, {
    keyValueUpsertMode: true,
});
```

### 2. 表單資料填充

```typescript
// 預設值
const defaultValues = {
    username: '',
    email: '',
    profile: {
        firstName: '',
        lastName: '',
        age: undefined,
        bio: '',
    },
};

// 使用者輸入
const userInput = {
    username: 'john_doe',
    email: 'john@example.com',
    profile: {
        firstName: 'John',
        lastName: 'Doe',
        bio: 'Hello world',
    },
};

/**
 * 結果：
 * - username: 'john_doe' (使用者輸入)
 * - email: 'john@example.com' (使用者輸入)
 * - profile.firstName: 'John' (使用者輸入)
 * - profile.lastName: 'Doe' (使用者輸入)
 * - profile.age: undefined (預設值)
 * - profile.bio: 'Hello world' (使用者輸入)
 */
const result = merge(userInput, defaultValues, {
    keyValueUpsertMode: true,
});
```

### 3. 環境配置繼承

```typescript
// 基礎配置
const baseConfig = {
    api: {
        url: 'https://api.example.com',
        timeout: 5000,
        retries: 3,
    },
    logging: {
        level: 'info',
        format: 'json',
    },
};

// 環境配置
const envConfig = {
    api: {
        url: 'https://api-staging.example.com',
        // timeout 和 retries 未設定，使用 base 的值
    },
    logging: {
        level: 'debug',
        // format 未設定，使用 base 的值
    },
};

/**
 * 結果：
 * - api.url: 'https://api-staging.example.com' (環境配置)
 * - api.timeout: 5000 (基礎配置)
 * - api.retries: 3 (基礎配置)
 * - logging.level: 'debug' (環境配置)
 * - logging.format: 'json' (基礎配置)
 */
const result = merge(envConfig, baseConfig, {
    keyValueUpsertMode: true,
});
```

---

## 結論 / Conclusion

### keyValueUpsertMode 與 lodash 的核心差異

1. **遞迴處理方式不同**：
   - lodash _.defaults：只填充第一層缺失的鍵
   - keyValueUpsertMode：會遞迴合併巢狀物件

2. **語義上的差異**：
   - lodash _.defaults：填充缺失的鍵（fill missing）
   - keyValueUpsertMode：有則保留（preserve if exists）

3. **無法完全匹配**：
   - keyValueUpsertMode 無法完全模擬 _.defaults 或 _.defaultsDeep
   - 這是設計上的差異，而非實作問題

4. **適用場景不同**：
   - lodash：適用於需要完全不覆蓋的場景
   - keyValueUpsertMode：適用於需要「有值則保留，無值則填充」的場景
   - lodash：適用於需要完全不覆蓋的場景
   - keyValueUpsertMode：適用於需要「有值則保留，無值則填充」的場景

### 推薦使用方式

| 需求 | 推薦方式 |
|------|---------|
| 保留使用者設定 | merge(userConfig, defaultConfig, { keyValueUpsertMode: true }) |
| 表單預設值填充 | merge(userInput, defaultValues, { keyValueUpsertMode: true }) |
| 配置繼承 | merge(envConfig, baseConfig, { keyValueUpsertMode: true }) |
| 深度合併 | 直接使用 merge() 或 merge(target, source, { keyValueUpsertMode: false }) |
| 自訂邏輯 | 使用 keyValueUpsertMode 函式 |

### 總結

- **最接近 lodash 概念**：無法完全匹配
- **最佳實作（實際應用）**：Attempt 1 + 正確的參數順序
- **keyValueUpsertMode 的價值**：不是用來模擬 lodash，而是提供一種新的合併策略

---

## 更新日誌 / Changelog

| 日期 | 更新內容 |
|------|---------|
| 2026-03-25 | 初始版本，建立 keyValueUpsertMode 實作分析 |
