はじめに
D言語でJSONを扱うときのライブラリとして mir-ion
というものが結構良かったので体験記事として共有します。
稚拙の openai-d というライブラリでもサーバー通信周りで要求応答の管理に使っているのですが、静的にSumTypeみたいな型定義ができ、操作の上で安全性も高いところがおすすめポイントです。
以下詳しく説明していきます。
D言語でJSONを扱うときの選択肢
D言語でJSONを扱う場合、真っ先に浮かぶのは標準ライブラリの std.json
です。
ライブラリを導入する手間もないので使うのも容易、公式ドキュメントもありますし、日本語Cookbookの解説もあるので第一候補として十分でしょう。
DUBを通じて利用できる他のライブラリも検討すると、DUBパッケージのスコアが高い方から見ておよそ以下の選択肢があります。
- 標準ライブラリ(std.json)
- vibe-serialization
- vibe.dのシリアライズ関連サブパッケージ。vibe-dを参照すれば
import vibe.data.json;
等で使える
- vibe.dのシリアライズ関連サブパッケージ。vibe-dを参照すれば
- asdf
- mir-ion
- jsonizer
他の選択肢は、主に速度などのパフォーマンス改善、記法などを改善して扱いやすくする目的で開発されたものが多いです。
特に asdf
は、様々な言語をまたいだJSONシリアライザのパフォーマンスランキングでもかなり上位の部類です(確か数年前は、The Fastest Json Serializer みたいな文言を掲げていたはず…)。今もC++や一部Rustのを除くと3番手ですね。すごい。
mir-ionとは
この中でおすすめしたいと思ったのが、今回触った 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とかも使える)
mir-ionでJSONを使う
何はともあれ、ざっとサンプルを見てみます。
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)を追加することで大体なんとかなります。
// 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;
// mir-coreからimportするのでパッケージ名に注意
import mir.serde;
// 無効なキーが含まれる場合、単にそれを除外して扱う
@serdeIgnoreUnexpectedKeys
struct EmbeddingResponse { ... }
他にも色々あるので公式サンプルを見てみてください。
mir-coreで定義された便利機能 Algebraic
型
mir-ionを支えるコア機能として、 mir.algebraic
というモジュールがあります。
これは std.sumtype
で出来ることはほぼ同じで、いわゆる代数的データ型、2つの型AとBがあるとき、 A or B が入る、みたいな型が定義できます。
これだけで一本記事が書けそうなので概略を伝えるに留めますが、mir-algorithm
では、これを使ってJsonAlgebraic
という型が定義されており、動的な部分を極力減らして安全にデータが取り扱えるようになっています。
(mir-core
にAlgebraic型があり、mir-algorithm
でJsonAlgebraicが定義され、それをmir-ion
でシリアライズ/デシリアライズする、という構図なので結構ややこしい)
他の実用例としては、openai-d
だと生成AIのAPIを叩くときのストップトークンを指定するところで、未指定、stringで1個指定する、string[]
で指定する、という3パターンある箇所があります。
これはAlgebraic型を使って以下のような定義にしました。
import mir.algebraic;
alias StopToken = Algebraic!(typeof(null), string, string[]);
これで以下のようにアクセスする必要があるため、静的に型検査され安全になります。
StopToken st = "。";
st.match!(
(typeof(null) _) {},
(string token) {},
(string[] tokens) {}
);
JSONで型の重ね合わせ表現みたいなものが最近多いですが、AlgebraicでA or Bという表現をしておけばある程度適切に分岐してもらえます。
デシリアライズのほうは判定用のキーを指定するなど調整も必要ですが、それは公式サンプルなどを探してみてください。
おわりに
以上、mir-ionを使ったJSONの取り扱いに関する宣伝記事でした。
普段は std.json
で困らないと思いますが、外部APIとのやり取り、複雑な重ね合わせのようなデータ表現と静的な構造体などへのマッピングが欲しいな、と思ったら候補になると思います。
というわけで、たまのライブラリ紹介記事でした。
みなさんも使ってるライブラリでおすすめがあれば紹介してください!!!