Case insensitive keys on objects #2920
-
Is there a way to get this to work? Example snippet: const User = z.object({
name: z.string(),
});
const test1 = JSON.parse('{"name": "testy"}');
const test2 = JSON.parse('{"Name": "testy"}'); // <- uppercase N in property name
const result1 = User.safeParse(test1);
const result2 = User.safeParse(test2);
// Returns true :)
console.log(result1.success);
// Returns false :(
console.log(result2.success); |
Beta Was this translation helpful? Give feedback.
Replies: 2 comments
-
Is this what you are looking for? import _ from 'lodash'
const jsonObjSchema = z.record( z.string(), z.any() )
const jsonToObj = z.string().transform( string => JSON.parse( string ) ).pipe( jsonObjSchema )
const jsonToCaseInsensitiveKeysRecord = jsonToObj.transform( obj => _.mapKeys( obj, ( val, key ) => _.camelCase( key ) ) )
const User = z.object( { name: z.string() } )
const jsonToUser = jsonToCaseInsensitiveKeysRecord.pipe( User )
console.log( jsonToUser.parse( '{"name": "foo"}' ) ) // { name: 'foo' }
console.log( jsonToUser.parse( '{"Name": "foo"}' ) ) // { name: 'foo' } If you found my answer satisfactory, please consider supporting me. Even a small amount is greatly appreciated. Thanks friend! 🙏 |
Beta Was this translation helpful? Give feedback.
-
I have a use case where I don't control certain objects and I need to validate them and the casing of the objects is really inconsistent. I've written the following, which will lowercase all keys both in the provided schema and the received objects to parse. This makes it easier to later work with a consistent naming in the code. Note that I didn't consider anything about the import { z } from "zod";
export type ZodObjectLowerKeyedSchema = z.ZodObject<z.ZodRawShape>;
export const zodObjectLowerKeyed = <S extends ZodObjectLowerKeyedSchema>(
zSchemaOrCreator: S | ((zSchema: ZodObjectLowerKeyedSchema) => S),
) => {
const zObject = z.object({});
const zSchema =
zSchemaOrCreator instanceof z.ZodSchema
? zSchemaOrCreator
: (zSchemaOrCreator?.(zObject) ?? zObject);
const zObjectLowerKeyedSchema = z.object(
Object.keys(zSchema.shape).reduce(
(acc, key) => {
const originalKey = key as keyof typeof zSchema.shape;
const lowerKey =
typeof originalKey === "string" ? toLower(originalKey) : originalKey;
// @ts-expect-error: missmatch between string and number and the actual object keys
acc[lowerKey] = zSchema.shape[originalKey];
return acc;
},
{} as LowercaseObjectKeysResult<S["shape"]>,
),
) as z.ZodObject<LowercaseObjectKeysResult<S["shape"]>>;
return z
.record(z.string(), z.any())
.transform((value) =>
Object.keys(value).reduce(
(acc, key) => {
const originalKey = key;
const lowerKey =
typeof originalKey === "string"
? toLower(originalKey)
: originalKey;
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
acc[lowerKey] = value[originalKey];
return acc;
},
{} as LowercaseObjectKeysResult<typeof value>,
),
)
.pipe(zObjectLowerKeyedSchema);
};
function toLower<T extends string>(value: T): Lowercase<T> {
return value.toLowerCase() as Lowercase<T>;
}
type LowercaseObjectKeysResult<T extends object> = {
[K in keyof T as K extends string ? Lowercase<K> : K]: T[K];
}; Usage: const schema = zodObjectLowerKeyed(
z.object({
"First NAME": z.string(),
"Last Name": z.string(),
}),
);
[
{ "First Name": "John", "last name": "Doe" },
{ "First name": "Jane", "last Name": "Doe" },
].forEach((item) => {
console.log("============================");
console.log("parsing:", item);
const result = schema.safeParse(item);
console.log("result:", result);
if (result.success) {
const firstName = result.data["first name"];
// ^? const firstName: string
const lastName = result.data["last name"];
// ^? const lastName: string
} else {
console.log("error:", result.error);
}
console.log("============================");
}); |
Beta Was this translation helpful? Give feedback.
Is this what you are looking for?
If you found my answer satisfactory, please consider supporting me. Even a small amount is greatly appreciated. …