Skip to content

Instantly share code, notes, and snippets.

@kentcdodds
Created January 6, 2023 21:09
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save kentcdodds/f9fe6995e2c8dea9bbd51da21cfb0a0c to your computer and use it in GitHub Desktop.
Save kentcdodds/f9fe6995e2c8dea9bbd51da21cfb0a0c to your computer and use it in GitHub Desktop.
import { z } from 'zod'
export function preprocessFormData<Schema extends z.ZodTypeAny>(
formData: FormData,
schema: Schema,
) {
const shape = getShape(schema)
return mapObj(shape, ([name, propertySchema]) =>
transformFormDataValue(
getFormValue(formData, String(name), propertySchema),
propertySchema,
),
)
}
type FormDataValue = FormDataEntryValue | FormDataEntryValue[] | null
function getFormValue(
formData: FormData,
name: string,
schema: z.ZodTypeAny,
): FormDataValue {
if (schema instanceof z.ZodEffects) {
return getFormValue(formData, name, schema.innerType())
} else if (schema instanceof z.ZodOptional) {
return getFormValue(formData, name, schema.unwrap())
} else if (schema instanceof z.ZodDefault) {
return getFormValue(formData, name, schema.removeDefault())
} else if (schema instanceof z.ZodArray) {
return formData.getAll(name)
} else {
return formData.get(name)
}
}
function transformFormDataValue(
value: FormDataValue,
propertySchema: z.ZodTypeAny,
): unknown {
if (propertySchema instanceof z.ZodEffects) {
return transformFormDataValue(value, propertySchema.innerType())
} else if (propertySchema instanceof z.ZodOptional) {
return transformFormDataValue(value, propertySchema.unwrap())
} else if (propertySchema instanceof z.ZodDefault) {
return transformFormDataValue(value, propertySchema.removeDefault())
} else if (propertySchema instanceof z.ZodArray) {
if (!value || !Array.isArray(value)) {
throw new Error('Expected array')
}
return value.map(v => transformFormDataValue(v, propertySchema.element))
} else if (propertySchema instanceof z.ZodObject) {
throw new Error('Support object types')
} else if (propertySchema instanceof z.ZodBoolean) {
return Boolean(value)
} else if (propertySchema instanceof z.ZodNumber) {
return Number(value)
} else {
return value
}
}
function getShape<Schema extends z.ZodTypeAny>(schema: Schema) {
let shape = schema
while (shape instanceof z.ZodObject || shape instanceof z.ZodEffects) {
shape =
shape instanceof z.ZodObject
? shape.shape
: shape instanceof z.ZodEffects
? shape._def.schema
: null
if (shape === null) {
throw new Error(`Could not find shape`)
}
}
return shape
}
function mapObj<Key extends string, Value, MappedValue>(
obj: Record<Key, Value>,
cb: (entry: [Key, Value]) => MappedValue,
): Record<Key, MappedValue> {
return Object.entries(obj).reduce((acc, entry) => {
acc[entry[0] as Key] = cb(entry as [Key, Value])
return acc
}, {} as Record<Key, MappedValue>)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment