'any way to make a json schema extend from a allOf?

I am quite new to json schema's and I'm not finding a way to do this.

We are being forced to use draft-4 as we want to use Visual Studio to edit these

We have a json object that has essentially two parts

Part 1

  • a. tableId (string)

    or

  • b. Schema (string) and a TableName (string)

Part 2

  • a. ObjectA

    or

  • b. ObjectB

Is there any elegent way to express this in json schema or am I going to have to declare each Object and type by name and create 4 seperate schemas referring to each piece and then essentially have an verbosely expanded version of a oneOf that is essentially "oneof":[{aa},{ab},{ba},{bb}]


UPDATED

I've tried the suggestions but I can't seem to get this to work

Attempt 1. allOf\oneOf

{
    "$schema": "http://json-schema.org/draft-04/schema",
    "allOf": [
        {
            "oneOf": [
                {
                    "$ref": "#/definitions/directTableReference"
                },
                {
                    "$ref": "#/definitions/tableIdObject"
                }
            ]
        },
        {
            "oneOf": [
                {
                    "$ref": "#/definitions/ObjectA"
                },
                {
                    "$ref": "#/definitions/ObjectB"
                }
            ]
        }
    ],
    "definitions": {
        "directTableReference": {
            "type": "object",
            "title": "schema directTableReference",
            "properties": {
                "schema": {
                    "type": "string"
                },
                "tableName": {
                    "type": "string"
                }
            },
            "required": [
                "schema",
                "tableName"
            ]
        },
        "tableIdObject": {
            "type": "object",
            "title": "schema tableIdObject",
            "properties": {
                "tableId": {
                    "type": "string"
                }
            },
            "required": [
                "tableId"
            ]
        },
        "ObjectA": {
            "type": "object",
            "title": "schema objectA",
            "properties": {
                "ObjectAName": {
                    "type": "string"
                }
            },
            "required": [
                "ObjectAName"
            ]
        },
        "ObjectB": {
            "type": "object",
            "title": "schema objectB",
            "properties": {
                "ObjectBName": {
                    "type": "string"
                }
            },
            "required": [
                "ObjectBName"
            ]
        }
    }
}

This is incorrect but it is validated. I can see that it hasn't found ObjectAName to be ObjectA as it doesn't show the title in the hover over in visual studio code

{
    "$schema": "./testSchema.json",
    "ObjectAName":"blah",
    "ObjectBName":"blah"
}

Attempt 2 - oneOf\allOf with all 4 combinations

Even this doesn't work

{
    "$schema": "http://json-schema.org/draft-04/schema",
    "title": "testSchema",
    "oneOf": [
        {
            "allOf": [
                {
                    "$ref": "#/definitions/directTableReference"
                },
                {
                    "$ref": "#/definitions/ObjectA"
                }
            ],
            "additionalProperties": false
        },
        {
            "allOf": [
                {
                    "$ref": "#/definitions/tableIdObject"
                },
                {
                    "$ref": "#/definitions/ObjectA"
                }
            ],
            "additionalProperties": false
        },
        {
            "allOf": [
                {
                    "$ref": "#/definitions/directTableReference"
                },
                {
                    "$ref": "#/definitions/ObjectB"
                }
            ],
            "additionalProperties": false
        },
        {
            "allOf": [
                {
                    "$ref": "#/definitions/tableIdObject"
                },
                {
                    "$ref": "#/definitions/ObjectB"
                }
            ],
            "additionalProperties": false
        }
    ],
    "definitions": {
        "directTableReference": {
            "type": "object",
            "title": "schema directTableReference",
            "properties": {
                "schema": {
                    "type": "string"
                },
                "tableName": {
                    "type": "string"
                }
            },
            "required": [
                "schema",
                "tableName"
            ]
        },
        "tableIdObject": {
            "type": "object",
            "title": "schema tableIdObject",
            "properties": {
                "tableId": {
                    "type": "string"
                }
            },
            "required": [
                "tableId"
            ]
        },
        "ObjectA": {
            "type": "object",
            "title": "schema objectA",
            "properties": {
                "ObjectAName": {
                    "type": "string"
                }
            },
            "required": [
                "ObjectAName"
            ]
        },
        "ObjectB": {
            "type": "object",
            "title": "schema objectB",
            "properties": {
                "ObjectBName": {
                    "type": "string"
                }
            },
            "required": [
                "ObjectBName"
            ]
        }
    }
}

This is now seen as invalid and actually as the directTableReference Object

{
    "tableId": "blah",
    "ObjectAName": "blah"
}

Struggling to see the point of allOf when it basically makes impossible objects, but surely I'm missing a trick here.

Is there no way to actually reuse these objects or is there no way to do this in draft-4. Does it help if I can go to draft-7?

Thanks for any help



Solution 1:[1]

Sure, you should be able to do that by combining definitions and $ref with allOf, anyOf and oneOf.

https://json-schema.org/understanding-json-schema/reference/conditionals.html
https://json-schema.org/understanding-json-schema/structuring.html

The docs refer to $defs, but in draft4 that was called definitions.

So, something like:

definitions:
  tableId:
    ...
  Schema:
    ...
  ObjectA:
    ...
  ObjectB:
    ...
  ObjectC:
    ...
allOf:
  - anyOf:
    - $ref: '#/definitions/tableId'
    - allOf:
      - $ref: '#/definitions/Schema'
      - $ref: '#/definitions/TableName'
  - anyOf:
    - allOf:
      - $ref: '#/definitions/ObjectA'
      - $ref: '#/definitions/ObjectB'
      - $ref: '#/definitions/ObjectC'
    - allOf:
      - $ref: '#/definitions/ObjectA'
      - $ref: '#/definitions/ObjectD'

Solution 2:[2]

Ok, @ether gave me confidence in the right direction and just writing this up here with the final actual schema in case it helps someone else trying to "merge" or "blend" json objects together with allOf

The solution was

  • definitions should only contain the "properties" and "required" parts finally construct the property options for our object with oneOf/allOf
  • use oneOf(allOf(Aa),allOf(Ab),allOf(Ba),allOf(Bb)) pattern
    • allOf(oneOf(A,B),oneOf(a, b)) would also work, however it means that you cannot provide a title to explain to the user which allowed property combination they are using, which was useful to our solution)
  • because we are using draft-4/7 we have to redefine the "properties" when combining the definitions to surface the inner definition properties. I understand this wouldn't be necessary with the later drafts

So the final working schema was this

{
    "$schema": "http://json-schema.org/draft-04/schema",
    "$id": "https://blah.com/test",
    "title": "testSchema",
    "oneOf": [
        {
            "title":"Ba",
            "allOf": [
                {
                    "$ref": "#/definitions/directTableReference"
                },
                {
                    "$ref": "#/definitions/ObjectA"
                }
            ],
            "properties": {
                "schema":{},
                "tableName":{},
                "ObjectAName":{}
            },
            "additionalProperties": false
        },
        {
            "title":"Aa",
            "allOf": [
                {
                    "$ref": "#/definitions/tableIdObject"
                },
                {
                    "$ref": "#/definitions/ObjectA"
                }
            ],
            "properties": {
                "tableId":{},
                "ObjectAName":{}
            },
            "additionalProperties": false
        },
        {
            "title":"Bb",
            "allOf": [
                {
                    "$ref": "#/definitions/directTableReference"
                },
                {
                    "$ref": "#/definitions/ObjectB"
                }
            ],
            "properties": {
                "schema":{},
                "tableName":{},
                "ObjectBName":{}
            },
            "additionalProperties": false
        },
        {
            "title":"Ba",
            "allOf": [
                {
                    "$ref": "#/definitions/tableIdObject"
                },
                {
                    "$ref": "#/definitions/ObjectB"
                }
            ],
            "properties": {
                "tableId":{},
                "ObjectBName":{}
            },
            "additionalProperties": false
        }
    ],
    "definitions": {
        "directTableReference": {
            "type":"object",
            "properties": {
                "schema": {
                    "title": "schema",
                    "type": "string"
                },
                "tableName": {
                    "title": "tableName",
                    "type": "string"
                }
            },
            "required": [
                "schema",
                "tableName"
            ]
        },
        "tableIdObject": {
            "type":"object",
            "properties": {
                "tableId": {
                    "title": "tableIdObject",
                    "type": "string"
                }
            },
            "required": [
                "tableId"
            ]
        },
        "ObjectA": {
            "type":"object",
            "properties": {
                "ObjectAName": {
                    "title": "ObjectA",
                    "type": "string"
                }
            },
            "required": [
                "ObjectAName"
            ]
        },
        "ObjectB": {
            "type":"object",
            "properties": {
                "ObjectBName": {
                    "title": "ObjectB",
                    "type": "string"
                }
            },
            "required": [
                "ObjectBName"
            ]
        }
    }
}

These are now valid

{
    "schema":"blah",
    "tableName": "blah",
    "ObjectAName": "blah"
}
{    
    "tableId": "blah",
    "ObjectAName": "blah",
}
{    
    "tableId": "blah",
    "ObjectBName": "blah",
}

and these are not

{    
    "tableId": "blah",
    "ObjectAName": "blah",
    "ObjectBName": "blah",
}
{    
    "tableId": "blah",
    "blah":"blah"
}

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Ether
Solution 2