Skip to content

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

@jv-np

Description

@jv-np
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
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions