Last active
March 21, 2024 15:03
-
-
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 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