Asdfの使用例
JSON形式のデータを取り扱う Asdf の使い方についてまとめます。
Asdf の特徴として、PhobosのJSONではできない、構造体やクラスの直接シリアライズ/デシリアライズや、UDAによる細かな挙動の調整が可能な点があります。
また、 Asdf オブジェクトがPhobosの JSONValue とおおむね同じ役割を持っているオブジェクトですが、AsdfはJSON以外のフォーマットにも対応できるような設計になっている(ような気がします)。
今は parseJson しかありませんが、 parseYaml とか parseSdl とかができる日が来るかもしれません。
今は parseJson しかありませんが、 parseYaml とか parseSdl とかができる日が来るかもしれません。
ドキュメント
http://docs.asdf.dlang.io/index.htmlSource:
https://github.com/dlang-jp/Cookbook/tree/master/thirdparty/json/source/asdf_usage/example.d
Examples:
JSON文字列の解析例です。
parseJson 関数を使って解析し、汎用的な Asdf オブジェクトに変換します。
import asdf; auto source = `{ "type": "VALUES", "obj": null, "flag": true, "count": 3 }`; Asdf json = source.parseJson(); // 文字と真偽値、nullは直接比較が可能です // ただし、注意点としてnullはisでの比較はできません // (演算子オーバーロードを用いてnullと比較を行っているため) assert(json["type"] == "VALUES"); assert(json["obj"] == null); assert(json["flag"]); // 数値は既定値を指定して取得後に比較します assert(json["count"].get(0) == 3); // 単純に値を取り出す場合はキャストするのが簡単です string type = cast(string) json["type"]; bool flag = cast(bool) json["flag"]; size_t count = cast(size_t) json["count"]; // 数値から文字列への変換なんかもキャストで出来てしまいます。 string countStr = cast(string) json["count"]; assert(countStr == "3"); // どうしてもキャスト(変換)できい場合は例外を投げるので、 // 例外処理する必要があります。ifThrownなんかが便利です。 import std.exception : ifThrown; auto typeVal = ifThrown(cast(int) json["type"], 255); assert(typeVal == 255);
Examples:
JSON文字列を独自の構造体にデシリアライズする例です。
1度 Asdf オブジェクトに変換した後か、文字列に deserialize 関数を使うことで独自構造体に変換できます。
import asdf; import std.math : isClose; static struct Constant { string name; double value; } static immutable source = `{ "name": "PI", "value": 3.1415 }`; Constant c = source.deserialize!Constant(); assert(c.name == "PI"); assert(isClose(c.value, 3.1415));
Examples:
オブジェクトのJSON文字列化(シリアライズ)の例です。
import asdf; import std.string : outdent, chompPrefix; static struct Data { string name; int value; } auto data = Data("count", 10); auto text = data.serializeToJson(); assert(text == `{"name":"count","value":10}`); // インデントを含む人が読みやすい形式にするには `serializeToJsonPretty` を使います auto pretty = data.serializeToJsonPretty(); assert(pretty == ` { "name": "count", "value": 10 }`.chompPrefix("\n").outdent());
Examples:
Unix timestampである数値をSysTimeとして扱う場合の変換方法です。
データ型に合わせたProxyを定義して変換ロジックを書き、 serializedAs を対象フィールドに属性として付与します。
See_Also: http://docs.asdf.dlang.io/asdf_serialization.html
import std.datetime; import asdf; // 変換ロジックを持ったProxyを定義します static struct SysTimeProxy { SysTime systime; alias systime this; static SysTimeProxy deserialize(Asdf data) pure { ulong unixtime; deserializeValue(data, unixtime); return SysTimeProxy(SysTime.fromUnixTime(unixtime, UTC())); } void serialize(S)(ref S serializer) pure { serializer.putValue(systime.toUnixTime()); } } static struct Data { // 必要なフィールドに属性として付与します @serializedAs!SysTimeProxy SysTime date; } // 2020-01-01T00:00:00+00:00 auto source = `{ "date": 1577836800 }`; auto json = source.parseJson(); auto data = json.deserialize!Data(); assert(data.date == SysTime(Date(2020, 1, 1), UTC()));
Examples:
特定のキーが複数の形式を取り得る場合の解析例です。
データ型を SumType で定義することで安全に取り扱います。
解析ではAsdfの kind を用いて分岐します。
sumtype : http://code.dlang.org/packages/sumtype
import asdf; import sumtype; alias StringOrInt = SumType!(string, int); static struct StringOrIntProxy { StringOrInt value; alias value this; static StringOrIntProxy deserialize(Asdf asdf) pure { switch (asdf.kind) { case Asdf.Kind.number: return StringOrIntProxy(StringOrInt(cast(int) asdf)); case Asdf.Kind.string: return StringOrIntProxy(StringOrInt(cast(string) asdf)); default: assert(false); } } void serialize(S)(ref S serializer) pure { // dfmt off value.match!( (string value) { serializer.putValue(value); }, (int value) { serializer.putValue(value); }, ); // dfmt on } } static struct Data { @serializedAs!StringOrIntProxy StringOrInt value; } auto s1 = `{ "value": "TEST" }`; assert(s1.parseJson().deserialize!Data().value == StringOrInt("TEST")); auto s2 = `{ "value": 1000 }`; assert(s2.parseJson().deserialize!Data().value == StringOrInt(1000));
Examples:
特定のキーが複数のオブジェクト形式を取り得る場合の解析例です。
データ型を SumType で定義することで安全に取り扱います。
解析ではオブジェクトがtypeキーを持つと想定し、キーを確認して形式を切り替えます。
sumtype : http://code.dlang.org/packages/sumtype
import asdf; import sumtype; static struct MyNumber { double foo; } static struct MyString { string foo; } alias MyData = SumType!(MyNumber, MyString); static struct MyDataProxy { MyData value; alias value this; static MyDataProxy deserialize(Asdf asdf) pure { // 特定のキーで分岐する if (asdf.kind == Asdf.Kind.object) { auto type = cast(string) asdf["type"]; if (type == "number") return MyDataProxy(MyData(MyNumber(cast(double) asdf["value"]))); if (type == "string") return MyDataProxy(MyData(MyString(cast(string) asdf["value"]))); } assert(false); } void serialize(S)(ref S serializer) pure { // dfmt off alias s = serializer; auto state = s.objectBegin(); s.putKey("type"); value.match!( (MyNumber value) { s.putValue("number"); s.putKey("value"); s.putValue(value.foo); }, (MyString value) { s.putValue("string"); s.putKey("value"); s.putValue(value.foo); }, ); s.objectEnd(state); // dfmt on } } static struct Data { @serializedAs!MyDataProxy MyData data; } auto s1 = `{"data":{"type":"number","value":10.5}}`; auto dat1 = Data(MyData(MyNumber(10.5))); assert(dat1.serializeToJson == s1); assert(s1.parseJson().deserialize!Data().data == MyData(MyNumber(10.5))); auto s2 = `{"data":{"type":"string","value":"TEST"}}`; auto dat2 = Data(MyData(MyString("TEST"))); assert(dat2.serializeToJson == s2); assert(s2.parseJson().deserialize!Data().data == MyData(MyString("TEST")));