Home > Enterprise >  JSON Schema - if then statement is always evaluated even when "if" isn't true
JSON Schema - if then statement is always evaluated even when "if" isn't true

Time:10-15

I have a JSON Schema, in the schema - when the property type is equal to menu, then the property default should be an integer or a string called "default". The problem is, the schema is throwing a warning / error that the default property should be an integer, even when type is not "menu".

(There are properties in the JSON named "type" and "default", not to be confused with the JSON schema type & default keys).

The Schema cut down to the relevant properties:

{
    "$schema": "http://json-schema.org/draft-07/schema#",
    "type": "array",
    "title": "HubSpot Module",
    "items": {
      "type": "object",
      "properties": {
        "type": {
          "type": "string",
          "description": "The type of field, see [field types](https://developers.hubspot.com/en/docs/cms/building-blocks/module-theme-fields#field-types) for documentation on all field types."
        },
        "default": {
          "type": ["string", "integer", "array", "boolean", "null", "number", "object"],
          "description": "Default value for the field.",
          "if": {
            "properties": {
              "type": {
                "const": "menu"
              }
            }
          },
          "then": {
            "anyOf": [
              {
                "type": "integer"
              },
              {
                "type": "string",
                "enum": [
                  "default"
                ]
              }
            ],
            "description": "The menu ID for the menu. The default value of null, defaults to the default menu under navigation.",
            "default": "null"
          },
          "else": {
            "anyOf": [
              {
                "type": ["string", "integer", "array", "boolean", "null", "number", "object"]
              }
            ]
          }
        }
      },
      "required": [
        "name",
        "label",
        "type"
      ]
    }
  }

It works when those conditions are met, e.g.

[
  {
    "label": "Primary menu field",
    "name": "primary_menu_field",
    "type": "menu",
    "default": 123
  }
]

[
  {
    "label": "Primary menu field",
    "name": "primary_menu_field",
    "type": "menu",
    "default": "default"
  }
]

neither of these throw warnings. However the following throws the warning: Incorrect type. Expected "integer".

[
  {
    "name": "boolean_field",
    "label": "Boolean field",
    "required": false,
    "locked": false,
    "type": "boolean",
    "inline_help_text": "",
    "help_text": "",
    "default": false 
  }
]

Despite the type property not being named "menu". Hovering over the "default" property VSCode does give me the definition I have for it.

It seems like the if statement is always being evaluated as true - when it should only be evaluated if the value of property type is menu, is there something I'm missing?

CodePudding user response:

Your if isn't working as expected because it's in the wrong place and it's insufficiently constrained. You have the if in the "default" property schema which means the if schema will apply to the value of the "default" property. So, if "default" is 123, then the if schema,

{
  "properties": {
    "type": { "const": "menu" }
  }
}

will be evaluated against 123. Since 123 is a number, the properties keyword is ignored and the schema will pass validation and the then schema will apply. This is what I meant by if being under constrained. When using if you need to check for missing data or data of the wrong type.

{
  "type": "object",
  "properties": {
    "type": { "const": "menu" }
  },
  "required": ["type"]
}

Now you'll get an error message when validating 123 against this schema and hopefully figure out that your if/then is in the wrong place. The if needs to apply to the object, you need to move it up to that level in the schema. That will make your if evaluated as expected. Don't forget to update the then as well since you moved the schema. I also needs to be defined at the object level rather than the property level.

{
  "$schema": "http://json-schema.org/draft-07/schema#",

  "type": "array",
  "items": {
    "type": "object",
    "properties": {
      "type": { "type": "string" }
    },
    "required": ["name", "label", "type"],

    "if": {
      "type": "object",
      "properties": {
        "type": { "const": "menu" }
      },
      "required": ["type"]
    },
    "then": {
      "properties": {
        "default": {
          "anyOf": [
            { "type": "integer" },
            { "const": "default" }
          ]
        }
      }
    }
  }
}

CodePudding user response:

Your if/then/else is under the default property, so it's looking for an object under default, rather than one level up where you want it.

Also, the properties check in the if condition will always be true if the property doesn't actually exist, so to ensure it does, you can add a "required": <propertyname> alongside it.

(Orthogonally, your type check doesn't really do anything since you include all valid types in the list. You might as well just leave it out to indicate "this can be any type".)

  • Related