JSONGeneratorクラス調査メモ(Apex)

# 概要
- WebAPIへデータをJSONで渡すにあたって[ApexのJSONGenerator](https://developer.salesforce.com/docs/atlas.ja-jp.apexcode.meta/apexcode/apex_class_System_JsonGenerator.htm)を調査し、試したことや思ったことを順番に記録しました
- ありていにいえば、やってみたメモ記事です

# やりたいこと
- ApexでJSON文字列を生成します
- WebAPIへJSON形式で情報を渡すことが目的です
- 最終的に文字列なので、頑張って編集することもできますが、ApexではJSONサポート機能が含まれているのでこれ利用し、スマートに実装したいと思っています
- [Apex開発者ガイド JSONサポート](https://developer.salesforce.com/docs/atlas.ja-jp.apexcode.meta/apexcode/apex_methods_system_json_overview.htm)

# 使い方
* JSON生成サンプルを参照すると、大きく2パターンあるようです
* [Apex開発者ガイド JSON ジェネレータ](https://developer.salesforce.com/docs/atlas.ja-jp.apexcode.meta/apexcode/apex_json_jsongenerator.htm)

## 使い方パターン1
- key/valueをセットする方法です
- `writeNumberField()`や`writeStringField()`のように、`write～Field()`メソッドが該当します

```java:sample1
JSONGenerator g = JSON.createGenerator(false);
g.writeStartObject();
g.writeNumberField('number', 12345);
g.writeEndObject();
System.debug(g.getAsString());
```

```json:sample1-result
{"number":12345}
```

## 使い方パターン2
- keyとvalueを別々にセットする方法です
- `witeFieldName()`してから`write～()`します

```java:sample2
JSONGenerator g = JSON.createGenerator(false);
g.writeStartObject();
g.writeFieldName('number');
g.writeNumber(7890);
g.writeEndObject();
System.debug(g.getAsString());
```

```json:sample2-result
{"number":7890}
```

# 利用イメージまとめ
- `writeStartObject()`で`{`を挿入
- 必要なキーと値をセット
- 配列の場合は`writeStartArray()`で`[`を挿入
- 全部セットしたら`getAsString()`でテキストとして取り出してWebAPIをコール

# 疑問（脱線）
- JSONGeneratorクラスにはメソッドがたくさんありますが、上記パターン1と2のメソッドが型（プリミティブ型）ごとにたくさんある感じですね
- オーバーロードして`writeValue()`とかにまとめられそうな気配は感じますが、String、Number、Booleanなど型ごとにメソッド名が分けられています
- （`writeNumber()`と`writeNumberField()`については別型でオーバーロードしていますが）
 - JSON表記上、クオートするかどうかとか、そういう観点で分けているのでしょうか。（ただの推測です）
 - 参考：[Apex開発者ガイド JSONGeneratorクラス](https://developer.salesforce.com/docs/atlas.ja-jp.apexcode.meta/apexcode/apex_class_System_JsonGenerator.htm)

# オブジェクト型
- プリミティブ型の他に、オブジェクト型を取り扱えます
- これは良いです

## リスト型のサンプル

```java:sample-list
List<Integer> numbers = new List<Integer>();
numbers.add(100);
numbers.add(200);

JSONGenerator g = JSON.createGenerator(false);
g.writeStartObject();
g.writeObjectField('numbers', numbers);
g.writeEndObject();
System.debug(g.getAsString());
```

```json:sample-list-result
{"numbers":[100,200]}
```

## オブジェクト型のサンプル
* ここまでの例では`writeStartObject()`という呪文が必要でした（要は `{`を挿入するステートですね）
* オブジェクトの場合は、いきなり`writeObject()`が可能です。オブジェクトですから、`{`が含まれているんですね
* WebAPIで渡すパラメータなんてたいてい一番外側がオブジェクトな気がするので、JSON全体を大きなオブジェクトにしてしまえば良さそう、つまり、まるごとオブジェクトJSONパターンです
* サンプルは省略しますが、メンバ変数にオブジェクトのリスト型（配列）も定義可能です

```java:sample-object
Hoge h = new Hoge();
h.code = '0001';
h.name = 'Hoge-0001';
h.price = 1200;

JSONGenerator g = JSON.createGenerator(false);
g.writeObject(h);
System.debug(g.getAsString());

public class Hoge {
    public String code;
    public String name;
    public Decimal price;
}
```

```json:sample-object-result
{"price":1200,"name":"Hoge-0001","code":"0001"}
```

# これだけで良いのでは
- 上記、まるごとオブジェクトパターンがあれば、もはやこれだけでいいのでは？と、さっきまで思っていました
- `{`を挿入するために`writeStartObject()`を記述するということは、実装者は文字列編集を意識する必要があるということです
* まるごとオブジェクトパターンでは、クラス定義が必要になりますが、むしろ構造が明確になり、編集機能はJSONGeneratorに任せるという、あるべき実装の姿ではないでしょうか

# 予約キーワード（予約語）の壁
- 結論としては「これだけでは良くない」です
- 予約語をkeyして指定したい場合、そのまま記述できません
- オブジェクトの場合、メンバ変数名がkeyとして採用される振る舞いになっています
- WebAPI連携する場合、keyは相手システムが提示する訳ですが、要求されたkey文字列がApexで予約キーワードだったりして記述できないケースがありました
- 下記サンプルはkeyとして`number`を要求されていたため、そのようにクラス定義したものですが、**numberはApex予約キーワード**のため、コンパイルエラーになります
- すごくキレイに記述できそうなのにできない。おしい。残念です。

```java:sample-error
Hoge h = new Hoge();
h.number = '0001';
h.name = 'Hoge-0001';
h.price = 1200;

JSONGenerator g = JSON.createGenerator(false);
g.writeObject(h);
System.debug(g.getAsString());

public class Hoge {
    public String number;
    public String name;
    public Decimal price;
}
```

```:sample-error-result
Line: 11, Column: 20
Identifier name is reserved: number
```