📝

DartのUri.httpsのqueryParametersはMap<String, dynamic>?ではない

2021/10/21に公開

タイトルそのままです。
一部のAPIはコードをちょっと追っかけて、仕様をチェックしましょう。


Dartのhttpパッケージを使ってGETリクエストをする場合の話です。

GETの場合、クエリパラメーターを組み立てる必要が生じます。
大抵の場合、URLが決まっているので Uri.parse と文字列リテラルの組み合わせで対応できます。次のようなイメージです。

final url = Uri.parse('https://example.com?a=$b`);
final response = await http.get(url);

さて、パラメーターがnon-nullであれば良いのですが、nullableな場合に「文字列にnullを含めたくない」というお気持ちが生まれることがあります。https://example.com?a=nullではなくhttps://example.comとしたい、という気持ちです。

気持ちの赴くままAPIを確認していると、Uri.httpUri.httpsが目につきます。StackOverFlowなんかを見ると評判が悪そうですが、queryParametersのMapを操作してあげればnullがURLの中に入ることもなさそうです。

/// Creates a new `https` URI from authority, path and query.
///
/// This constructor is the same as [Uri.http] except for the scheme
/// which is set to `https`.
factory Uri.https(String authority, String unencodedPath, 
    [Map<String, dynamic>? queryParameters]) = _Uri.https;

https://github.com/dart-lang/sdk/blob/2.14.0/sdk/lib/core/uri.dart#L164

ところが、下記のようなコードを書くと動きません。

final keyString = "key";
final keyInt = 1;

final uri = Uri.https(
  'example.com',
  'list',
   {
     'keyString': keyString,
     'keyInt': keyInt,
   },
);
final response = await http.get(url);

一方で、下記のようにすると動きます。

final keyString = 'key';
final keyInt = 1;

final uri = Uri.https(
  'example.com',
  'list',
   {
     'keyString': keyString,
     'keyInt': '$keyInt', <- here
   },
);
final response = await http.get(url);

この原因を探るために、メソッドを追っていくと原因がわかります。

/// Implementation of [Uri.https].
factory _Uri.https(String authority, String unencodedPath,
    [Map<String, dynamic>? queryParameters]) {
  return _makeHttpUri("https", authority, unencodedPath, queryParameters);
}

static _Uri _makeHttpUri(String scheme, String? authority,
  String unencodedPath, Map<String, dynamic>? queryParameters) {
~~~
return _Uri(
  scheme: scheme,
  userInfo: userInfo,
  host: host,
  port: port,
  pathSegments: unencodedPath.split("/"),
  queryParameters: queryParameters);
}

/// Implementation of [Uri.Uri].
factory _Uri(
  {String? scheme,
  String? userInfo,
  String? host,
  int? port,
  String? path,
  Iterable<String>? pathSegments,
  String? query,
  Map<String, dynamic /*String|Iterable<String>*/ >? queryParameters, <- here
  String? fragment}) {
~~~
  query = _makeQuery(query, 0, _stringOrNullLength(query), queryParameters);
~~~
  return _Uri._internal(scheme, userInfo, host, port, path, query, fragment);
}

static String? _makeQuery(String? query, int start, int end,
    Map<String, dynamic /*String|Iterable<String>*/ >? queryParameters) {
~~~
  if (queryParameters == null) return null;
~~~
  queryParameters.forEach((key, value) {
    if (value == null || value is String) { <- here
      writeParameter(key, value);
    } else {
      Iterable values = value;
      for (String value in values) {
        writeParameter(key, value);
      }
    }
  });
  return result.toString();
}

https://github.com/dart-lang/sdk/blob/2.14.0/sdk/lib/core/uri.dart#L1412

ということで、queryParametersMap<String, dynamic>?で定義されていますが、内部的にMap<String, dynamic /*String|Iterable<String>*/ >?として処理されているようです。


気になったのでIssue立ててみました。
何らかの形で直るといいな。

https://github.com/dart-lang/language/issues/1919

※追記
Issueに返ってきたコメントによると、factory Uriのdocumentに書いてあるからそれを参照して欲しい(意訳)とのことでした。個人的には「う〜ん」という感じなのですが、ドキュメント全体を確認してやっていきましょう!

GitHubで編集を提案

Discussion