Skip to content

zod .defaultValue failing even with default values provided in schema #564

@JoaoVictorVP

Description

@JoaoVictorVP
function.js:64 Uncaught Error: Exhaustive match failed
    at Object.exhaustive (function.js:64:9)
    at default-value.js:21:19
    at loop (function.js:48:14)
    at Array.<anonymous> (functor.js:476:61)
    at Object.transform (function.js:78:22)
    at default-value.js:69:27
    at loop (function.js:48:14)
    at functor.js:439:20
    at functor.js:703:30
    at loop (function.js:48:35)

Reproduction

Custom type ->

import { z } from 'zod';

export class Time {
  value: number;

  get hours() {
    return Math.trunc(this.value);
  }
  get minutes() {
    return Math.trunc((this.value - this.hours) * 60);
  }

  withHours(hours: number) {
    return Time.from(hours, this.minutes);
  }

  withMinutes(minutes: number) {
    return Time.from(this.hours, minutes);
  }

  constructor(value: number) {
    this.value = value;
  }

  static from(hours: number, minutes: number) {
    return new this(hours + (minutes / 60));
  }

  private static intFormat: Intl.NumberFormatOptions = {
    minimumIntegerDigits: 2,
  };

  toString(): string {
    return `${this.hours.toLocaleString(undefined, Time.intFormat)}:${
      this.minutes.toLocaleString(undefined, Time.intFormat)
    }`;
  }

  toJSON() {
    return this.value;
  }

  static readonly zero = new Time(0);

  static forceParse(raw: string): Time {
    const parts = raw.split(':');
    if (parts.length !== 2) {
      throw new Error('Invalid time');
    }

    const hours = parseInt(parts[0]);
    const minutes = parseInt(parts[1]);
    if (isNaN(hours) || isNaN(minutes)) {
      throw new Error('Invalid time');
    }

    return this.from(hours, minutes);
  }

  static is(dat: any): dat is Time {
    return dat instanceof Time;
  }
}

export function zodTime(def?: Time) {
  if (def) {
    return z.union([
      z.instanceof(Time),
      z.number().transform(n => new Time(n)),
      z.string().transform(s => Time.forceParse(s)),
    ]).or(z.instanceof(Time).default(def));
  }
  return z.union([
    z.instanceof(Time),
    z.number().transform(n => new Time(n)),
    z.string().transform(s => Time.forceParse(s)),
  ]);
}

(It also happens if you put .default in every single variant of the union in the def case.)
Simple Code ->

const exampleSchema = z.object({
  name: zodTime(Time.zero),
});
console.log('constructed', zx.defaultValue(exampleSchema));

(also happens with name: zodTime(Time.zero).default(Time.zero))

Expected Behavior

Well, it should simply work if you already provide a default value for the property using zod.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions