[dart-dio] Skip null optional form params and JSON-encode model form params
Fixes #10295
Problem
For multipart/form-data and application/x-www-form-urlencoded operations, the dart-dio api/serialize.mustache templates put every form param into the map passed to Dio. Dio stringifies every value that isn't a MultipartFile, which causes two bugs:
- Unset nullable params are sent as
""(all three serialization libraries). Theif (x != null)guard is only emitted for non-nullable optional params ({{^required}}{{^isNullable}}…{{/isNullable}}{{/required}}). A nullable optional param that the caller leaves unset is still added to the map, and Dio sends it as an empty value (note=in multipart, a barenotekey in url-encoded bodies). That can overwrite stored values with blanks (e.g. sending an empty password, as reported in #10295). - Model params are sent as
toString()(json_serializableandfreezed). A model orList<Model>form param is passed through as-is, so the server receives the DarttoString()(Asset(id: a1)) instead of JSON.built_valueis not affected, because it already encodes models throughencodeFormParameter.
Fix
built_value,json_serializable,freezed: guard every non-required form param withif (x != null), whether or not it is nullable. Required params are always sent, as before.json_serializable,freezed: wrap model and array-of-model form params injsonEncode(...). Withexplicit_to_json: true(set in both generatedbuild.yamls),toJson()already returns plain JSON, so notoEncodableis needed.uniqueItemsmodel arrays are generated asSet<Model>, whichjsonEncodecannot encode (JsonUnsupportedObjectError), so they are converted with.toList()first. Primitives and files are unchanged.
Generated code for an optional nullable string note, a model assets and a list of models assetList:
// before
r'note': note,
if (assets != null) r'assets': assets,
if (assetList != null) r'assetList': assetList,
// after
if (note != null) r'note': note,
if (assets != null) r'assets': jsonEncode(assets),
if (assetList != null) r'assetList': jsonEncode(assetList),
Verification
Requests captured with a Dio interceptor from clients generated from the new test spec (form_params.yaml), built with build_runner and run:
| request | library | before | after |
|---|---|---|---|
multipart, assets + assetList set, note unset | freezed | note= · assets=Asset(id: a1) · assetList=Asset(id: a2) | assets={"id":"a1"} · assetList=[{"id":"a2"}] |
| json_serializable | note= · assets={id: a1} · assetList={id: a2} | assets={"id":"a1"} · assetList=[{"id":"a2"}] | |
| built_value | note= (models already correct) | note omitted | |
multipart, only name | all three | name=only-name · note= | name=only-name |
url-encoded, assets set | freezed | name=x¬e&assets=Asset%28id%3A+a1%29 | name=x&assets=%7B%22id%22%3A%22a1%22%7D |
url-encoded, only name | freezed, json_serializable | name=only-name¬e | name=only-name |
| built_value | name=only-name¬e= | name=only-name |
- New tests in
DartDioClientCodegenTest:testOptionalNullableFormParamsAreSkippedWhenNull(data-driven over all three libraries) andtestModelFormParamsAreJsonEncoded. They fail onmasterand pass with this change. - The existing dart-dio samples regenerate unchanged: their spec has no nullable or model-typed form params.
Behaviour change
An unset nullable form param is now omitted instead of being sent as "". Callers that relied on "" to clear a field must now pass '' explicitly.
PR checklist
- Read the contribution guidelines.
- Ran
./mvnw clean package,./bin/generate-samples.sh ./bin/configs/dart-dio-*.yamland./bin/utils/export_docs_generators.sh; committed all changed files (no sample changes). - File the PR against the
masterbranch. - Technical committee: @jaumard @amondnet @sbu-WBT @kuhnroyal @agilob @ahmednfwela @wing328
Summary by cubic
Fixes dart-dio form-param serialization: unset nullable form params were sent as "" (see #10295), and json_serializable/freezed sent model form params as Dart toString() output instead of JSON. Optional form params are now omitted when null, and model and List<Model> form params are JSON-encoded. built_value only needed the null-skip fix since it already encodes models.
Behavior change
- Unset nullable form params are now omitted instead of being sent as
""; callers that relied on""to clear a field must pass''explicitly.
Tests
- New generated-code tests cover nullable skip and model JSON encoding across all three serialization libraries.