Yup: Create dynamic validation schema.

Created on 23 Jun 2019  路  25Comments  路  Source: jquense/yup

I have a JSON from which I'm trying to create dynamic form and validation. I'm using Yup along with Formik. I want to create the validation schema after iterating over the form elements validation key but I'm not sure how to do this.

export const formData = [
  {
    id: "name",
    label: "Full name",
    placeholder: "Enter full name",
    type: "text",
    required: true,
    value: "User name",
    values: [],
    validations: [
      {
        type: "minLength",
        value: "5",
        error_message: "name should be atleast 5 char long"
      },
      {
        type: "maxLength",
        value: "10",
        error_message: "name should be atleast 5 char long"
      }
    ]
  },
  {
    id: "email",
    label: "Email",
    placeholder: "Email",
    type: "text",
    required: true,
    value: "email",
    values: [],
    validations: [
      {
        type: "minLength",
        value: "5",
        error_message: "name should be atleast 5 char long"
      },
      {
        type: "maxLength",
        value: "10",
        error_message: "name should be atleast 5 char long"
      },
      {
        type: "email",
        error_message: "Valid email"
      }
    ]
  },
  {
    id: "phoneNumber",
    label: "phone number",
    type: "text",
    required: true,
    value: "7878787878",
    values: [],
    validations: [
      {
        type: "minLength",
        value: "5",
        error_message: "name should be atleast 5 char long"
      },
      {
        type: "maxLength",
        value: "10",
        error_message: "name should be atleast 5 char long"
      },
      {
        type: "required",
        error_message: "phone number is required"
      }
    ]
  },
  {
    id: "total",
    label: "Total People in Family",
    placeholder: "family members count",
    type: "text",
    required: false,
    value: "1",
    values: [],
    validations: [
      {
        type: "minLength",
        value: "1",
        error_message: "there should be atleast 1 family member"
      },
      {
        type: "maxLength",
        value: "5",
        error_message: "max family members can be 5"
      }
    ]
  }
]

I want to create something like after iterating over the validation but I'm unable to do so.
let validateSchema = yup.object().shape({ name: yup.string().required("name is required"), email: yup.string().email(), phoneNumber: yup.number().min(10, "minium 10 numbers"), total: yup .number() .min(1, "minium 1 member") .max(5, "max 5 member") .required("member is required") })
Which I will send to Formik. I looked into many links, even posted on SO but couldn't find any useful lead.

Codesandbox

Most helpful comment

In case someone is trying to create yupschema on the fly. With some help, I was able to do it without using any external package.

import * as yup from "yup";

export function createYupSchema(schema, config) {
  const { id, validationType, validations = [] } = config;
  if (!yup[validationType]) {
    return schema;
  }
  let validator = yup[validationType]();
  validations.forEach(validation => {
    const { params, type } = validation;
    if (!validator[type]) {
      return;
    }
    console.log(type, params);
    validator = validator[type](...params);
  });
  schema[id] = validator;
  return schema;
}

codesandbox

All 25 comments

This is the only open source one available right now - https://www.npmjs.com/package/json-schema-to-yup I'm working on something similar because it didn't suit my needs, but it's not ready for open source yet.

In case someone is trying to create yupschema on the fly. With some help, I was able to do it without using any external package.

import * as yup from "yup";

export function createYupSchema(schema, config) {
  const { id, validationType, validations = [] } = config;
  if (!yup[validationType]) {
    return schema;
  }
  let validator = yup[validationType]();
  validations.forEach(validation => {
    const { params, type } = validation;
    if (!validator[type]) {
      return;
    }
    console.log(type, params);
    validator = validator[type](...params);
  });
  schema[id] = validator;
  return schema;
}

codesandbox

@vijayranghar this approach is solid! thanks for sharing

Thanks @vijayranghar for sharing it. was looking for that exact way to solve it.

@vijayranghar Great example, just one thing I can't get my head around is this ...
createYupSchema(schema, config)
Where does the "schema" come from?
In the form.js you reduce like this ...
const yepSchema = formData.reduce(createYupSchema, {});
But I just can't figure it out.
It would be great if you could explain it.

@vijayranghar one more question, how to add to an existing validation schema? Let's say I already have two inputs inside the form that doesn't get generated dynamically and they already have validation inside an existing schema, how to add to that the dynamic validation?

@vijayranghar is there any way to support object and array in implementation?

@vijayranghar this is elegant. Thank you for sharing.

@vijayranghar thank you!!

Edit. Just a little any makes everybody happy.

import * as yup from "yup";

export function createYupSchema(schema: any, config: any) {
    const {id, validationType, validations = []} = config;

    if (!(yup as any)[validationType]) {
        return schema;
    }

    let validator = (yup as any)[validationType]();

    validations.forEach((validation: any) => {
        const {params, type} = validation;
        if (!validator[type]) {
            return;
        }
        validator = validator[type](...params);
    });

    schema[id] = validator;
    return schema;
}

Although this issue is resolved.. for those looking for a way to do this via a utility I just released this: https://www.npmjs.com/package/json-schema-yup-transformer

Although this issue is resolved.. for those looking for a way to do this via a utility I just released this: https://www.npmjs.com/package/json-schema-yup-transformer

Can you explain how your project is different / better / more appealing than the existing one? https://www.npmjs.com/package/json-schema-to-yup

Competition is good, but why not just work with the existing one to improve it?

I鈥檝e contributed to schema to yup, it鈥檚 a good project...however decided to build my own because:

  1. Does not adhere to the draft 7 spec
  2. Lacks good code coverage
  3. The code is hard to read and debug
  4. It feels like it鈥檚 more catered to making schemas work with yup as opposed to adhering to schema specifications

The main difference I see with schema to yup is that is intended to work with all sorts of schemas but does not strictly adhere to their rules.

Hope that helps.

@ritchieanesco Fantastic reasons! You should add... written in typescript. =)

I just took a look and you've got nice test coverage. I'll definitely go with yours when I need it. Keep up the great work.

@lookfirst how can we add conditional validation here, like
Yup.object().shape({ email: Yup.string().email('Invalid email address').required('Email is required!'), username: Yup.string().required('This man needs a username').when('email', (email, schema) => { if (email === '[email protected]') { return schema.min(10); } return schema; }), });

@lookfirst how can we add conditional validation here, like
Yup.object().shape({ email: Yup.string().email('Invalid email address').required('Email is required!'), username: Yup.string().required('This man needs a username').when('email', (email, schema) => { if (email === '[email protected]') { return schema.min(10); } return schema; }), });

For this to work your config should look like this:

const exampleFieldConfig = {
    id: "username",
    label: "Username",
    placeholder: "username",
    type: "text",
    validationType: "string",
    required: true,
    value: undefined,
    validations: [
      {
        type: "required",
        params: ["this field is required"]
      },
      {
        type: "min",
        params: [5, "current min: 5 characters"]
      },
      {
        type: "when",
        params: [
          "email",
          (email, schema) => {
            return email === "[email protected]" ? schema.min(10, "current min: 10 characters") : schema;
          }
        ]
      }
    ]
  }

Then you can pass it to the function:

createYupSchema({}, exampleFieldConfig)

Hi everyone!
In my case, I don't want write a lot of code to do the nested validations that Yup do "automatically".
So I resolved the dynamic validation creating a function that receive the data to validate, and in the field that I want change the validation, I execute an lambda function that return the corresponding new Yup instance.

import * as Yup from 'yup'

const schemaGenerator = data => Yup.object().shape({
  name: Yup.string().required('this field is required'),
  email: Yup.string().email('enter a valid email').required('this field is required'),
  address: Yup.object().shape({
    city: Yup.string().required('this field is required'),
    neighborhood: (() => {
      let validation = Yup.string()
      if (data?.address?.street) {
        validation = validation.required('this field is required')
      }
      return validation
    })(), // lambda function here to create neighborhood schema dinamically!
    street: Yup.string()
  })
})

So, to:

schemaGenerator({
  name: '',
  email: '',
  address: {
    city: '',
    neighborhood: '',
    street: ''
  }
})

I have corresponding Yup schema:

Yup.object().shape({
  name: Yup.string().required('this field is required'),
  email: Yup.string().email('enter a valid email').required('this field is required'),
  address: Yup.object().shape({
    city: Yup.string().required('this field is required'),
    neighborhood: Yup.string(),
    street: Yup.string()
  })
})

And to:

schemaGenerator({
  name: '',
  email: '',
  address: {
    city: '',
    neighborhood: '',
    street: 'something'
  }
})

I have corresponding Yup schema:

Yup.object().shape({
  name: Yup.string().required('this field is required'),
  email: Yup.string().email('enter a valid email').required('this field is required'),
  address: Yup.object().shape({
    city: Yup.string().required('this field is required'),
    neighborhood: Yup.string().required('this field is required'),
    street: Yup.string()
  })
})

@vijayranghar thas is nice!!! thank you very much, do you know how to add a nullable validation to a date field with this approach?

In case someone is trying to create yupschema on the fly. With some help, I was able to do it without using any external package.

import * as yup from "yup";

export function createYupSchema(schema, config) {
  const { id, validationType, validations = [] } = config;
  if (!yup[validationType]) {
    return schema;
  }
  let validator = yup[validationType]();
  validations.forEach(validation => {
    const { params, type } = validation;
    if (!validator[type]) {
      return;
    }
    console.log(type, params);
    validator = validator[type](...params);
  });
  schema[id] = validator;
  return schema;
}

codesandbox

Hi,

I have the following schema requirement:

validationSchema: Yup.object({
        dialCode: Yup.string().required('Required'),
        mobileNumber: Yup.string().trim().when('dialCode', (dialCode, schema) => {
            return schema
                .phone(getAlpha2Code(dialCode), true, 'Phone number is invalid')
                .required('Phone number is required');
        })
    }),

_getAlpha2Code is function which returns the Alpha2Code of a particular country from its dial code._
How do I create a JSON schema for this particular Yup schema? What should the validations array look like, especially for the mobileNumber?
Please help.

@Waqqars987 I was wondering the same. I have a JSON that contains the inputs and although Im able to get min and max working. Required never seems to pickup

import * as yup from "yup";

export default function createYupSchema(schema, config) {
  const { id, validationType, validations = [] } = config;
  if (!yup[validationType]) {
    return schema;
  }
  let validator = yup[validationType]();
  validations.forEach((validation) => {
    const { type, value, msg } = validation;
    if (!validator[type]) {
      return;
    }
    validator = validator[type](value, msg);
  });
  schema[id] = validator;
  return schema;
}

Input:
"inputs": [ { "id": "name", "type": "text", "label": "Name", "validationType": "string", "validations": [ { "type": "required", "msg": "this field is required" }, { "type": "min", "value": 5, "msg": "name cannot be less than 5 characters" }, { "type": "max", "value": 10, "msg": "name cannot be more than 10 characters" } ] }]

@Waqqars987 I was wondering the same. I have a JSON that contains the inputs and although Im able to get min and max working. Required never seems to pickup

import * as yup from "yup";

export default function createYupSchema(schema, config) {
  const { id, validationType, validations = [] } = config;
  if (!yup[validationType]) {
    return schema;
  }
  let validator = yup[validationType]();
  validations.forEach((validation) => {
    const { type, value, msg } = validation;
    if (!validator[type]) {
      return;
    }
    validator = validator[type](value, msg);
  });
  schema[id] = validator;
  return schema;
}

Required does work for me. However, I can't figure out how to make other Yup functions work as per my requirements.

@Waqqars987 I was wondering the same. I have a JSON that contains the inputs and although Im able to get min and max working. Required never seems to pickup

import * as yup from "yup";

export default function createYupSchema(schema, config) {
  const { id, validationType, validations = [] } = config;
  if (!yup[validationType]) {
    return schema;
  }
  let validator = yup[validationType]();
  validations.forEach((validation) => {
    const { type, value, msg } = validation;
    if (!validator[type]) {
      return;
    }
    validator = validator[type](value, msg);
  });
  schema[id] = validator;
  return schema;
}

Required does work for me. However, I can't figure out how to make other Yup functions work as per my requirements.

What shape is your JSON? I've attached mine above

@Waqqars987 I was wondering the same. I have a JSON that contains the inputs and although Im able to get min and max working. Required never seems to pickup

import * as yup from "yup";

export default function createYupSchema(schema, config) {
  const { id, validationType, validations = [] } = config;
  if (!yup[validationType]) {
    return schema;
  }
  let validator = yup[validationType]();
  validations.forEach((validation) => {
    const { type, value, msg } = validation;
    if (!validator[type]) {
      return;
    }
    validator = validator[type](value, msg);
  });
  schema[id] = validator;
  return schema;
}

Required does work for me. However, I can't figure out how to make other Yup functions work as per my requirements.

What shape is your JSON? I've attached mine above

{
id: "label",
label: "Field Label",
placeholder: "Enter Label",
control: "text",
validationType: "string",
validations: [{type: "required", params: ["Field Label is required."]}],
}

What should the object for yup.array().of(yup.string()) validation look like?

Was this page helpful?
0 / 5 - 0 ratings

Related issues

mikkelwf picture mikkelwf  路  4Comments

RobBednark picture RobBednark  路  3Comments

you-fail-me picture you-fail-me  路  4Comments

the-daniel-rothig picture the-daniel-rothig  路  4Comments

Simmetopia picture Simmetopia  路  4Comments