dynamic と Object
一応、ポエム記事です。
きっかけ
先日、仕事でたまたまAPIにリクエストを送る時のパラメータの一部にJSON化したものを渡すものがあったので、
その処理を書こうと次のコードを書いた時にコンパイルエラーになったので気になったのがきっかけです。
修正前
Future<http.Response> post({required String path, Map<String, Object>? bodyParams}) async {
// 省略
}
final jsonParam = json.decode(param);
final params = {"key": jsonParam};
final response = await this.post(
path: 'https://api-sample.com/sample',
bodyParams: params // <- コンパイルエラー
);
(コード自体はかなり雑に書いていて、事象がわかりやすいように加工しています。)
コード自体は単純でAPIのparamにJSON化したものを渡さないといけなかったので、json.decode(param)
しました。
params
にしてAPIクライアントに引数として渡すやり方が好きです。
エラーになった理由はjson.decode(param)
で返ってきた値の型がdynamic
で、post
関数のMap<String, Object>?
のbodyParams
の
Object
に渡すことができないということがエラーの原因でした。
Map<String, dynamic>?
に型を変更するとうまくいくようになりました。
修正後
Future<http.Response> post({required String path, Map<String, dynamic>? bodyParams}) async {
// 省略
}
Map<String, Object>?
にしていたのは筆者のFlutter開発の経験値が浅いのが大きな理由で、次の理由はdynamic
を極力使わないコーディングスタイルを心掛けていたためでした。
それでもStringやintは渡せていたのでうまくいっていました。
継承関係?
今回はJSON(dynamic型)でしたのでdynamicはObjectに渡せないからエラーが発生しました。
この時点ではdynamicとObjectで継承関係が違うんだろうなというぐらいの理解でした。
普段ならここまでで終了で特に意識しないんですが、ふと「dynamicとObjectってどう違うの」と疑問に残りました。
dynamicとObjectの継承関係ってどんな感じなんだろうって、まあブログのネタになりそうかなと。
そこで、Twitterにメモとして残すことにしました。こうしたらあとで将来の自分が復習してくれることを願ってのことですが・・・
そしたら、すぐにへぶんさん(@heavenOSK)からリプライが来て驚きました。
Swiftでいうところの
dynamic = Any
Object = AnyObject
ということでもないらしく、
dynamicは「動的に型が決まっていない」からObject
にキャストできないのが正しいみたいです。
JsonCodec#decode の定義
JsonCodec#decode
の定義にジャンプすると次のコードで定義されています。
dynamic decode(String source,
{Object? reviver(Object? key, Object? value)?}) {
reviver ??= _reviver;
if (reviver == null) return decoder.convert(source);
return JsonDecoder(reviver).convert(source);
}
ということで、dynamic
は定義しようと思えばできるみたいで、Dartでは明確にdynamic
とObject
と分けられているみたい。
ここまでくると、dynamic
とObject
の違いについて興味が湧いてしまいますね。
dynamicってSwiftのvar
の位置付けだと思っていました。
公式ドキュメントでは
また、共有いただいた公式ドキュメントのページに次の記載がありました。
AVOID using dynamic unless you want to disable static checking.
静的チェックの恩恵を受けたい場合にはdynamic
を使うのは避けるべきと書いていました。
Objectもdynamicもすべてのオブジェクトを許すものですが、
- Object: 全てのオブジェクトを許可する
- dynamic: 「動的な型」で全てのオブジェクトを許可するだけでなく、全ての操作も許可するもの
という違いがありました。
基本はObject
にして、動的ディスパッチが必要な場合に、dynamicにするのが良さそうです。
つまり、Map<String, Object>
で良いわけですね。
dynamic にする例外
この基本から外れる例外がAPIを利用する場合です。
The main exception to this rule is when working with existing APIs that use dynamic, especially inside a generic type. For example, JSON objects have type Map and your code will need to accept that same type. Even so, when using a value from one of these APIs, it’s often a good idea to cast it to a more precise type before accessing members.
つまり、JSONオブジェクトが絡む場合はMap<String, dynamic>
にする必要があるということ。
なので、APIクライアントが絡むクラスではMap<String, dynamic>
がベターということですね。
いやー、ほんと勉強になりました。
へぶんさん(@heavenOSK)に感謝です!
参考
Objectの定義
The base class for all Dart objects except null.