Created
July 8, 2022 01:38
-
-
Save BlackyWolf/cc7f12c0f66dc8613a2a8d8e67bbbd9b to your computer and use it in GitHub Desktop.
A series of function that help convert the FormData object into a usable model.
This file contains 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
// deno-lint-ignore-file no-explicit-any | |
/** | |
* Checks if the array value being assigned to the property is for simple value | |
* types, e.g. `String`, `Number`, `Boolean`, etc. or for complex types like | |
* `Object`. | |
* | |
* @param name The name of the property on the model object. | |
* | |
* @returns `True` if the property is a primitive array, otherwise `false`. | |
*/ | |
const isPrimitiveArray = (name: string) => name.indexOf("[]") > 0; | |
/** | |
* Checks if the array value being assigned to the property is for complex | |
* types like `Object`, or for simple value types, e.g. `String`, `Number`, | |
* `Boolean`, etc. | |
* | |
* @param name The name of the property on the model object. | |
* | |
* @returns `True` if the property is a complex array, otherwise `false`. | |
*/ | |
function isComplexArray(name: string) { | |
const lastIndex = name.length - 1; | |
const firstBraceIndex = name.indexOf("["); | |
// Checks if there's actually a property name present. Must be at least one | |
// character in length. | |
if (firstBraceIndex < 1) return false; | |
// Checks if the first brace is the last character of the field name. | |
if (firstBraceIndex === lastIndex) return false; | |
// Checks if there are exactly two additional character indices after the | |
// first brace index. | |
if (lastIndex - firstBraceIndex !== 2) return false; | |
const indexNumber = name[firstBraceIndex + 1]; | |
// Ensures the index number in the field name is actually a number. | |
if (isNaN(+indexNumber)) return false; | |
const lastBraceIndex = name.indexOf("]"); | |
// Checks if the last brace index is actually the last character. | |
if (lastBraceIndex !== lastIndex) return false; | |
return true; | |
} | |
/** | |
* Gets the property name of the array for the model. | |
* | |
* @param name The name of the property on the model object. | |
* | |
* @returns The field name minus the braces and index number, if present. | |
*/ | |
function getArrayPropertyName(name: string) { | |
const arrayBracketIndex = name.indexOf("["); | |
return name.substring(0, arrayBracketIndex); | |
} | |
/** | |
* Gets the index number for mapping sub-properties of a complex array. | |
* | |
* @param name The name of the property on the model object. | |
* | |
* @returns The index number of the object in the array. | |
*/ | |
function getComplexArrayIndex(name: string) { | |
const arrayBracketIndex = name.indexOf("["); | |
return +name[arrayBracketIndex + 1]; | |
} | |
/** | |
* Converts the passed value to a primitive value of `Number`, `Boolean`, or | |
* `String`. | |
* | |
* @param value The value to convert. | |
* | |
* @returns The value as either a number, boolean, or the passed value. | |
*/ | |
function convertValue(value: any): number | boolean | string { | |
if (!isNaN(value)) return +value; | |
if (value === "true") return true; | |
return value; | |
} | |
/** | |
* Builds the properties on the passed model object based on the list of names | |
* and value. The method may attempt to recursively build properties of complex | |
* types. | |
* | |
* @param model The model to build the properties for. | |
* @param names The field name, split into an array at the period. | |
* @param value The value to assign to the constructed property. | |
*/ | |
function buildProperty(model: any, names: string[], value: unknown) { | |
const firstPropertyName = names[0]; | |
if (isPrimitiveArray(firstPropertyName)) { | |
const arrayPropertyName = getArrayPropertyName(firstPropertyName); | |
// If the array exists, push the value. | |
if (Array.isArray(model[arrayPropertyName])) { | |
model[arrayPropertyName].push(value); | |
} | |
// Else, create the new array on the property with the passed value. | |
else { | |
model[arrayPropertyName] = [value] | |
} | |
return; | |
} | |
if (isComplexArray(firstPropertyName)) { | |
const arrayPropertyName = getArrayPropertyName(firstPropertyName); | |
const indexNumber = getComplexArrayIndex(firstPropertyName); | |
// If the array does not yet exist on the property, assign an empty | |
// array | |
if (!Array.isArray(model[arrayPropertyName])) { | |
model[arrayPropertyName] = []; | |
} | |
// If the object being assigned values to does not yet exist in the | |
// array, push an empty object to assign values to. | |
if (model[arrayPropertyName].length === indexNumber) { | |
model[arrayPropertyName].push({}); | |
} | |
// Recursively build the complex object using the same method and only | |
// passing in the necessary names and value. | |
buildProperty(model[arrayPropertyName][indexNumber], names.slice(1), value); | |
return; | |
} | |
// If the property is a primitive type, assign the value and return. | |
if (names.length === 1) { | |
model[firstPropertyName] = value; | |
return; | |
} | |
// If the object has not yet been created for the property, assign an empty | |
// object to assign values to. | |
if (!model[firstPropertyName]) model[firstPropertyName] = {}; | |
// Recursively build the complex object using the same method and only | |
// passing in the necessary names and value. | |
buildProperty(model[firstPropertyName], names.slice(1), value); | |
} | |
/** | |
* Converts the `FormData` to an object. The generic type is for convenience | |
* when using the returned value. | |
* | |
* @param formData The form field data used to build the object. | |
* | |
* @returns The newly constructed object model. | |
*/ | |
export function formToModel<TModel>(formData: FormData): TModel { | |
const model: any = {}; | |
formData.forEach((value, key) => { | |
const names = key.split("."); | |
const actualValue = convertValue(value.valueOf()); | |
buildProperty(model, names, actualValue); | |
}); | |
return model; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment