colinhacks / zod

TypeScript-first schema validation with static type inference
https://zod.dev
MIT License
33.95k stars 1.19k forks source link

suggestion: z.record() should support .max() and .min() to specify a number of properties #3802

Open mitchell-merry opened 3 weeks ago

mitchell-merry commented 3 weeks ago

Apologies if this is a duplicate - I wasn't able to find any existing discussion on this topic.

I have a use case where I'm trying to replicate the behaviour of the minProperties / maxProperties (see https://json-schema.org/draft/2020-12/json-schema-validation#name-validation-keywords-for-obj).

I can currently achieve this like so:

const TestSchema = z.record(z.string()).refine(
  value => {
    const propertyCount = Object.keys(value).length;
    return propertyCount >= 1 && propertyCount <= 3;
  },
  {
    message: 'Invalid input: must have between 1 and 3 properties',
  },
);

Which results in:

TestSchema.safeParse({}).success; // false
TestSchema.safeParse({ a: 'foo' }).success; // true
TestSchema.safeParse({ a: 'foo', b: 'bar', c: 'baz' }).success; // true
TestSchema.safeParse({ a: 'foo', b: 'bar', c: 'baz', d: 'buz' }).success; // false

I'm proposing that there's an API like so:

const TestSchema = z.record(z.string()).min(1).max(3);

that behaves the same way, primarily so that JSON schema generation tools can read min and max here to fill in the maxProperties and minProperties fields in their generated schemas.

It may be more useful for this to be extended more generally into a minProperties() and maxProperties() that is usable for anything that resembles a z.object, so that it can deal with more complex intersections and constructions of objects than just records. It also may be useful to include a .length() equivalent (though at this moment I don't have use for it).