zod
zod copied to clipboard
Parse a boolean value represented by string logically
From https://github.com/colinhacks/zod/issues/1630#issuecomment-1824670600
Currently z.boolean()
simply replesents Boolean()
for coerce. So it handles string value as boolean along with the standard Boolean()
specification.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean#boolean_coercion
const b = z.coerce.boolean()
b.parse("true"); // true
b.parse("false"); // true
b.parse("True"); // true
b.parse("False"); // true
b.parse("1"); // true
b.parse("0"); // true
b.parse(""); // false
b.parse(undefined); // false
b.parse("others"); // true
However we often need to handle string as boolean, "false"
or "0"
as false
, such as parsing environment valiables.
Current workaround is like
const b1 = z.string().toLowerCase().transform(JSON.parse).pipe(z.boolean());
b1.parse("true"); // true
b1.parse("false"); // false
b1.parse("True"); // true
b1.parse("False"); // false
b1.parse("tRue"); // true
b1.parse("fAlse"); // false
b1.parse("1"); // ZodError
b1.parse("0"); // ZodError
b1.parse("others"); // ZodError
My suggestion is a new string transformation: z.string().boolean()
The transforming specification in Go standard library's strconv.ParseBool()
is good for it.
https://pkg.go.dev/strconv#ParseBool
ParseBool returns the boolean value represented by the string. It accepts 1, t, T, TRUE, true, True, 0, f, F, FALSE, false, False. Any other value returns an error.
// Suggestions
const b1 = z.string().boolean()
b1.parse("true"); // true
b1.parse("false"); // false
b1.parse("True"); // true
b1.parse("False"); // false
b1.parse("tRue"); // ZodError in boolean transformation
b1.parse("fAlse"); // ZodError in boolean transformation
b1.parse("1"); // true
b1.parse("0"); // false
b1.parse(""); // ZodError in boolean transformation
b1.parse(undefined); // ZodError by string() as required
b1.parse("others"); // ZodError in boolean transformation
// false for undefined string
const b2 = z.string().boolean().default("false")
b2.parse(undefined); // false
// false for any boolean transformation errors and string errors
const b3 = z.string().boolean().catch(false)
b3.parse("tRue"); // false
b3.parse("fAlse"); // false
b3.parse(""); // false
b3.parse(undefined); // false
b3.parse("others"); // false
const b1 = z.string().toLowerCase().transform(JSON.parse).pipe(z.boolean());
If there is such a simple work around, then why should this be a part of zod core?
@JacobWeisenburger Thank you for your feedback.
My opinions are:
-
z.string().toLowerCase().transform(JSON.parse).pipe(z.boolean())
is not clear for users. As you can see #1630, many users are struggling to do this in different ways and sometimes mistake in their programs. It is worth to be on the lineup of builtin string transformation and helps many users in the future. - It is gereral to set
"1"
or"0"
as a boolean in environment variables for lot of tools or products butJSON.parse()
does not works. I think boolean is a last piece of zod advantage in the area of environment variables parsing.
const envSchema = z.object({
SERVER_URL: z.string().min(5, {message: "Required"}),
SOMETHING_VALUE: z.string(),
EXPIRE_DAYS: z.coerce.number().default(30),
SOMETHING_NUMERIC_CONFIG: z.coerce.number().min(1),
DEBUG: z.string().boolean(),
ENABLE_SOMETHING: z.string().boolean(),
});
const env = envSchema.parse(Deno.env.toObject());
I beleave that it is not much complex implementation. Sorry for my previous comment with lack of understaing of the zod architecutre. Now I am deepdiving zod codes.
This is a really good idea. I lost a couple hours of debugging today trying to figure out why "false" provided as a string to a ZOD parse function was resulting in "true" for a z.boolean() value. I know it is fault of Boolean('false') being truthy, but this is a foot gun. No one reasonable expects "false" to be parsed as Boolean true.
@jlandowner this is both useful for environment variable parsing as well as URL query parameter parsing. It is incredibly common to have "feature=true" or "feature=false" on an URL and for that to parse as always "true" by default is sort of crazy.
Also I should add that this bug isn't present with Joi. So when I converted over to Zod from Joi, this was a huge surprise.
A really quick heads up, rather than invoking the entire JSON.parse
the transform can also be just
z.string().toLowerCase().transform((x) => x === 'true').pipe(z.boolean())
This issue is for a feature request and the implementation in PR #2989.
If you would like to discuss about workarounds, #1630 is a better place.
This is what is working for me:
z.enum(['true', 'false']).transform((value) => value === 'true')
I didn't find any working solutions for my use case (query params), but I modified one example from here
export const BooleanStringZod = z
.string()
.refine((value) => value === 'true')
.transform((value) => value === 'true');
Now it does what I want. string "true" turns into boolean true, everything else is false and basically fails.
EDIT:
For my use-case, that actually was the wrong answer. I should have just done this:
export const BooleanStringZod = z.preprocess((val) => val === 'true', z.boolean()).default(false);
This code won't fail on any input and just returns false
if you don't pass it "true"-value. Not the "optimal" use of Zod for now, but it fits into my schema, where I just want to convert some JSON string-fields from another API into booleans
See https://github.com/colinhacks/zod/pull/2989#issuecomment-2091965974
I'm going to leave a comment here in case anyone has the same problem as me and finds this same GitHub issue on Google.
I needed a schema that handles both boolean values and boolean strings such as "true"
and "false"
, turning them into a true boolean.
The solution I found was to use z.preprocess
:
const schemas = {
foo: z.preprocess((val) => {
if (typeof val === "string") {
if (val.toLowerCase() === "true") return true;
if (val.toLowerCase() === "false") return false;
}
return val;
}, z.boolean()),
}
I'm going to leave a comment here in case anyone has the same problem as me and finds this same GitHub issue on Google.
I needed a schema that handles both boolean values and boolean strings such as
"true"
and"false"
, turning them into a true boolean. The solution I found was to usez.preprocess
:const schemas = { foo: z.preprocess((val) => { if (typeof val === "string") { if (val.toLowerCase() === "true") return true; if (val.toLowerCase() === "false") return false; } return val; }, z.boolean()), }
Like a charm! Thank you.