Skip to content

Instantly share code, notes, and snippets.

@BlackyWolf
Created July 8, 2022 01:38
Show Gist options
  • Save BlackyWolf/cc7f12c0f66dc8613a2a8d8e67bbbd9b to your computer and use it in GitHub Desktop.
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.
// 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