[Delphi] JSON の処理は Serializer を使うのがナウい

# JSON
サーバー・クライアント間でデータをやり取りするフォーマットとして XML と JSON が有名所だと思います。
それぞれのフォーマットについて詳しい所はググればいくらでも出てくるので、ここでは述べません。
ただ、XML に比べて JSON が勝っているところを上げておくと…

* XML よりサイズが小さくなる場合が多い
* Delphi RTL や .NET といった一部のフレームワークには JSON Serializer が用意されている

ということです。

最近、僕が自分でフォーマットを決められる場合は、ほぼ必ず JSON を指定しています。
なぜなら **JSON Serializer が便利すぎる**から！

…で、さっきはググればいくらでも出てくるから述べないと書きましたが、JSON Serializer も同じで色々引っかかります。

では、何故書くのかというと、Qiita には、Delphi の TJsonSerializer の記事がないからです！
（正確には ["クソアプリを作った件"](https://qiita.com/pik/items/4759d8597a434c9a691f#json-serializer) で簡単に紹介していますが）

# Delphi で JSON を扱う

実は Delphi には JSON を扱う仕組みが２つ載っています。

* [JSON オブジェクト フレームワーク](http://docwiki.embarcadero.com/RADStudio/Rio/ja/JSON_%E3%82%AA%E3%83%96%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88_%E3%83%95%E3%83%AC%E3%83%BC%E3%83%A0%E3%83%AF%E3%83%BC%E3%82%AF)
* [JSON リーダー/ライタ フレームワーク](http://docwiki.embarcadero.com/RADStudio/Rio/ja/JSON_%E3%83%AA%E3%83%BC%E3%83%80%E3%83%BC/%E3%83%A9%E3%82%A4%E3%82%BF_%E3%83%95%E3%83%AC%E3%83%BC%E3%83%A0%E3%83%AF%E3%83%BC%E3%82%AF)

それぞれについては上記のリンクをご覧ください。
DocWiki 上では、この２つしか紹介がありませんが、３つめの仕組みとして **JsonSerializer** があるのです。

# TJsonSerializer の使い方

未だに [DocWiki には TJsonSerializer の説明](http://docwiki.embarcadero.com/Libraries/Rio/ja/System.JSON.Serializers)がないため、機能を知らない方も多いと思います。

TJsonSerializer を一言で纏めると、**JSON を構造体・クラスとして読み書きできる仕組み**、です。

## 例１
例えば↓のような JSON があるとします。

```json
{
  "foo":"30",
  "bar":"Sample",
  "baz":["Hoge", "Fuga"]
}
```

では、これを旧来の JSON オブジェクトフレームワークを使った書き方で読んでみると…

```pascal
// uses System.JSON が必要
procedure ReadJsonByJsonObject;
begin
  var Sample := TJsonObject.ParseJSONValue(SAMPLE); // SAMPLE は上記の JSON
  try
    // JSON の内容を読み取る
    var foo := Sample.GetValue<Integer>('Foo');
    var bar := Sample.GetValue<String>('Bar');
    var baz := Sample.GetValue<TJsonArray>('Baz');

    // 出力
    Writeln(foo);
    Writeln(bar);
    for var V in baz do
      Writeln(V.GetValue<String>);
  finally
    Sample.DisposeOf;
  end;
end;
```

こんな感じです。

次に TJsonSerializer を使うと…

```pascal
// uses System.JSON.Serializers が必要
procedure ReadJsonBySerializer;
type
  // JSON を構造体として定義する
  // Field 名は、JSON の Key 名と同じにする
  TSample = record 
    foo: Integer;
    bar: String;
    baz: TArray<String>;
  end;
begin
  var S := TJsonSerializer.Create;
  try
    // JSON の内容を読み取る
    var Sample  := S.Deserialize<TSample>(SAMPLE);

    // 出力
    Writeln(Sample.foo);
    Writeln(Sample.bar);
    for var V in Sample.baz do
      Writeln(V);
  finally
    S.DisposeOf;
  end;
end;
```

構造体が定義されているので、どんな内容の JSON が来るのか一目瞭然ですし、一々型を指定して取り出す必要もありません！

TJsonObject を使った方法は、出力時にも TJsonObject の機能を使います（ ```V.GetValue<String>``` の部分)。
読み込んだデータを他の処理に渡すとき、必然的に他の処理も TJsonObject を使って処理しなければなりません。

一方、TJsonSerializer は、出力時は JSON の機能は使わず、Delphi の基本型を使っているだけです。
つまり、読み込んだデータを別の処理に渡すとき、別の処理は JSON の事を知らなくていいのです。

いまのサンプルは簡単だったので、TJsonObject を使ってもそんなに大変じゃないんじゃない？とお思いでしょう。
しかし、複雑な JSON の場合どうでしょう？

## 例２
[クソアプリを作った件と同じ MediaWiki API](https://qiita.com/pik/items/4759d8597a434c9a691f#%E3%82%A2%E3%83%B3%E3%82%B5%E3%82%A4%E3%82%AF%E3%83%AD%E3%83%9A%E3%83%87%E3%82%A3%E3%82%A2-api) で Wikipedia から記事をランダムで取得した場合を見てみます。

```:wikipediaから記事をランダムに取得
https://ja.wikipedia.org/w/api.php?format=json&action=query&rnnamespace=0&list=random
```

すると、こんな JSON が戻ってきます。

```json
{
    "batchcomplete": "",
    "continue": {
        "rncontinue": "0.833062629913|0.833063470494|2568882|0",
        "continue": "-||"
    },
    "query": {
        "random": [
            {
                "id": 2558363,
                "ns": 0,
                "title": "Predia"
            }
        ]
    }
}
```

これを TJsonObject で書くと

```pascal
procedure ReadJsonByJsonObject;
type
  TRandom = record
    Id: Integer;
    Ns: Integer;
    Title: String;
  end;
begin
  var Root := TJsonObject.ParseJSONValue(MEDIA_WIKI);
  try
    // 読み取り
    var Batchcomplete := Root.GetValue<String>('batchcomplete'); 
    var Cont := Root.GetValue<TJsonObject>('continue');
    var Cont_RNContinue := Cont.GetValue<String>('rncontinue');
    var Cont_Continue := Cont.GetValue<String>('continue');

    var Q := Root.GetValue<TJsonObject>('query');
    var Q_Rand := Q.GetValue<TJsonArray>('random');

    var Q_Rands: TArray<TRandom>;
    SetLength(Q_Rands, Q_Rand.Count);

    var i := 0;
    for var V in Q_Rand do
    begin
      Q_Rands[i].Id := V.GetValue<Integer>('id');
      Q_Rands[i].Ns := V.GetValue<Integer>('ns');
      Q_Rands[i].Title := V.GetValue<String>('title');
      Inc(i);
    end;

    // 出力部は省略
  finally
    Root.DisposeOf;
  end;
end;
```

TJsonSerializer だと

```pascal
procedure ReadJsonBySerializer;
type
  TRandom = record // random 配列の要素はオブジェクト
    id: Integer;
    ns: Integer;
    title: String;
  end;

  TQuery = record // query もオブジェクト
    random: TArray<TRandom>; // 上記の TRandom を要素として持つ配列
  end;

  TContinue = record // continue もオブジェクト
    rncontinue: String;
    continue: String;
  end;

  TRoot = record // JSON 全体の定義
    batchcomplete: String; 
    continue: TContinue; // continue は TContinue として
    query: TQuery;       // query は TQuery として読み込む
  end;
begin
  var S := TJsonSerializer.Create;
  try
    // 読み取り
    var Root  := S.Deserialize<TRoot>(MEDIA_WIKI);

    // 出力部は省略
  finally
    S.DisposeOf;
  end;
end;
```

どちらがエレガントか、一目瞭然ではありませんか！？
TJsonSerializer を使った方法は、どんなに複雑な JSON でも読込はたったの１行です。
複雑さが増せば増すほど、JsonSerializer を使った方が可読性が高くなります。

## TJsonSerializer を使った JSON の生成

次に JSON を生成する方法を見てみます。
ここからは TJsonSerializer のサンプルのみです。

下記の JSON で考えてみます。

```json
{
  "name":"御坂美琴",
  "age":"14",
  "nickname":["常盤台のレールガン", "Level5 の No.3"]
  "ability": {"name":"電撃使い", "Level":"5"}
}
```

まずこの JSON を表す構造体を定義します。

```pascal
type
  TAbility = record // ability が指す JSON Object の構造体を定義して
    name: String;
    level: Integer;
  end;
  TPerson = record // JSON の全体を指す構造体を定義
    name: String;
    age: Integer;
    nickname: TArray<String>;
    ability: TAbility; // ability は TAbility 構造体
  end;
```

こんな感じですね。
ability は {} で括られるためオブジェクトです。
ですので ability は TAbility という構造体で定義しました。

あとは、下記の様に値をセットして

```pascal
// 構造体に値をセット
var Person: TPerson ;
Person.name := '御坂美琴';
Person.age := 14;
Person.nickname := ['常盤台のレールガン', 'Level 5 の No.3'];
Person.ability.name := '電撃使い';
Person.ability.Level := 5;
```

TJsonSerializer.Serialize を呼ぶだけで JSON が生成されます。

```pascal
var S := TJsonSerializer.Create;
try
  // TJsonFormatting.Indent を指定するとインデントした JSON を出力する
  S.Formatting := TJsonFormatting.Indented;

  // JSON 化はたったの１行
  var Json := S.Serialize<TPerson>(Person);

  // 出力
  Writeln(Json);
finally
  S.DisposeOf;
end;
```

```json:結果
{
    "name": "御坂美琴",
    "age": 14,
    "nickname": [
        "常盤台のレールガン",
        "Level 5 の No.3"
    ],
    "ability": {
        "name": "電撃使い",
        "level": 5
    }
}
```

どんなに複雑になっても、やり方は同じです。

でもここで「Object Pascal の予約語が JSON のキー名と同じだったらどうするのさ！」という疑問が！

## TJsonSerializer の属性

TJsonSerializer に使える属性が System.JSON.Serializers に定義されています。
先ほどの疑問を解決するのが **JsonName 属性**です。

### JsonName 属性

JsonName 属性は、Delphi の Field 名と JSON の Key 名が異なっている場合に威力を発揮します。

例えば

```json
  {"begin":"はじめ","end":"おわり"}
```

という JSON だと begin や end は Object Pascal の予約語なのでキー名には使えません。
このような時に JsonName 属性を使うと

```pascal
TSample = record.
  [JsonName('begin')]
  _begin: String;
  [JsonName('end')]
  _end: String;
end;
```

こんな風に書けます。
つまり、Field 名を Key 名として使うのでは無く JsonName 属性で Key 名を指定する、ということです。

### JsonIgnore 属性

JsonIgnore は JSON を生成する際に無視するキーを指定します。

例えば、最初の JSON では foo, bar, baz が存在しましたが、この構造体に FTag という識別用のタグを付けたいとします。

```pascal
TSample = record.
  foo: Integer;
  bar: String;
  baz: TArray<String>;
  FTag: Integer;
end;
```

これをそのまま TJsonSerializer.Serialize すると

```json:結果
{"foo":100,"bar":"aaa","baz":["A","B","C"],"FTag":0}
```

こんな風に FTag も JSON に含まれてしまいます
そこで、FTag に JsonIgnore 属性を付けてあげると…

```pascal
TSample = record.
  foo: Integer;
  bar: String;
  baz: TArray<String>;
  [JsonIgnore]
  FTag: Integer;
end;
```

↓このように FTag が含まれなくなります。

```json:結果
{"foo":100,"bar":"aaa","baz":["A","B","C"]}
```

### JsonConverter 属性

TJsonSerailizer は、多くの型にデフォルトで対応していますが、自分で定義した型だったり、普通とは違う方法で値を処理したい場合などに力を発揮します。

ちょっとあまり良い例ではないのですが、例えば

```json
{"foo":{"a":"aaa", "b":"bbb"}}
```

```json
{"foo":{"n1":"111", "n2":"222"}}
```

↑こんな風に foo の中身が変わる時、a, b, n1, n2 という４つのキーを持つクラスを作り、使う側で判断してもいいのですが、もっと複雑になった時面倒です。読み取った時点で判断してみます。

まず、foo のために a, b, n1, n2 という４つのフィールドを持つ TFoo 型を定義します。
そして、どちらのタイプの JSON なのかを見分けるフラグ DataType を定義します。

なお、この時注意したいのが JsonSerializer で生成されたクラスは自動的に破棄されるのですが、このクラスは自分で生成するので自動的に破棄されるように TInterfacedObject を利用します。

```pascal
type
  // 自動開放されるように Interfaced Object を継承する
  TFoo = class(TInterfacedObject)
  public type
    TDataType = (Text, Number); 
  public
    a, b: String;
    n1, n2: Integer;
    [JsonIgnore]
    DataType: TDataType; // a, b が有効な時は Text, n1, n2 が有効な時は Number を返す
  end;
```

といった型を定義して、さらに JsonConverter を定義し

```pascal
// TJsonConverter の定義には下記の Unit 全てが必要
uses
  System.SysUtils,
  System.TypInfo,
  System.Rtti,
  System.JSON,
  System.JSON.Types,
  System.JSON.Serializers,
  System.JSON.Readers,
  System.JSON.Writers;

type
  TFooConverter = class(TJsonConverter)
  public
    // 該当キーが読まれるときに呼ばれる, 今回は省略したが WriteJson もある
    function ReadJson(
      const AReader: TJsonReader;
      ATypeInf: PTypeInfo;
      const AExistingValue: TValue;
      const ASerializer: TJsonSerializer): TValue; override;
  end;

function TFooConverter.ReadJson(
  const AReader: TJsonReader;
  ATypeInf: PTypeInfo;
  const AExistingValue: TValue;
  const ASerializer: TJsonSerializer): TValue;
begin
  Result := TValue.Empty;

  // Json が null なら何もしない
  if AReader.TokenType = TJsonToken.Null then
    Exit;

  // Foo を生成して、戻り値を設定
  var Foo := TFoo.Create;
  Result := TValue.From(Foo);

  // AReader から JSON を読み出して、Foo に設定する
  ASerializer.Populate(AReader, Foo);

  // a が空の時は Number とする
  if Foo.a.IsEmpty then
    Foo.DataType := TFoo.TDataType.Number
  else
    Foo.DataType := TFoo.TDataType.Text;
end;
```

この TFooConverter を foo の JsonConveter に指定します。

```pascal
type
  TSample = class
    [JsonConverter(TFooConverter)]
    foo: TFoo;
  end;
```

このように定義すると、foo を読み込むときに自動的に TFooConverter が生成され実行されます。
すると、こんな風に foo の中身に応じて DataType が設定されるので、利用側で即座に判断できる、というわけです。

```pascal
procedure ReadData;
const
  JSON_TEXT = '{"foo":{"a":"aaa", "b":"bbb"}}';
  JSON_NUM =  '{"foo":{"n1":"111", "n2":"222"}}';
begin
  var S := TJsonSerializer.Create;
  try
    var Data1 := S.Deserialize<TSample>(JSON_TEXT);
    if Data1.foo.DataType = TFoo.TDataType.Text then // Text?
      Writeln('Text: ', Data1.foo.a, ', ', Data1.foo.b);

    var Data2 := S.Deserialize<TSample>(JSON_NUM);
    if Data2.foo.DataType = TFoo.TDataType.Number then // Number?
      Writeln('Number: ', Data2.foo.n1, ', ', Data2.foo.n2);
  finally
    S.DisposeOf;
  end;
end;
```

つまり、上記の例のように JsonConverter を作成すれば JSON のデータ解析時に独自の処理を組み込めます。

## その他の属性

**JsonSerialize**
Serialize/Deserialize するメンバーを指定します。
次の TJsonMemberSerialization 型の３つの値が指定できます

| 指定   | Serialize 対象            |
|--------|---------------------------|
| Fields | フィールド                |
| Public | Public なメンバー         |
| In     | JsonIn で指定されている物 |

**JsonIn**
上記の JsonSerialize で TJsonMemberSerialization.In が指定されている時、Serialize/Deserialize 対象になります。

**JsonObjectHandling**
**JsonObjectOwnership**

Object の生成方法やそのインスタンスの扱いを決定する属性です。
とてつもなく大きなクラスではない限り、Default のままで特に問題無いので、ここでは割愛します。

# 構造体とクラス
JsonConveter の所でサラッと構造体では無くクラスを使いましたが、基本的にはどちらもで構いません。
ただ、Json の値として nil がある場合は、class の方が直感的でしょう（構造体だとメモリそのものは存在してしまうため、キーが存在しないのか、キーの中身が空なのかどうか判別できない）

# まとめ
JSON を TJsonObject で使ってると死にそうになるけど、TJsonSerializer なら超簡単なので、オススメです。
ただし、高機能になればなるほど処理は重くなる事は忘れないようにしてください。