Validating array of different object shapes in yup
Asked Answered
P

5

10

I am using formik for form validation and came across some problems in array validation. here is my form structure

{
 flow: [
  { text: "hello"
  },
  { input: "world"
  },
  { buttons: [
       'hi',
       'hello'
     ]
  }
 ]
}

I have to create validation schema for this. So the array may contain any of these objects.

I tried this,

export const validationSchema = yup.object().shape({
  flow: yup.array().of(
      yup.mixed().oneOf([
        {
          text: yup.string().required('Enter text'),
        },
        {
          buttons: yup.array().of(yup.string().required('Enter button title')),
        },
        {
          input: yup.string(),
        }
      ])
  ),
});

But am getting the following as formik error :

flow:[

"flow[0] must be one of the following values: [object Object], [object Object]",
"flow[1] must be one of the following values: [object Object], [object Object]"

]

How to solve this?

Philipson answered 4/10, 2019 at 14:10 Comment(1)
Hello, did you managed to solve this? I am facing a similar problem and I saw polymorphism must be solved with yup lazy but I do not manage to make it work. Thank you in advance and regardsThermae
B
3
import { array, object, string, lazy } from 'yup';    

const differentObjectsArraySchema = array().of(lazy((item) => {
    const { type } = item;

    // any other condition
    if (type === 'text') {
        return object({
            text: string(),
        });
    }

    if (type === 'buttons') {
        return object({
            buttons: array(),
        });
    }

    if (type === 'input') {
        return object({
            input: string(),
        });
    }
}));

source: https://github.com/jquense/yup#yuplazyvalue-any--schema-lazy

Billy answered 26/3, 2021 at 13:25 Comment(0)
D
0

Maybe new custom schema type is a solution, but what about array schema with custom test function (with anyof logic)? In example below is just simple validity checking. https://codesandbox.io/s/yup-anyof-array-w0cww

Demonolater answered 3/3, 2020 at 16:51 Comment(0)
I
0

I couldn't achieve this feature using Yup, I switched to joi and rewrite my two apps(it was very easy because the libraries have a lot of similarity between them) that I used Yup with them, joi support this feature out of the box along with other amazing features.

Introductory answered 3/3, 2020 at 18:13 Comment(0)
H
0

Here's an adaptation cb109 example that runs with runkit:

var yup = require("yup")

yup.addMethod(yup.array, 'oneOfSchemas', function oneOfSchemas(
    schemas,
    message,
) {
    return this.test(
        'one-of-schemas-exact',
        message || 'Not all items in ${path} match one of the allowed schemas',
        (items) =>
            items.every(item => schemas.some(schema => schema.isValidSync(item, { strict: true }))),
    );
});

const validationSchema = yup.object().shape({
  flow: yup.array().oneOfSchemas([
        yup.object({
          text: yup.string().required('Enter text'),
        }),
        yup.object({
          buttons: yup.array().of(yup.string().required('Enter button title')).required(),
        }),
        yup.object({
          input: yup.string().required(),
        })
      ])
});

const passes = validationSchema.validateSync({
 flow: [
  { text: "hello"
  },
  { input: "world"
  },
  { buttons: [
       'hi',
       'hello'
     ]
  }
 ]
})
console.log(passes);

// Fails
validationSchema.validateSync({
 flow: [
  { bad_input: "hello"
  },
  { input: "world"
  },
  { buttons: [
       'hi',
       'hello'
     ]
  }
 ]
})

Importand gotcha: if you skip .required() the objects will always match and the test will always return false. I.e. make sure that the objects have unique patterns and don't overlap.

For a TypeScript implementation it works poorly. I've managed to adapt the original .d.ts with something like this:

import { AnyObject, ObjectSchema } from 'yup';

// Not exported from yup
type Flags = 's' | 'd' | '';

declare module 'yup' {
  export interface ArraySchema<
    TIn extends any[] | null | undefined,
    TContext,
    TDefault = undefined,
    TFlags extends Flags = ''
  > {
    /**
     * Allows you to define mutliple disparate types which should
     * be considered valid within a single array. You can tell the method
     * what types of schemas to expect by passing the schema types:
     *
     * ```
     * // Array of object schemas
     * yup.array().oneOfSchemas<yup.ObjectSchema>([
     *     yup.object().shape({ ... })
     * ]);
     *
     * // Array of object or string schemas
     * yup.array().oneOfSchemas<yup.ObjectSchema | yup.StringSchema>([
     *     yup.object().shape({ ... }),
     *     yup.string()
     * ]);
     * ```
     *
     * @param schemas A list of yup schema definitions
     * @param message The message to display when a schema is invalid
     */
    oneOfSchemas<TIn1 extends AnyObject, TIn2 extends AnyObject>(
      schemas: [ObjectSchema<TIn1, TContext>, ObjectSchema<TIn2, TContext>],
      message?: string
    ): ArraySchema<(TIn1 | TIn2)[], TContext, TFlags>;
    oneOfSchemas<
      TIn1 extends AnyObject,
      TIn2 extends AnyObject,
      TIn3 extends AnyObject
    >(
      schemas: [
        ObjectSchema<TIn1, TContext>,
        ObjectSchema<TIn2, TContext>,
        ObjectSchema<TIn3, TContext>
      ],
      message?: string
    ): ArraySchema<(TIn1 | TIn2 | TIn3)[], TContext, TFlags>;
    oneOfSchemas<
      TIn1 extends AnyObject,
      TIn2 extends AnyObject,
      TIn3 extends AnyObject,
      TIn4 extends AnyObject
    >(
      schemas: [
        ObjectSchema<TIn1, TContext>,
        ObjectSchema<TIn2, TContext>,
        ObjectSchema<TIn3, TContext>,
        ObjectSchema<TIn4, TContext>
      ],
      message?: string
    ): ArraySchema<(TIn1 | TIn2 | TIn3 | TIn4)[], TContext, TFlags>;
    // Add more variants as needed
  }
}

Anyone with a nicer solution is most welcome to contribute!

The actual definition is also not the prettiest:

/* eslint-disable no-template-curly-in-string */
import { addMethod, array, MixedSchema } from 'yup';

addMethod(
  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
  // @ts-ignore
  array,
  'oneOfSchemas',
  function oneOfSchemas(schemas: MixedSchema[], message?: string) {
    return this.test(
      'one-of-schemas',
      message || 'Not all items in ${path} match one of the allowed schemas',
      (items: unknown[]) =>
        items.every((item) =>
          schemas.some((schema) => schema.isValidSync(item, { strict: true }))
        )
    );
  }
);
Husha answered 4/11, 2022 at 20:48 Comment(0)
A
-1

If you look at the function signature for the oneOf method:

mixed.oneOf(arrayOfValues: Array<any>, message?: string | function): Schema

The first parameter is an array of any, so any value will be valid within the array in the first parameter. It's the second message argument that can only be a string/function.

That being said, your code does look correct, so I'm not sure why you'd be receiving any errors. Maybe try adding a message argument and see if that will placate things. What is the exact error you're receiving, if there is one?

Apogeotropism answered 4/10, 2019 at 14:25 Comment(3)
the validation is not working. am not getting any error messagePhilipson
Try creating an array of Yup.object.shape({ ...values }).Apogeotropism
Did you test this array of Yup.objects? Accordingly to the docs, it doesn't accept yup types, only actual values, so it will check if the value received is equal to the yup.object (no validation).Manicure

© 2022 - 2024 — McMap. All rights reserved.