【kintone】プラグイン設定画面で設定を保存する際にエラーを吐く！

## 出来事
最近、kintoneでプラグインを複数作成しているが、毎回configの保存でエラーを吐いてしまっていた。
`Usage: kintone.plugin.app.setConfig(config, opt_callback)`
理由がわからないが`JSON.stringify()`をがむしゃらに使うと解決することが多かったです。
今回、この原因を調べて記事にしてみました。

## はじめに

kintoneプラグイン開発において、ユーザーが設定した値を保存する場合、単純なテキスト値だけでなく、複雑なデータ構造（配列やオブジェクト）を扱う場面が多々あります。特に複数の設定項目を持つプラグインでは、これらのデータをどのように保存し、どう読み込むかが重要になります。

この記事では、kintoneプラグインの設定画面で複雑なデータ構造を扱う方法、特に `JSON.stringify()` を活用した保存方法について解説します。

## kintone Plugin APIの制約

kintoneプラグインの設定を保存する際に使用する `kintone.plugin.app.setConfig()` メソッドには、重要な制約があります：

**このメソッドは単純なキーと値のペアの連想配列（オブジェクト）しか受け付けません。**

これは以下のようなデータ構造なら問題なく保存できることを意味します：

```javascript
// OK: 単純なキーと値のペア
const config = {
  appId: "123",
  fieldCode: "title",
  isEnabled: "true"  // 文字列として保存
};
```

しかし、以下のような複雑なデータ構造は直接保存できません：

```javascript
// NG: 配列やネストされたオブジェクトを含む
const config = {
  appId: "123",
  fieldCode: "title",
  settings: [
    { field: "field1", value: "value1" },
    { field: "field2", value: "value2" }
  ]
};
```

## JSON.stringifyを使った解決方法

この制約を解決するための基本的なアプローチは、配列やオブジェクトなどの複雑なデータ構造を `JSON.stringify()` を使って文字列に変換して保存することです。

### 保存時の実装例

```javascript
// 保存したいデータ構造
const settings = [
  { field: "field1", value: "value1", isVisible: true },
  { field: "field2", value: "value2", isVisible: false }
];

// 設定オブジェクトを構築
const saveConfig = {
  appId: "123",
  fieldCode: "title",
  settings: JSON.stringify(settings) // 配列を文字列に変換
};

// kintone Plugin APIで保存
kintone.plugin.app.setConfig(saveConfig, function() {
  alert("設定を保存しました");
  // リダイレクト処理など
});
```

この方法により、複雑なデータ構造を文字列として保存することができます。

### 読み込み時の実装例

保存した設定を読み込む際は、文字列から元のデータ構造に戻す必要があります：

```javascript
// 設定を取得
const config = kintone.plugin.app.getConfig(PLUGIN_ID);

if (config) {
  // 単純な値は直接取得可能
  const appId = config.appId;
  const fieldCode = config.fieldCode;
  
  // 文字列化されたデータは JSON.parse() で元に戻す
  let settings = [];
  try {
    settings = JSON.parse(config.settings || '[]');
  } catch (error) {
    console.error('設定の解析に失敗:', error);
  }
  
  // 以降の処理で settings 配列を使用
  console.log(settings);
}
```

## 実装する際の注意点

### 1. 例外処理を適切に行う

`JSON.parse()` は不正な JSON 文字列に対して例外をスローするため、try-catch ブロックで例外処理を行うことが重要です：

```javascript
try {
  const settings = JSON.parse(config.settings || '[]');
  // 成功時の処理
} catch (error) {
  console.error('JSONパースに失敗:', error);
  // エラー時のフォールバック処理
  const settings = [];
}
```

### 2. 型の判定も行う

プラグインの更新などで設定の形式が変わる可能性を考慮し、型チェックも行うと安全です：

```javascript
const settings = typeof config.settings === 'string'
  ? JSON.parse(config.settings)
  : (Array.isArray(config.settings) ? config.settings : []);
```

### 3. デバッグ用のログ出力

開発中は設定オブジェクトの内容を確認できるよう、デバッグ用のログ出力を行うと便利です：

```javascript
console.log('保存する設定オブジェクト:', saveConfig);
console.log('設定オブジェクトの型:', typeof saveConfig);
console.log('JSON文字列化:', JSON.stringify(saveConfig));
```

## 実際のコード例

以下は、複数のフィールド設定を持つプラグインの設定保存処理の具体例です：

```javascript
// 保存ボタンの処理
document.getElementById("js-save-button").addEventListener("click", function () {
  // 基本設定の取得
  const appId = document.getElementById("app-id").value.trim();
  const fieldCode = document.getElementById("field-code").value.trim();
  
  // バリデーション
  const errorMessages = [];
  if (!appId) {
    errorMessages.push('・アプリIDは必須です');
  }
  
  // 複数の設定項目を収集
  const fields = [];
  
  $('.setting-item').each(function () {
    const name = $(this).find('.name-field').val().trim();
    const code = $(this).find('.code-field').val().trim();
    const isVisible = $(this).find('.visibility-toggle').prop('checked');
    
    if (name && code) {
      fields.push({
        name,
        code,
        isVisible
      });
    } else if (name || code) {
      errorMessages.push('・項目「' + (name || code) + '」の情報が不完全です');
    }
  });
  
  // エラーがあれば表示して処理中断
  if (errorMessages.length > 0) {
    alert('以下のエラーを修正してください：\n\n' + errorMessages.join('\n'));
    return;
  }
  
  // 設定オブジェクトの構築
  const saveConfig = {
    appId: appId,
    fieldCode: fieldCode,
    // 複雑なデータ構造は文字列化して保存
    fields: JSON.stringify(fields)
  };
  
  // デバッグ用
  console.log('保存する設定オブジェクト:', saveConfig);
  
  // 設定の保存
  kintone.plugin.app.setConfig(saveConfig, function () {
    alert("設定を保存しました");
    window.location.href = '../../' + kintone.app.getId() + '/plugin/';
  });
});
```

また、設定を読み込む部分は次のようになります：

```javascript
// 既存の設定を読み込む
const config = kintone.plugin.app.getConfig(PLUGIN_ID);

if (config) {
  // 基本設定の読み込み
  $('#app-id').val(config.appId || '');
  $('#field-code').val(config.fieldCode || '');
  
  // 複雑なデータ構造の読み込み
  try {
    // 文字列からオブジェクトに戻す
    const fields = typeof config.fields === 'string'
      ? JSON.parse(config.fields)
      : [];
    
    if (fields && fields.length > 0) {
      // 最初の項目を設定
      $('.setting-item:first').find('.name-field').val(fields[0].name || '');
      $('.setting-item:first').find('.code-field').val(fields[0].code || '');
      
      if (fields[0].isVisible) {
        $('.setting-item:first').find('.visibility-toggle').prop('checked', true);
      }
      
      // 追加の項目があれば、それぞれ新しい行を作成して設定
      for (let i = 1; i < fields.length; i++) {
        // 設定行を追加する処理
        addSettingRow(fields[i]);
      }
    }
  } catch (error) {
    console.error('設定の読み込み中にエラーが発生:', error);
  }
}
```

## よくあるエラーと対処法

### 1. "Usage: kintone.plugin.app.setConfig(config, opt_callback)"

このエラーは、`setConfig()` に正しくないパラメータを渡した場合に発生します。

**原因の例：**
- 設定オブジェクト全体を `JSON.stringify()` してしまった
- 設定オブジェクトではなく配列を渡した

**解決策：**
- 設定オブジェクトは単純なキーと値のペアのオブジェクトとして渡す
- 複雑なデータのみを `JSON.stringify()` する

```javascript
// NG
const settings = {...};
kintone.plugin.app.setConfig(JSON.stringify(settings), callback);

// OK
const settings = {
  key1: value1,
  key2: JSON.stringify(complexValue)
};
kintone.plugin.app.setConfig(settings, callback);
```

### 2. 設定の読み込みで値がundefinedになる

**原因の例：**
- 保存時と読み込み時で、キー名が一致していない
- 新しいバージョンのプラグインで設定構造を変更した

**解決策：**
- キー名の一貫性を保つ
- バージョン間の互換性のための移行コードを用意する

```javascript
// 移行コードの例
if (config.oldKey && !config.newKey) {
  config.newKey = config.oldKey;
}
```

## まとめ

kintoneプラグイン開発において複雑なデータ構造を扱う場合の基本は以下の通りです：

1. **複雑なデータ構造は文字列に変換して保存する**
   - `JSON.stringify()` を使用
   - オブジェクト全体ではなく、複雑なプロパティのみに適用

2. **読み込み時は文字列をデータ構造に戻す**
   - `JSON.parse()` を使用
   - 例外処理を忘れない

3. **デバッグ情報を活用する**
   - 保存前の設定オブジェクトの内容と型をログ出力

これらの方法を使うことで、kintoneプラグイン設定で複雑なデータ構造を安全に扱うことができます。プラグインの機能が複雑になるほど、設定データの管理も重要になってきます。適切な設計と実装で、メンテナンス性の高いプラグインを開発しましょう。

---