Skip to content

Instantly share code, notes, and snippets.

@joemaffei
Last active March 21, 2024 15:03
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save joemaffei/d14c4f11f6a2ade4a8e45deee60c59fc to your computer and use it in GitHub Desktop.
Save joemaffei/d14c4f11f6a2ade4a8e45deee60c59fc to your computer and use it in GitHub Desktop.
Convert an iterable to an object, keeping only key/value pairs that are both strings. Useful for excluding File values from FormData.
/**
* Converts an iterable to an object, keeping only key/value pairs that are both strings.
*/
function pickStringKeysAndValues<
T extends { entries(): IterableIterator<[unknown, unknown]> },
>(iterable: T): Record<string, string> {
let result: Record<string, string> = {};
for (let [key, value] of iterable.entries()) {
if (typeof key === 'string' && typeof value === 'string') {
result[key] = value;
}
}
return result;
}
// BUT... WHY?
const formData = new FormData();
formData.set('key', 'value');
// formData.set('numeric', 123); // not valid in TS, but allowed in JS
// formData.set('bool', false); // not valid in TS, but allowed in JS
// formData.set('undef', undefined); // not valid in TS, but allowed in JS
// etc...
formData.set('file', new Blob());
typeof formData.get('key') === 'string'; // true
// typeof formData.get('numeric') === 'string';
// typeof formData.get('bool') === 'string';
// typeof formData.get('undef') === 'string';
formData.get('file') instanceof File; // THIS IS WHY
/**
* The WhatWG spec doesn't specifically account for initializing
* URLSearchParams with FormData, whose values can be `string` or `File`.
*
* @see https://url.spec.whatwg.org/#interface-urlsearchparams
* @see https://xhr.spec.whatwg.org/#interface-formdata
*
* If the value is a File, it shows as `%5Bobject+File%5D` in the search params
* in most/all implementations, but one could argue that this behavior
* doesn't follow the spec.
*
* The current TypeScript definitions only account for what's defined in the spec,
* so this invocation is invalid:
*/
const notGreat = new URLSearchParams(formData);
/**
* Since URLSearchParams expects an object whose keys and values are strings,
* we can use pickStringKeysAndValues to make TypeScript happy and avoid
* unexpected results.
*/
const good = new URLSearchParams(pickStringKeysAndValues(formData));
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment