VorpalSword
VorpalSword

Reputation: 1293

How do I represent polymorphic objects in JSON schema?

I'm trying to create a schema for a JSON object that varies its schema depending on the value of one of its properties: type.

Like this:

{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "type": "object",
  "properties": {
    "type": {
      "type": "string",
      "enum": ["INT", "PERCENT"]
    }
  },
  "required": ["type"],
  "allOf": [
    {
      "if": {
        "properties": {"type": {"const": "INT"}}
      },
      "then": {
        "properties": {
          "value": {"type": "number", "multipleOf": 1}
        },
        "required": ["value"],
        "additionalProperties": false
      }
    },
    {
      "if": {
        "properties": {"type": {"const": "PERCENT"}}
      },
      "then": {
        "properties": {
          "value": {"type": "number"},
          "min": {"type": "number"},
          "max": {"type": "number"}
        },
        "required": ["value", "min", "max"],
        "additionalProperties": false
      }
    }
  ]
}

But I'm getting all sorts of misbehaviour from the various validators I'm trying.

Some examples with problems noted after the //:

{
    "type": "PERCENT", // property type has not been defined(?!)
    "value": 0.0,
    "min": 10,
    "max": 25
}

{
    "type": "INT", // no errors allowed, and value 
    "value": 0.1,  // should've been flagged as not multiple of 1
    "min": 10,  // should've been flagged as disallowed additional property
    "max": 25 // same as above
}

Here's the validator I'm trying

Thanks in advance for your help!

Upvotes: 1

Views: 1460

Answers (2)

Jason Desrosiers
Jason Desrosiers

Reputation: 24439

It seems that you've figured out that the problem is that addtionalProperties only considers the properties defined in the sub-schema it's defined in. That's why you have to include the "type" property in your then. You have a few options that are an improvement over the solution you posted that you weren't happy with.

Option 1: unevaluatedProperties

Draft 2019-09 introduced the unevaluatedProperties keyword. If you use this at the top level of your schema and don't use addtionalProperties anywhere, you will get the behavior you expected from additionalProperties. I see you are using draft-07, so you have to upgrade in order to use this solution.

Option 2: Boolean schemas

The solution you came up with is to re-define the "type" property in the then schemas. additionalProperties just needs "type" to be declared, you don't need to include the schema for it again. You can use true or the empty schema {} instead.

Option 3: propertyNames

Instead of additionalProperties you can use propertyNames. This allows you to declare a list of which property names are allowed in the object rather than hacking additionalProperties with true or {} to figure out the allowed property names the way you want it to.

Upvotes: 1

VorpalSword
VorpalSword

Reputation: 1293

well I found a way to get what I wanted, but it doesn't mean I like it. Having to redefine "type" doesn't seem right to me.

{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "type": "object",
  "properties": {
    "type": {
      "type": "string",
      "enum": ["INT", "PERCENT"]
    }
  },
  "required": ["type"],
  "allOf": [
    {
      "if": {
        "properties": {"type": {"const": "INT"}}
      },
      "then": {
        "properties": {
          "value": {"type": "number", "multipleOf": 1},
          "type": {"type": "string"}
        },
        "required": ["value", "type"],
        "additionalProperties": false
      }
    },
    {
      "if": {
        "properties": {"type": {"const": "PERCENT"}}
      },
      "then": {
        "properties": {
          "value": {"type": "number"},
          "min": {"type": "number"},
          "max": {"type": "number"},
          "type": {"type": "string"}
        },
        "required": ["value", "min", "max", "type"],
        "additionalProperties": false
      }
    }
  ]
}

tests...

This validates:

{
    "type": "PERCENT", 
    "value": 0,
    "min": 10,
    "max": 20
}

so does this:

{
    "type": "INT", 
    "value": 0
}

this errors in the way I want:

{
    "type": "INT", 
    "value": 0.1,  // not a multiple of 1
    "min": 4, //  not expected
    "max": 5 // not expected
}

and so does this:

{
    "type": "PERCENT", 
    "value": 0.1,
    "max": 5   // min is a required property
}

Upvotes: 0

Related Questions