Atributo jsonSchema condicionalmente exigido

96

Em jsonSchema você pode indicar se os campos definidos são obrigatórios ou não usando o requiredatributo:

{
    "$schema": "http://json-schema.org/draft-04/schema#",
    "type": "object",
    "properties": {
        "header": {
            "type": "object",
            "properties": {
                "messageName": {
                    "type": "string"
                },
                "messageVersion": {
                    "type": "string"
                }
            },
            "required": [
                "messageName",
                "messageVersion"
            ]
        }
    },
    "required": [
        "header"
    ]
}

Em certos casos, gostaria que o messageVersioncampo não fosse obrigatório. Existe alguma maneira de tornar a obrigatoriedade deste campo condicional?

tom redfern
fonte
Sim, deve ser possível. Quais informações nos dados acionariam a obrigatoriedade?
jruizaranguren
@SarveswaranMeenakshiSundaram - Não sei, só usei a v4 do esquema json
tom redfern
Isso é possível na versão 3?
Sarvesh
@SarveswaranMeenakshiSundaram - Não sei. Experimente e diga-nos, por favor!
tom redfern

Respostas:

261

Dependendo da sua situação, existem algumas abordagens diferentes. Posso pensar em quatro maneiras diferentes de exigir um campo condicionalmente.

Dependências

A dependenciespalavra-chave é uma variação condicional da requiredpalavra - chave. Propriedade Foreach em dependencies, se a propriedade estiver presente no JSON sendo validado, o esquema associado a essa chave também deve ser válido. Se a propriedade "foo" estiver presente, a propriedade "bar" é necessária

{
  "type": "object",
  "properties": {
    "foo": { "type": "string" },
    "bar": { "type": "string" }
  },
  "dependencies": {
    "foo": { "required": ["bar"] }
  }
}

Também existe uma forma abreviada se o esquema contiver apenas a requiredpalavra - chave.

{
  "type": "object",
  "properties": {
    "foo": { "type": "string" },
    "bar": { "type": "string" }
  },
  "dependencies": {
    "foo": ["bar"]
  }
}

Implicação

Se sua condição depende do valor de um campo, você pode usar um conceito de lógica booleana chamado implicação. "A implica B" significa efetivamente, se A é verdadeiro, então B também deve ser verdadeiro. A implicação também pode ser expressa como "! A ou B". A propriedade "foo" não é igual a "bar" ou a propriedade "bar" é obrigatória . Ou, em outras palavras: se a propriedade "foo" for igual a "bar", então a propriedade "bar" é necessária

{
  "type": "object",
  "properties": {
    "foo": { "type": "string" },
    "bar": { "type": "string" }
  },
  "anyOf": [
    {
      "not": {
        "properties": {
          "foo": { "const": "bar" }
        },
        "required": ["foo"]
      }
    },
    { "required": ["bar"] }
  ]
}

Se "foo" não for igual a "bar", as #/anyOf/0correspondências e a validação são bem-sucedidas. Se "foo" for igual a "bar", #/anyOf/0falha e #/anyOf/1deve ser válido para que a anyOfvalidação seja bem-sucedida.

Enum

Se sua condicional for baseada em um enum, é um pouco mais direto. "foo" pode ser "bar" ou "baz". Se "foo" for igual a "bar", então "bar" é necessário. Se "foo" for igual a "baz", então "baz" é necessário.

{
  "type": "object",
  "properties": {
    "foo": { "enum": ["bar", "baz"] },
    "bar": { "type": "string" },
    "baz": { "type": "string" }
  },
  "anyOf": [
    {
      "properties": {
        "foo": { "const": "bar" }
      },
      "required": ["bar"]
    },
    {
      "properties": {
        "foo": { "const": "baz" }
      },
      "required": ["baz"]
    }
  ]
}

If-Then-Else

A adição relativamente nova JSON Schema (draft-07) adiciona os if, thene elsepalavras-chave. Se a propriedade "foo" for igual a "bar", então a propriedade "bar" é necessária

{
  "type": "object",
  "properties": {
    "foo": { "type": "string" },
    "bar": { "type": "string" }
  },
  "if": {
    "properties": {
      "foo": { "const": "bar" }
    },
    "required": ["foo"]
  },
  "then": { "required": ["bar"] }
}

EDITAR 23/12/2017: Seção de implicação atualizada e seção If-Then-Else adicionada.

EDITAR 06/04/2018: Correção de bug para If-Then-Else e atualização de singleton enums para uso const.

Jason Desrosiers
fonte
7
@scubbo Não sou fã das if-then-elsepalavras - chave e me recuso a usá-las. Mas, se você optar por usá-lo, sugiro sempre envolvê-los em um allOfque contenha apenas essas três palavras-chave. { ...other_keywords..., "allOf": [{ "if": ..., "then": ..., "else": ... }], ...more_keywords... }
Jason Desrosiers,
2
@Jason Por que não um fã de if...? Acho que uma breve opinião sobre isso em sua resposta seria inteiramente justificada. Ou é uma longa história?
Clay Bridges de
6
@ClayBridges A seção de comentários não é o lugar certo para essa discussão, mas aqui está a versão resumida. Como regra geral, as palavras-chave do esquema JSON são sem estado. Nenhuma informação diferente do valor da palavra-chave pode ser usada para validar a instância. if, thenE elseviolar esta regra, porque eles dependem uns dos outros.
Jason Desrosiers
3
@GGirard, este é o melhor tratamento do uso desses padrões no esquema JSON que conheço. As operações booleanas são documentadas oficialmente, mas o resto é apenas matemática. allOf== AND, anyOf== OR, oneOf== XOR e not== NOT. Você pode pesquisar "álgebra booleana" no Google para obter mais recursos sobre matemática (como implicação).
Jason Desrosiers
2
@AlexeyShrub Há algum tempo que desejo escrever sobre isso, mas tenho me distraído com outras coisas. Sou fã da ideia de uma condicional. Facilita o entendimento das pessoas. Minha objeção é a forma como foi definido como três palavras-chave com estado separadas (veja o comentário anterior). Ter palavras-chave que violam as propriedades arquitetônicas que outras palavras-chave seguem torna os validadores de esquema JSON mais difíceis de implementar e menos eficientes. Se as condicionais fossem definidas de uma maneira diferente sem estado, eu não teria objeções.
Jason Desrosiers