How do I allow a field to be undefined while disallowing type coercion using Yup?

Viewed 3409

I'm trying to validate an object's field using yup.

These are my requirements:

  1. The field should either be a string or undefined.
  2. If it is undefined, it should be given a default value.
  3. If it is a value of some other type, it should not be coerced into a string. Instead, it should be incompatible and should result in an error.

I've tried the following so far:

A. The following schema does not prevent type coercion. e.g. when the value is a number, it passes the validation. However, I want it to fail the validation.

const schema = yup.object().shape({
  myField: yup.string().default('myDefaultString')
});

B. The following schema prevents type coercion, but it fails when I pass an undefined value. I actually want the value to be 'myDefaultString' if an undefined value is given.

const schema = yup.object().shape({
  myField: yup.string().strict(true).default('myDefaultString')
});

C. The following schema has the same result as option B.

const schema = yup.object().shape({
  myField: yup.string().strict(true).notRequired().default('myDefaultString')
});

D. Use strict: true as part of the options when using validateSync method to validate the schema. Again, this has the same result as B.

Would appreciate any help with this, thanks!

2 Answers

Yup is capable of satisfying all three of your requirements using yup.lazy* which creates a schema at validation/cast time. It's also highly readable as it utilizes an easy to follow switch statement.

The following evaluates a value based on your conditions:

const StringOrUndefinedSchema = yup.lazy((value) => {
  switch (typeof value) {
    case 'undefined':
      return yup.string().default('myDefaultString');
    case 'string':
      return yup.string().strict();
    default:
      throw new yup.ValidationError("Value must be a string or `undefined`"); 
  }
});

It can then be used anywhere, including an object's field as in your example:

const ObjectWithFieldSchema = yup.object({
  myField: StringOrUndefinedSchema,
});

Putting it all together:

const yup = require("yup");

const StringOrUndefinedSchema = yup.lazy((value) => {
  switch (typeof value) {
    case 'undefined':
      return yup.string().default('myDefaultString');
    case 'string':
      return yup.string();
    default:
      throw new yup.ValidationError("Value must be a string or `undefined`"); 
  }
});

const ObjectWithFieldSchema = yup.object({
  myField: StringOrUndefinedSchema,
});

const iWillDefault = await ObjectWithFieldSchema.validate({ });
const iWillBeStrict = await ObjectWithFieldSchema.validate({ myField: "I am a string" });

console.log(iWillDefault); // { myField: "myDefaultString" }
console.log(iWillBeStrict); // { myField: "I am a string" }

View the previous example code on RunKit here: https://runkit.com/joematune/6138658a9113f00008292564

I figured that you can extend the yup string as follows:

class StrictString extends yup.string {
  constructor() {
    super();
    transforms = []; // remove the default type coercion transforms
  }
}

And then, replace StrictString() with yup.string(). This will fit the requirements.

Related