LoginSignup
19
6

More than 1 year has passed since last update.

DartにおけるdynamicとObjectの違い

Posted at

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では明確にdynamicObjectと分けられているみたい。
ここまでくると、dynamicObjectの違いについて興味が湧いてしまいますね。

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.

19
6
6

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
19
6