jquense / yup

Dead simple Object schema validation
MIT License
22.86k stars 932 forks source link

Cyclic dependency issue #193

Closed GazzaHazza closed 5 years ago

GazzaHazza commented 6 years ago

Hi, I am having a issue with cyclic dependency within a schema. I have looked at this issue, but what was suggested didn't work for me.

yup.object().shape(
  {
    sub_building: yup.string(),
    building_name: yup.string().when('building_number', {
      is: building_number => !building_number,
      then: yup.string().required(),
    }),
    building_number: yup.string().when('building_name', {
      is: building_name => !building_name,
      then: yup.string().required(),
    }),
    street: yup.string().required(),
    town: yup.string().required(),
    county: yup.string().required(),
  },
  ['sub_building', 'building_name', 'building_number', 'street', 'town', 'county']
);

This is my schema. Basically i would like either building_number or building_name to be filled.

Thanks for your help in advance.

davidchase commented 6 years ago

Both ideas from https://github.com/jquense/yup/issues/79#issuecomment-274174656 worked for me

const inst = yup.object({
  location: yup
    .object({
      state: yup.string(),
      county: yup.string()
    })
    .test(
      'is-optional',
      '${path}.state or ${path}.county is required',
      function({ state, county }) {
        return state === '' && county === '' ? false : true
      }
    )
})
// throws ValidationError: location.state or location.county is required
inst.validate({
    location: {
     state: '',
     county: ''
    }
})
// doesn't throw
inst.validate({
    location: {
     state: 'CA',
     county: ''
    }
})

or

const inst2 = yup.object().shape(
  {
    location: yup.object().shape({
      state: yup
        .string()
        .when('county', {
           is: '',
           then: yup.string().required(),
           otherwise: yup.string()
        }),
      county: yup
        .string()
        .when('state', {
           is: '',
           then: yup.string().required(),
           otherwise: yup.string()
        })
    }, ['county', 'state'])
  }
)

I needed either county or state to be filled in

MarcosApostolo commented 6 years ago

Hi, the second solution worked for me. Thank you very much.

web2wire commented 6 years ago

I am having this same issue but both of the solutions seems to suggest that the optional fields needs to be present in their own sub-object, location in the above case.

In my case there is one single level object of user data where the fields in question are mixed in with the rest of the users data. Specifically, to register, a user has to specify an entry in one of the two telephone fields. It's actually a little more complicated than that as certain types of users may have to fill in one of three or more fields but the 'two field' case should be general enough.

The proposed solutions appear to only work if I manually munge the data received so that { name: "John Doe", company: "Test Inc", mobile: "012345678", phone: "" } becomes { name: "John Doe", company: "Test Inc", telephone: { mobile: "012345678", phone: "" } } for the purposes of form validation, and then restructured again for submission. Which seems rather clunky and potentially error prone.

There must be a general solution to the 'one of set of fields is required' problem that doesn't require data to be artificially bent?

davidchase commented 6 years ago

I think it would be helpfully to post your data you are attempting to validate as well as your current validation schema. What I posted is simply the scenario that I had to deal with so I outlined my solution based on previous issues. I don’t think my solution would work for everyone thus if you would like help please post sample data and schema. :)

cenda commented 5 years ago

Hi, could anyone tell me where is issue, or what is correct approach, when you have scenario with 'all or nothing' is valid? I've tried to mimic @davidchase solution with following code, but witout success :(

Thank you for every help

var schema = yup.object().shape({
  address: yup.object().shape({
    city: yup
      .string()
      .when('zip_code', {
           is: val => val.length > 0,
           then: yup.string().required(),
           otherwise: yup.string()
        })
      .when('street', {
           is: val => val.length > 0,
           then: yup.string().required(),
           otherwise: yup.string()
        }),
    zip_code: yup
      .string()
      .when('city', {
           is: val => val.length > 0,
           then: yup.string().required(),
           otherwise: yup.string()
        })
      .when('street', {
           is: val => val.length > 0,
           then: yup.string().required(),
           otherwise: yup.string()
        }),
    street: yup
      .string()
      .when('zip_code', {
           is: val => val.length > 0,
           then: yup.string().required(),
           otherwise: yup.string()
        })
      .when('city', {
           is: val => val.length > 0,
           then: yup.string().required(),
           otherwise: yup.string()
        }),
  }, ['street', 'zip_code', 'city'])
});

schema.isValid({
  address: {
    street: 'Street Name', 
    city: 'City Name',
    zip_code: '12345'
  }
}).then(valid => console.log(valid))
jquense commented 5 years ago

@cenda the second argument to object.shape() is not an array of conflicting fields. its an array of field pairs. e.g. [['street', 'zip_code'], ['zip_code', 'city']] you need to make the list exhaustive

cenda commented 5 years ago

umm, sorry, but I'm afraid I don't understand

jquense commented 5 years ago

https://github.com/jquense/yup/issues/176#issuecomment-369925782

cenda commented 5 years ago

@jquense Thanks!

msakal20 commented 2 years ago

@davidchase Thanks a lot!!! This works for me https://github.com/jquense/yup/issues/193#issuecomment-383712114

NitinGlasier commented 1 year ago

const CompanyProfileValidationSchema = Yup.object({ locations: Yup.array().of( Yup.object().shape( { addressType: Yup.string().when(["address", "city", "country", "pincode", "state"], { is: (address, city, country, pincode, state) => address || city || country || pincode || state, then: Yup.string().required("Please Add FacilityType") }), country: Yup.string().when(["address", "addressType", "city", "pincode", "state"], { is: (address, addressType, city, pincode, state) => address || addressType || city || pincode || state, then: Yup.string().required("Please Add Country") }), city: Yup.string().when(["address", "addressType", "country", "pincode", "state"], { is: (address, addressType, country, pincode, state) => address || addressType || country||pincode||state, then: Yup.string().required("Please Add City") }), state: Yup.string().when(["address", "addressType", "city", "country", "pincode"], { is: (address, addressType, city, country, pincode) => address || addressType || city || country || pincode, then: Yup.string().required("Please Add State") }), address: Yup.string().when(["addressType", "city", "country", "pincode", "state"], { is: (addressType, city, country, pincode, state) => addressType || city || country || pincode || state, then: Yup.string().required("Please Add Address") }), pincode: Yup.string().when(["address", "addressType", "city", "country", "state"], { is: (address, addressType, city, country, state) => address || addressType || city || country || state, then: Yup.string().required("Please Add Pincode") }) }, [ ["addressType", "country"], ["addressType", "city"], ["addressType", "pincode"], ["addressType", "address"], ["addressType", "state"], ["country", "city"], ["country", "pincode"], ["country", "address"], ["country", "state"], ["address", "city"], ["address", "pincode"], ["address", "state"], ["state", "city"], ["state", "pincode"], ["city", "pincode"] ] ) ) });

it's a right code for cyclic dependency if not than please help how to solve this

Junaid300 commented 11 months ago

I faced same issue. But i had to pass same dependency twice and it worked for me. I am not sure what is the reason for this. Can someone example?

yup.object().shape(
  {

    building_number: yup.string().when('building_number', {
      is: building_name => Boolean(building_name),
      then: yup.string().required(),
    }),
    street: yup.string().required(),
    town: yup.string().required(),
    county: yup.string().required(),
  },
  [['building_number', 'building_number']]
);