javascriptnode.jszod

How to make Zod behave like interface and class forced to implement said interface


I am trying to achieve the following behavior.

export const NotificationDataSchema = z.object({
    type: z.string(),
    data: z.object({}),
});

export const LikesMilestoneSchema = NotificationDataSchema.extend({
    type: z.literal('LikesMilestone'),
    data: z.object({
        count: z.number(),
    });
});

export const NewFollowerSchema = NotificationDataSchema.extend({
    type: z.literal('NewFollower'),
    // data: z.object({
    //     age: z.number(),
    // })
});

And I want NewFollowerSchema to show Error because it doesn't define data similar to how interface and classes interact, is that possible?

My end goal is to create:

export const NotificationData = z.union([
    LikesMilestoneSchema,
    NewFollowerSchema,
]);

export const NotificationSchema = z.object({
    id: z.string(),
    isRead: z.boolean(),
    date: z.date(),
    data: NotificationData
});

So I could send it as a merged list to the frontend where it will be parsed. But can Zod enforce a subschema to define all fields?


Solution

  • I got an answer for it by the creator on github:

    export const NotificationBase = z.object({
      type: z.string(),
      data: z.record(z.string(), z.unknown()),
    });
    
    type NotificationBase = z.ZodType<z.infer<typeof NotificationBase>>;
    
    export const LikesMilestoneSchema = z.object({
      type: z.literal("LikesMilestone"),
      data: z.object({
        count: z.number(),
      }),
    }) satisfies NotificationBase;
    
    export const NewFollowerSchema = z.object({
      type: z.literal("NewFollower"),
    }) satisfies NotificationBase;
    // ^ does not satisfy the expected type 'NotificationDataSchema'.
    

    https://github.com/colinhacks/zod/issues/4654#event-18065855866