sinclairzx81 / typebox

Json Schema Type Builder with Static Type Resolution for TypeScript
Other
4.79k stars 153 forks source link

[Q] Is there a way to express "property A is Optional if property B is set" and viceversa? #498

Closed AntonioAngelino closed 1 year ago

AntonioAngelino commented 1 year ago

I need to validate the following JSON objects. Both properties "A" and "B" cannot be optional simultaneously. If "A" is undefined, then "B" must be set to pass the checks (and vice-versa).

{ 
"a": "This is valid",
"c": "test",
"d": 123
}
{ 
"b": "This is valid",
"c": "test",
"d": 123
}
{ 
"a": "This is valid...",
"b": "...too",
"c": "test",
"d": 123
}
{ 
"c": "Invalid!!!",
"d": 123
}

Is there a way of doing it with TypeBox? Thank you.

sinclairzx81 commented 1 year ago

@AntonioAngelino Hi!

TypeBox validators don't support conditional schematics (currently, but it's on my list of things to do), but Ajv does. You can express schematics (as you've described) using JSON schema if/then/else keywords, the following is how you could express such a type in TypeBox (using Ajv to validate)

TypeScript Link Here

import { Type } from '@sinclair/typebox'

const T = Type.Object({                   // this schema must pass
  a: Type.Optional(Type.String()),
  b: Type.Optional(Type.String()),
  c: Type.Optional(Type.String()),
  d: Type.Optional(Type.Number())
}, {
  if: Type.Object({                       // but if this schema passes (allowing c, d only)
    c: Type.Optional(Type.String()),
    d: Type.Optional(Type.Number())
  }, { additionalProperties: false }),
  then: Type.Not(Type.Any()),             // then use this schema (not anything)
})

import Ajv from 'ajv'

const ajv = new Ajv()
console.log(ajv.validate(T, { a: 'This is valid', c: 'test', d: 123 })) // true
console.log(ajv.validate(T, { b: 'This is valid', c: 'test', d: 123 })) // true
console.log(ajv.validate(T, { a: 'This is valid...', b: '...too', c: 'test', d: 123 })) // true
console.log(ajv.validate(T, { c: 'Invalid!!!', d: 123 })) // false

There are likely a number of different ways could express this. You can find additional information on conditional schematics at the following link (just be a bit mindful these kinds of schematics can get very complex)

https://json-schema.org/understanding-json-schema/reference/conditionals.html#id6

Hope that helps! S

sinclairzx81 commented 1 year ago

@AntonioAngelino Hi, might close up this issue.

Will be looking to implement if/then/else in subsequent updates to the Compiler (which are due for an update), may also take a look at dependentRequired as part of that work also, however I don't expect updates to be done before September, so for now, do recommend Ajv for these kinds of conditional schematics.

Also, with the previous example, just note you can also replace Type.Not(Type.Any()) with Type.Never() which is semantically the same (and may read a little clearer)

const T = Type.Object({                   // this schema must pass
  a: Type.Optional(Type.String()),
  b: Type.Optional(Type.String()),
  c: Type.Optional(Type.String()),
  d: Type.Optional(Type.Number())
}, {
  if: Type.Object({                       // but if this schema passes (allowing c, d only)
    c: Type.Optional(Type.String()),
    d: Type.Optional(Type.Number())
  }, { additionalProperties: false }),
  then: Type.Never(),                     // then never
})

If you have any follow up questions, feel free to reply on this thread. All the best! S