question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Schema introspection of optional or required fields

See original GitHub issue

Hello to everybody, I am building my forms with zod as a validation solution. Currently I am struggling with finding out, if a field inside an object is required or not. I use this, to display the red asterisk next to a form field so that my schema definition from zod is used for the display and always in sync with the user interface. I have been using the following snippet which has worked out so far:

const isRequired = (name, schema) => !schema._def.shape()[name]?.isOptional(),

This introspects the schema deeper in the tree of my components and checks if inside the _def.shape() object there is a field with name that is not optional.

Right now I have a more complex form approach where I am using superRefine to manually add issues for a field to be required so this approach doesn’t work:

const schema = z.object({
  attendeeType: z.nativeEnum(AttendeeType),
  company: z.string().optional(),
}).superRefine(({ attendeeType, company }, { addIssue }) => {
  if (attendeeType === AttendeeType.BUSINESS && !company) {
    addIssue({
      code: TOO_SMALL, 
      inclusive: true,
      minimum: 1,
      path: ['company'], 
      type: 'string' 
    })
  }
})

Has someone an idea how I get this information for all cases?

Issue Analytics

  • State:open
  • Created 9 months ago
  • Reactions:2
  • Comments:5 (3 by maintainers)

github_iconTop GitHub Comments

1reaction
dominikspiertzcommented, Dec 9, 2022

Hey @maxArturo thank you for your answer. I ended up building a function that dynamically loops through all effects until it finds a ZodObjectDef with .shape() to work with:

export function getZodDef({ _def }: ZodObject<any> | ZodEffects<any>): ZodObjectDef {
  if ('shape' in _def) return _def

  return getZodDef(_def.schema)
}

Still this very inner type has the .optional() property, so calling .isOptional() still returns true, since it does not pick up the effects from zod in my case. Am I missing something? Does .sourceType() work different?

@micah-redwood I am working with the same implementation by using react-hook-form. For now I use a manual flag to set the field to required, if I have a schema that uses .refine(), or .superRefine(). For cases without it, I do not need to set a prop to my component since my posted function does serve my purpose.

0reactions
maxArturocommented, Dec 22, 2022

Still this very inner type has the .optional() property, so calling .isOptional() still returns true, since it does not pick up the effects from zod in my case. Am I missing something? Does .sourceType() work different?

@dominikspiertz sourceType() “pulls out” the inner validator from the effects, so its a quality of life over innerType():

  sourceType(): T {
    return this._def.schema._def.typeName === ZodFirstPartyTypeKind.ZodEffects
      ? (this._def.schema as unknown as ZodEffects<T>).sourceType()
      : (this._def.schema as T);
  }

Also, optional() is defined for all schemas so everything will have .optional() unfortunately. Feel free to do this instead (I tried it out locally and it should work for arbitrary levels of nesting of .refine():

import { z, ZodEffects, ZodObject } from "./src";

const AppComponent = z
  .object({
    first: z.literal("yes").optional(),
    second: z.literal(33),
  })
  .superRefine((t, ctx) => {
    if (!t.first) {
      ctx.addIssue({ message: "error here", code: "custom" });
    }
  })
  .refine((t) => t.second)
  .refine((t) => t.second)
  .refine((t) => t.second)
  .refine((t) => t.second)
  .refine((t) => t.second)
  .refine((t) => t.second);

console.log("first", isRequired("first", AppComponent)); // false 
console.log("second", isRequired("second", AppComponent)); // true

function isRequired(name: string, schema: ZodEffects<any> | ZodObject<any>) {
  if (schema instanceof ZodEffects) {
    return isRequired(name, schema.sourceType());
  }

  const shape = schema._def.shape();
  const element = shape[name];
  return !element.isOptional();
}

Just keep in mind that if your innermost ZodObject has nested attributes, you’ll have to make sure to traverse those yourself at the end. And if this helps solve your issue, please feel free to close it. Thanks!

Read more comments on GitHub >

github_iconTop Results From Across the Web

Get required/non-null field on introspection - graphql
I'm using an introspection to query InputTypes to generically create a form to alter an entity. As far as I understood the type....
Read more >
Making a field in graphql schema required - Squidex Support
When we create a field for any entity and we mark the field as required it is still shown as optional in GraphQL...
Read more >
Introspection | GraphQL Kotlin
By default, GraphQL servers expose a built-in system, called introspection, that exposes details about the underlying schema.
Read more >
Apollo Federation subgraph specification
The sdl field returns a string representation of the subgraph's schema. The returned sdl string has the following requirements:
Read more >
Make required fields non-nullable in GraphQL schema
When you create a required field, the schema should reflect that and make the corresponding GraphQL field non-nullable.
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found