1

I have a json file with nested objects.

{
    "apiVersion":"0.0.9b",
    "apiDate":"18.01.19",

    "general":{
        "documentType": "invoice",

        "references":{

            "invoiceId":"123",
            "invoiceDate":"01.01.1970",

            "creditNoteId":"123",
            "creditNoteDate":"01.01.1970"
        }
    }
}

Now I would like to define that invoiceId and invoiceDate should be required if documentType is invoice, and also the other way arraound (creditNoteId and Date are required if documentType is creditNote). All other Properties should be optional.

Pseudo-Code:

documentType = invoice
- required: invoiceId, invoiceDate
- optional: creditNoteId, creditNoteDate
documentType = creditNote
- required: creditNoteId, creditNoteDate
- optional: invoiceId, invoiceDate

If i store all properties in the same object I found this working solution:

{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "type": "object",
  "required": [
    "apiVersion",
    "apiDate"
  ],
  "properties": {
    "apiVersion": {
      "type": "string",
      "description": "The version of the json file"
    },
    "apiDate": {
      "type": "string",
      "description": "The date when the json version was published"
    },
    "general": {
      "$ref": "#/definitions/general_identifiers"
    }
  },
  "definitions" : {

    "general_identifiers" : {
      "type": "object",
      "required": [
        "documentType"
      ],
      "properties": {
        "documentType": {
          "enum": [
            "invoice",
            "creditNote"
          ]
        },
        "invoiceId": {
          "type": "string"
        },
        "invoiceDate": {
          "type": "string"
        },
        "creditNoteId": {
          "type": "string"
        },
        "creditNoteDate": {
          "type": "string"
        }
      },
      "oneOf": [
        {
          "$comment": "Invoice",
          "properties": {
            "documentType": { "enum": ["invoice"] }
          },
          "required": ["invoiceId", "invoiceDate"]
        },
        {
          "$comment": "CreditNote",
          "properties": {
            "documentType": { "enum": ["creditNote"] }
          },
          "required": ["creditNoteId", "creditNoteDate"]
        }
      ]
    }
  }
}

Is there a way to display this dependency with nested objects used in the above json?

What I tried already was:

{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "type": "object",
  "required": [
    "apiVersion",
    "apiDate"
  ],
  "properties": {
    "apiVersion": {
      "type": "string",
      "description": "The version of the json file"
    },
    "apiDate": {
      "type": "string",
      "description": "The date when the json version was published"
    },
    "general": {
      "$ref": "#/definitions/general_identifiers"
    },
    "references": {
      "type": "object",
      "properties": {
        "invoiceId": {
          "type": "string"
        },
        "invoiceDate": {
          "type": "string"
        },
        "creditNoteId": {
          "type": "string"
        },
        "creditNoteDate": {
          "type": "string"
        }
      },
      "oneOf": [
        {
          "$comment": "Invoice",
          "properties": {
            "documentType": { "enum": ["invoice"] }
          },
          "required": ["invoiceId", "invoiceDate"]
        },
        {
          "$comment": "CreditNote",
          "properties": {
            "documentType": { "enum": ["creditNote"] }
          },
          "required": ["creditNoteId", "creditNoteDate"]
        }
      ]
    }

  },
  "definitions" : {

    "general_identifiers" : {
      "type": "object",
      "required": [
        "documentType"
      ],
      "properties": {
        "documentType": {
          "enum": [
            "invoice",
            "creditNote"
          ]
        }
      }
    }
  }
}

But with this i get an Error from https://www.jsonschemavalidator.net

Message: JSON is valid against more than one schema from 'oneOf'. Valid schema indexes: 0, 1.

What have I missed?

2 Answers 2

1

You're very close. You just need to pull your oneOf up to the top level so you can reference #/properties/general and #/properties/references from the same schema.

Also, you almost always want to use anyOf instead of oneOf. oneOf enforces that one and only one schema in the list validates. When the schemas are mutually exclusive, oneOf is just asking the validator to do unnecessary work.

"anyOf": [
  {
    "properties": {
      "general": {
        "properties": {
          "documentType": { "enum": ["invoice"] }
        }
      },
      "references": {
        "required": ["invoiceId", "invoiceDate"]
      }
    }
  },
  {
    "properties": {
      "general": {
        "properties": {
          "documentType": { "enum": ["creditNote"] }
        }
      },
      "references": {
        "required": ["creditNoteId", "creditNoteDate"]
      }
    }
  }
]
Sign up to request clarification or add additional context in comments.

2 Comments

Thanks for your Feedback - I'll use anyOf in the future.
As I tried your soulution I saw that my json Example in the question was not realy correct. As I said I found a solution when general an references are objects in the same level. But what I actually not get is how to work with it when they are in different levels like: { apiVersion general{ documentType references{ invoiceId ... } } } I updated the json Example above. And with your help i solved my problem :)
1

With the help of Jason Desrosiers I finaly found a solution also for my nested json.

{
    "$schema": "http://json-schema.org/draft-07/schema#",
    "type": "object",
    "required": [
        "apiVersion",
        "apiDate"
    ],
    "anyOf": [
        {
            "properties": {
                "general": {
                    "properties": {
                        "documentType": { "enum": ["invoice"] },
                        "references": {
                            "required": ["invoiceId", "invoiceDate"]
                        }
                    }
                }
            }
        },
        {
            "properties": {
                "general": {
                    "properties": {
                        "documentType": { "enum": ["creditNote"] },
                        "references": {
                            "required": ["creditNoteId", "creditNoteDate"]
                        }
                    }
                }
            }
        }
    ], 
    "properties": {
        "apiVersion": {
            "type": "string",
            "description": "The version of the json file"
        },
        "apiDate": {
            "type": "string",
            "description": "The date when the json version was published"
        },
        "general": {
            "$ref": "#/definitions/general_identifiers",
            "references": {
                "type": "object",
                "properties": {
                    "invoiceId": {
                        "type": "string"
                    },
                    "invoiceDate": {
                        "type": "string"
                    },
                    "creditNoteId": {
                        "type": "string"
                    },
                    "creditNoteDate": {
                        "type": "string"
                    }
                } 
            }
        }
    },
    "definitions" : {
        "general_identifiers" : {
            "type": "object",
            "required": [
                "documentType"
            ],
            "properties": {
                "documentType": {
                    "enum": [
                        "invoice",
                        "creditNote"
                    ]
                }
            }
        }
    }
}

3 Comments

Note if you use draft7 like your title says, you can say "const": "invoice" instead of "enum": ["invoice"] which would be slightly clearer to the reader. (const is new in draft 7)
Thanks Erwin for your reply. In the example I removed the other enum values so that the code is shorter and more universal.
const is the same as a single-value enum, just clearer.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.