7

I have a json object like:

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

The sdk name can either be android or ios. And the session_id is based on name field in sdk json. I have written a json schema using conditional statement (Using draft 7) as follows:

But it works in an unexpected manner:

{
  "$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"
    }
  }
}

So the following JSON Passes:

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

But this fails:

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

can someone explain y?.. Even this passes:

{
      "session": {
        "session_id": "A",
        "start_timestamp": 1535619633301
      },
      "sdk": {
        "name": "android",
        "version": "21"
      }
    }
user2565192
  • 694
  • 1
  • 8
  • 19
  • The `if` keyword in JSON Schema, the value must be a JSON Schema. Currently, if you were to take the value of `if` in your schema, and use that as a JSON Schema, what do you expect to happen? Do you see the issue? =] (Here to help further if not, but I feel this is a simple missunderstanding.) – Relequestual Mar 18 '19 at 16:12
  • @Relequestual Can u please elaborate more. I don't see the issue – user2565192 Mar 18 '19 at 16:39
  • Sure. I've copied the schema to a gist so I can reference line numbers: https://gist.github.com/Relequestual/f225c34f6becba09a2bcaa66205f47f3#file-schema-json-L29-L35 – Relequestual Mar 18 '19 at 16:41
  • I get what you are saying. But the syntax for the if statement i have validated from here: https://json-schema.org/understanding-json-schema/reference/conditionals.html . Is the way i am referencing SDK name field wrong? – user2565192 Mar 18 '19 at 16:45

3 Answers3

10

I think you're having a similar problem as in this question.

@Relequestual is right in that you need the properties keyword around your SDK callout. But for what you want to do, you need to reorganize.

Subschemas only operate on their level in the instance, not at the root.

Consider this schema for a simple JSON object instance containing a one and a two property:

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

The if keyword under the two property can only consider the portion of the instance under the two property (i.e. two's value). It's not looking at the root of the instance, so it can't see the one property at all.

To make it so that the subschema under the two property subschema can see the one property in the instance, you have to move the if outside of the properties keyword.

{
  "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
  }
}

For two possible values of one, this is pretty good. Even three possible values isn't bad. However, as the possible values of one increases, so does the nesting of ifs, which can make your schema horrible to read (and possibly make validation slower).

Instead of using the if/then/else construct, I suggest using an anyOf or oneOf where each subschema represents a valid state for the instance, given the varying values of 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
      }
    }
  ]
}

This is much cleaner in my opinion.

Hopefully that explanation helps you reconstruct your schema to allow those other instances to pass.

gregsdennis
  • 7,218
  • 3
  • 38
  • 71
4

You have to move your conditional to a high enough level to be able to reference all of the the properties it needs to reference. In this case, that's the /definitions/Base schema. Then you just need to write your schemas properly as Relequestual explained.

{
  "$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" }
                }
              }
            }
          }
        }
      ]
    },
  ...
}
Jason Desrosiers
  • 22,479
  • 5
  • 47
  • 53
0

The value of if must be a JSON Schema. If you were to take lines https://gist.github.com/Relequestual/f225c34f6becba09a2bcaa66205f47f3#file-schema-json-L29-L35 (29-35) and use that as a JSON Schema by itself, you would impose no validation constraints, because there are no JSON Schema key words at the top level of the object.

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

This is allowed in the specification, because people may want to extend the functionality of JSON Schema by adding their own key words. So it's "valid" JSON Schema, but doesn't actually DO anything.

You Need to add properties to the schema for it to make sense.

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

Additionally, enum must be an array. When you only have a single item, you may use const.

Relequestual
  • 11,631
  • 6
  • 47
  • 83
  • Thanks but i tried the schema as you suggested. https://api.myjson.com/bins/1crf16 . It behaves in the same manner. – user2565192 Mar 18 '19 at 16:57