JSON Schema for Events

Viewed 46

Just getting started with JSON Schema. I understood the part where one can define a schema and validate a JSON against this schema.

What is not clear is how I can define different schema for different JSON objects (say Events) and still validate my incoming JSON against the appropriate schema?

Consider the following 2 events:


{
    "type": "A",
    "a": 1
}

{
    "type": "B",
    "b": 1,
    "b1": 2
}

For event type A, there is one property a where as for event type B there are two other properties b and b1. So if I have 2 separate schema for each of the types A and B, am I supposed to check the type of the incoming event JSON and then pick the correct schema to validate against? Or is there any other simpler way?

The reason for this ask is, there are situations where I have multiple JSON objects without any identifying type property. I am wondering if there is any way to see my JSON is valid against any of the schemas.

2 Answers

The most efficient way would be to check the type property and then validate the object against the corresponding schema.
However, if you don't have any hint about the event type, all you can do is brute-force the schema by checking the JSON object against every possible schema you defined until you find the right one.

If you care about runtime performance, you could create an apposite parsing table (may also be called a tree) to find the correct schema without doing a completely blind brute force. Such a table would help you narrow the possible schemas by cutting out those schemas that are obviously not compatible with the given JSON object.
For example, you start with n possible schemas to validate against. The table will then have n final points (tree leaves, if you want) each indicating a specific schema.
The table/tree determines the correct schema by checking if the given JSON object has certain properties that belong to the schemas you defined.

An example of this table/tree would look like this: (note that I use a JSON-like structure to stay language-neutral)

{
    "wings": {
        "beak": bird_schema,
        "teeth": bat_schema
    },
    "4 legs": {
        "herbivore": {
            "big ears": elephant_schema,
            "long neck": giraffe_schema
        },
        "carnivore": {
            "stripes": tiger,
            "mane": lion
        }
    }
}

Note that the most common object properties are placed at the top of the tree and, as you get towards the leaves, the properties get more specific. You should be careful not to have overlapping branches in your tree/table, though.

In code, you can do something like this:

table = ... // the parting tree/table

function find_schema(object: JSON) -> Schema:
    possible_props = table

    while true:

        for property in possible_props:
            if object has property:
                if possible_props[property] is List:
                    // Narrow down the possibilities
                    possible_props = possible_props[property]
                    continue the outer while loop
                else if possible_props[property] is Schema:
                    // Found the correct schema, return it
                    return possible_props[property]
                   
        // If this line is reached, there was no matching schema for the JSON object
        throw an error or return null

This pseudo-code example might not be 100% correct (I haven't tested it), but should give you the general idea of how you would approach validating a JSON object against a list of schemas using a dedicated tree/table.

I hope this helps.

This can be done with pure JSON Schema by using a oneOf with const (draft 6 and later).

{
  "type": "object",
  "required": [ "type" ],
  "oneOf": [
    {
      "properties": {
        "type": { "const" : "A" }
      },
      "required": [ "a" ]
    },
    {
      "properties": {
        "type": { "const" : "B" }
      },
      "required": [ "b", "b1" ]
    }
  ]
}

This is the traditional way to do something like you want (and I believe it to be more extensible). I prefer this way.

If you're using draft 7 or later, you can use the if/then/else keywords, which is considered the more modern way.

{
  "type": "object",
  "required": [ "type" ],
  "oneOf": [
    {
      "if": {
        "properties": {
          "type": { "const" : "A" }
        }
      },
      "then": {
        "required": [ "a" ]
      }
    },
    {
      "if": {
        "properties": {
          "type": { "const" : "B" }
        }
      },
      "then": {
        "required": [ "b", "b1" ]
      }
    }
  ]
}

The idea is that it breaks up the condition from the requirement and makes things more readable.

These are functionally equivalent. There's just a user preference for readability.

Related