Используйте условные операторы в схеме json на основе другого объекта схемы - PullRequest
3 голосов
/ 18 марта 2019

У меня есть объект json, такой как:

{
  "session": {
    "session_id": "A",
    "start_timestamp": 1535619633301
  },
  "sdk": {
    "name": "android",
    "version": "21"
  }
}

sdk name может быть android or ios. И session_id основан на name field в sdk json. Я написал json schema, используя условное утверждение (используя черновик 7) следующим образом:

Но это работает неожиданным образом:

{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "$ref": "#/definitions/Base",
  "definitions": {
    "Base": {
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "session": {
          "$ref": "#/definitions/Session"
        },
        "sdk": {
          "$ref": "#/definitions/SDK"
        }
      },
      "title": "Base"
    },
    "Session": {
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "start_timestamp": {
          "type": "integer",
          "minimum": 0
        },
        "session_id": {
          "type": "string",
          "if": {
            "SDK": {
              "properties": {
                "name": {
                  "enum": "ios"
                }
              }
            }
          },
          "then": {
            "pattern": "A"
          },
          "else": {
            "pattern": "B"
          }
        }
      },
      "required": [
        "session_id",
        "start_timestamp"
      ],
      "title": "Session"
    },
    "SDK": {
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "version": {
          "type": "string"
        },
        "name": {
          "type": "string",
          "enum": [
            "ios",
            "android"
          ]
        }
      },
      "required": [
        "name",
        "version"
      ],
      "title": "SDK"
    }
  }
}

Итак, следующие JSON проходит:

{
      "session": {
        "session_id": "A",
        "start_timestamp": 1535619633301
      },
      "sdk": {
        "name": "ios",
        "version": "21"
      }
    }

Но это не удалось:

{
      "session": {
        "session_id": "B",
        "start_timestamp": 1535619633301
      },
      "sdk": {
        "name": "android",
        "version": "21"
      }
    }

может кто-нибудь объяснить вам? .. Даже это проходит:

{
      "session": {
        "session_id": "A",
        "start_timestamp": 1535619633301
      },
      "sdk": {
        "name": "android",
        "version": "21"
      }
    }

Ответы [ 3 ]

3 голосов
/ 19 марта 2019

Я думаю, что у вас такая же проблема, как в этот вопрос .

@ Relequestual прав в том, что вам нужно ключевое слово properties вокруг вашей выноски SDK. Но для того, что вы хотите сделать, вам нужно реорганизоваться.

Подсхемы работают только на своем уровне в экземпляре, а не в корне.

Рассмотрим эту схему для простого экземпляра объекта JSON, содержащего one и свойство two:

{
  "properties": {
    "one": {
      "enum": ["yes", "no", "maybe"]
    },
    "two": {
      "if": {
        "properties": {
          "one": {"const": "yes"}
        }
      },
      "then": {
        ...       // do some assertions on the two property here
      },
      "else": {
        ...
      }
    }
  }
}

Ключевое слово if в свойстве two может рассматривать только часть экземпляра в свойстве two (т.е. значение two). Он не смотрит на корень экземпляра, поэтому вообще не видит свойства one.

Чтобы подсхема в подсхеме свойства two могла видеть свойство one в экземпляре, необходимо переместить if за пределы ключевого слова properties.

{
  "if": {
    "properties": {
      "one": {"const" : "yes"}
    }
  },
  "then": {
    ...       // do some assertions on the two property here
  },
  "else": {
    ...       // assert two here, or have another if/then/else structure to test the one property some more
  }
}

Для двух возможных значений one это довольно хорошо. Даже три возможных значения неплохо. Однако с увеличением возможных значений one увеличивается и вложенность if с, что может сделать вашу схему ужасной для чтения (и, возможно, замедлить проверку).

Вместо использования конструкции if / then / else я предлагаю использовать anyOf или oneOf, где каждая подсхема представляет действительное состояние для экземпляра, учитывая различные значения one .

{
  "oneOf": [
    {
      "properties": {
        "one": {"const": "yes"},
        "two": ...         // do some assertions on the two property here
      }
    },
    {
      "properties": {
        "one": {"const": "no"},
        "two": ...         // do some assertions on the two property here
      }
    },
    {
      "properties": {
        "one": {"const": "maybe"},
        "two": ...         // do some assertions on the two property here
      }
    }
  ]
}

На мой взгляд, это намного чище.

Надеюсь, это объяснение поможет вам восстановить схему, чтобы эти другие экземпляры могли пройти.

2 голосов
/ 19 марта 2019

Вы должны переместить ваше условие на достаточно высокий уровень, чтобы иметь возможность ссылаться на все свойства, на которые оно должно ссылаться. В данном случае это схема /definitions/Base. Тогда вам просто нужно правильно написать свои схемы, как объяснил Relequestual.

{
  "$ref": "#/definitions/Base",
  "definitions": {
    "Base": {
      "type": "object",
      "properties": {
        "session": { "$ref": "#/definitions/Session" },
        "sdk": { "$ref": "#/definitions/SDK" }
      },
      "allOf": [
        {
          "if": {
            "properties": {
              "sdk": {
                "properties": {
                  "name": { "const": "ios" }
                }
              }
            },
            "required": ["sdk"]
          },
          "then": {
            "properties": {
              "session": {
                "properties": {
                  "session_id": { "pattern": "A" }
                }
              }
            }
          },
          "else": {
            "properties": {
              "session": {
                "properties": {
                  "session_id": { "pattern": "B" }
                }
              }
            }
          }
        }
      ]
    },
  ...
}
0 голосов
/ 18 марта 2019

Значение if должно быть схемой JSON.Если бы вы взяли строки https://gist.github.com/Relequestual/f225c34f6becba09a2bcaa66205f47f3#file-schema-json-L29-L35 (29-35) и использовали бы их в качестве схемы JSON сами по себе, вы бы не наложили никаких ограничений проверки, потому что на верхнем уровне объекта нет ключевых слов схемы JSON.

{
  "SDK": {
    "properties": {
      "name": {
        "enum": "ios"
      }
    }
  }
}

Это разрешено в спецификации, поскольку люди могут захотеть расширить функциональность схемы JSON, добавив свои собственные ключевые слова.Так что это «правильная» схема JSON, но на самом деле ничего не делает.

Вам нужно добавить properties в схему, чтобы она имела смысл.

{
  "properties": {
    "SDK": {
      "properties": {
        "name": {
          "const": "ios"
        }
      }
    }
  }
}

Дополнительно, enum должен быть массивом.Если у вас есть только один предмет, вы можете использовать const.

...