DartにおけるdynamicとObjectの違い

# dynamic と Object

一応、ポエム記事です。

## きっかけ

先日、仕事でたまたまAPIにリクエストを送る時のパラメータの一部にJSON化したものを渡すものがあったので、
その処理を書こうと次のコードを書いた時にコンパイルエラーになったので気になったのがきっかけです。

修正前

```dart
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>?`に型を変更するとうまくいくようになりました。

修正後

```dart
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にメモとして残すことにしました。こうしたらあとで将来の自分が復習してくれることを願ってのことですが・・・

https://twitter.com/tamapppe/status/1451381771501654025

そしたら、すぐにへぶんさん(@heavenOSK)からリプライが来て驚きました。

https://twitter.com/heavenOSK/status/1451385639673368580

https://twitter.com/heavenOSK/status/1451390104111497220

Swiftでいうところの

dynamic = Any
Object = AnyObject

ということでもないらしく、
dynamicは「動的に型が決まっていない」から`Object`にキャストできないのが正しいみたいです。

https://api.dart.dev/stable/2.14.2/dart-convert/JsonCodec/decode.html

## JsonCodec#decode の定義

`JsonCodec#decode`の定義にジャンプすると次のコードで定義されています。

```dart
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.

https://dart.dev/guides/language/effective-dart/design#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<String, dynamic> 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の定義

https://api.dart.dev/stable/2.14.4/dart-core/Object-class.html

> The base class for all Dart objects except null.