Should we change the object schema?

GitHub profile picture of fabian-hillerGitHub profile picture of anderskGitHub profile picture of EltonLobo07

Great news! Valibot v1 RC is just around the corner. There are only a few issues holding us back. One of them is issue #983, which may lead to a breaking change. That's why I want to discuss this with you before making a final decision. This blog post will explain the current situation and the reasons for the proposed changes.

The problem

Currently, Valibot does not distinguish between missing and undefined object entries. This leads to a mismatch between the input and output values of a schema and their types when TypeScript's exactOptionalPropertyTypes configuration is enabled.

import * as v from 'valibot';

// This throws no error and types `output` as `{ key?: string }`
const Schema = v.object({ key: v.optional(v.string()) });
const output = v.parse(Schema, { key: undefined });

// TypeScript thinks that `key` is a string if it is present
// but this is wrong because `key` is actually `undefined`
if ('key' in output) {
  const key = output.key;
}

The solution

The solution to this mismatch is to finalize PR #1013 and change the implementation of object and optional to distinguish between missing and undefined object entries. To allow missing entries, optional must be used as the outermost schema of an object entry. To explicitly allow undefined as a value, another schema function called undefinedable must be used.

const Schema = v.object({
  key1: v.string(),                               // key1: string
  key2: v.optional(v.string()),                   // key2?: string
  key3: v.undefinedable(v.string()),              // key3: string | undefined
  key4: v.optional(v.undefinedable(v.string())),  // key4?: string | undefined
});

The impact

As seen in the previous code example, defining an object entry that can be both missing and undefined gets a little verbose. This gets especially ugly if you define a default value for both cases.

const Schema = v.object({
  key: v.optional(
    v.undefinedable(v.string(), 'undefinedable_default'),
    'optional_default'
  ),
});

To fix this, we could add a new function that covers both. The problem is that there is a third schema function called nullable that also allows null values. So adding a new function to cover every possible combination of optional, undefinedable and nullable would result in 7 functions in total. I doubt this will make the API any better.

Let's discuss

What do you think? Should we distinguish between missing and undefined object entries and fully support TypeScript's exactOptionalPropertyTypes configuration? If so, should we remove nullish and only provide 3 strong primitives with optional, undefinedable and nullable? Or should we add 4 more functions to cover all possible combinations? If so, what names would you use?

Your opinion matters to me. I encourage everyone to share their thoughts. Even quick feedback like "I like this ... and I don't like that ..." is welcome and will help to shape Valibot's future API design. Please discuss with us on GitHub or share your thoughts on social media.

Edit page

Contributors

Thanks to all the contributors who helped make this page better!

  • GitHub profile picture of fabian-hiller

Partners

Thanks to our partners who support the project ideally and financially.

Sponsors

Thanks to our GitHub sponsors who support the project financially.

  • GitHub profile picture of antfu
  • GitHub profile picture of Thanaen
  • GitHub profile picture of osdiab
  • GitHub profile picture of ruiaraujo012
  • GitHub profile picture of hyunbinseo
  • GitHub profile picture of F0rce
  • GitHub profile picture of Unique-Pixels
  • GitHub profile picture of jdgamble555
  • GitHub profile picture of nickytonline
  • GitHub profile picture of KubaJastrz
  • GitHub profile picture of caegdeveloper
  • GitHub profile picture of akhmadqasim
  • GitHub profile picture of dslatkin