D言語でJSONを扱うときの選択肢としてのmir-ion

# はじめに
D言語でJSONを扱うときのライブラリとして `mir-ion` というものが結構良かったので体験記事として共有します。

稚拙の [openai-d](https://github.com/lempiji/openai-d) というライブラリでもサーバー通信周りで要求応答の管理に使っているのですが、静的にSumTypeみたいな型定義ができ、操作の上で安全性も高いところがおすすめポイントです。
以下詳しく説明していきます。

# D言語でJSONを扱うときの選択肢
D言語でJSONを扱う場合、真っ先に浮かぶのは標準ライブラリの `std.json` です。
ライブラリを導入する手間もないので使うのも容易、[公式ドキュメント](https://dlang.org/phobos/std_json.html)もありますし、[日本語Cookbookの解説](https://dlang-jp.github.io/Cookbook/cookbook--data.json_example.html)もあるので第一候補として十分でしょう。

DUBを通じて利用できる他のライブラリも検討すると、DUBパッケージのスコアが高い方から見ておよそ以下の選択肢があります。

- 標準ライブラリ(std.json)
- vibe-serialization
    - vibe.dのシリアライズ関連サブパッケージ。vibe-dを参照すれば `import vibe.data.json;` 等で使える
- [asdf](https://code.dlang.org/packages/asdf)
- [mir-ion](https://code.dlang.org/packages/mir-ion)
- [jsonizer](https://code.dlang.org/packages/jsonizer)

他の選択肢は、主に速度などのパフォーマンス改善、記法などを改善して扱いやすくする目的で開発されたものが多いです。
特に `asdf` は、様々な言語をまたいだJSONシリアライザのパフォーマンスランキングでもかなり上位の部類です（確か数年前は、The Fastest Json Serializer みたいな文言を掲げていたはず…）。今もC++や一部Rustのを除くと3番手ですね。すごい。

https://github.com/kostya/benchmarks#json

# mir-ionとは

この中でおすすめしたいと思ったのが、今回触った [mir-ion](https://code.dlang.org/packages/mir-ion) です。
なんで `asdf` じゃないの？という話をすると、`asdf` と `mir-ion` は同じ作者で、`mir-ion` のほうが後発で若干機能豊富、2024年時点ではメンテナンスもこちらがやや活発、という背景があるからです。

なお、`mir` というのはD言語のライブラリシリーズの名称です。他に `mir-alfogithm`, `mir-blas`, `mir-lapack`, `mir-lubeck`などがあります。科学計算に関するものが多いイメージですね。

また `-ion` とついていますが、これは `Amazon Ion` というAmazonが開発した階層型シリアライズフォーマットのことです。これが転じて、`mir-ion` はJSONも扱えるライブラリになっています。（他にもyamlとかmsgpackとかも使える）

https://amazon-ion.github.io/ion-docs/

## mir-ionでJSONを使う

何はともあれ、ざっとサンプルを見てみます。

```d
import mir.ser.json;   // JSONシリアライゼーション用モジュール
import mir.deser.json; // JSONデシリアライゼーション用モジュール
import std.stdio;

// シリアライズ対象の構造体を定義
struct Person {
    string name;
    int age;
}

void main() {
    // Person構造体のインスタンスを作成
    Person original = Person("Alice", 30);

    // 構造体をJSON文字列にシリアライズ
    string jsonString = original.serializeJson();
    writeln("Serialized JSON: ", jsonString);

    // JSON文字列を構造体にデシリアライズ
    Person deserialized = jsonString.deserializeJson!Person();
    writeln("Deserialized Person: ", deserialized);
}
```

使うモジュールは、`mir.ser.json` と `mir.deser.json` です。
使うのは `serializeJson` と `deserializeJson` ですね。これだけ覚えれば使うのはなんとかなります。

## 属性によるカスタマイズ

どんなライブラリでも細かいことをしようとすると、色々手間がかかります。
しかしそこは我らがD言語、属性（UDA）を追加することで大体なんとかなります。

```d:フィールドに付ける系UDA
// mir-coreからimportするのでパッケージ名に注意
import mir.serde;

struct Example {
    // JSON custom key specification
    // キー名を変える場合
    @serdeKeys("encoding_format")
    string encodingFormat;

    // Optional key specification
    // Nullableとの組み合わせでOptionalを表現
    @serdeOptional
    Nullable!ChatMessageFunctionCall functionCall = null;

    // Exclusion from serialization
    // デフォルト値(null、stringなら空文字も)はシリアライズ時に含めない
    @serdeIgnoreDefault
    string model;
```

```d:構造体などの型に付ける系UDA
// mir-coreからimportするのでパッケージ名に注意
import mir.serde;

// 無効なキーが含まれる場合、単にそれを除外して扱う
@serdeIgnoreUnexpectedKeys
struct EmbeddingResponse { ... }
```

他にも色々あるので公式サンプルを見てみてください。

## mir-coreで定義された便利機能 `Algebraic`型

mir-ionを支えるコア機能として、 `mir.algebraic` というモジュールがあります。

これは `std.sumtype` で出来ることはほぼ同じで、いわゆる代数的データ型、2つの型AとBがあるとき、 A or B が入る、みたいな型が定義できます。

http://mir-core.libmir.org/mir_algebraic.html#Algebraic

これだけで一本記事が書けそうなので概略を伝えるに留めますが、`mir-algorithm` では、これを使って`JsonAlgebraic` という型が定義されており、動的な部分を極力減らして安全にデータが取り扱えるようになっています。

https://github.com/libmir/mir-algorithm/blob/277dd3723c5144106e7982a95ea8af4bf21b36cf/source/mir/algebraic_alias/json.d#L12-L45

（`mir-core`にAlgebraic型があり、`mir-algorithm`でJsonAlgebraicが定義され、それを`mir-ion`でシリアライズ/デシリアライズする、という構図なので結構ややこしい）


他の実用例としては、`openai-d`だと生成AIのAPIを叩くときのストップトークンを指定するところで、未指定、stringで1個指定する、`string[]`で指定する、という3パターンある箇所があります。
これはAlgebraic型を使って以下のような定義にしました。

```d:nullまたはstringまたはstring[]
import mir.algebraic;

alias StopToken = Algebraic!(typeof(null), string, string[]);
```

これで以下のようにアクセスする必要があるため、静的に型検査され安全になります。

```d:使い方サンプル
StopToken st = "。";

st.match!(
  (typeof(null) _) {},
  (string token) {},
  (string[] tokens) {}
  );
```

JSONで型の重ね合わせ表現みたいなものが最近多いですが、AlgebraicでA or Bという表現をしておけばある程度適切に分岐してもらえます。
デシリアライズのほうは判定用のキーを指定するなど調整も必要ですが、それは公式サンプルなどを探してみてください。


# おわりに

以上、mir-ionを使ったJSONの取り扱いに関する宣伝記事でした。

普段は `std.json` で困らないと思いますが、外部APIとのやり取り、複雑な重ね合わせのようなデータ表現と静的な構造体などへのマッピングが欲しいな、と思ったら候補になると思います。

というわけで、たまのライブラリ紹介記事でした。
みなさんも使ってるライブラリでおすすめがあれば紹介してください！！！