'JSON Schema validation for different property values

I am attempting to validate the following JSON file:

{
"Transaction": {
    "Header": {
        "Workflow": "Rejection",
        "Job-Offer": {
            "Offer-Status": "New",
            "Datetime-Offered": "2017-12-15T16:00:00",
            "Accepted": "YES",
            "Datetime-Accepted": "2017-12-15T16:00:00"
        }
    }
}

}

against the following schema:

{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "Schema",
"description": "Schema",
"$ref": "#/defs/Schema",
"defs": {
    "Schema": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
            "Transaction": {
                "$ref": "#/defs/Transaction"
            }
        },
        "required": [
            "Transaction"
        ],
        "title": "Schema"
    },
    "Transaction": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
            "Transaction-Header": {
                "$ref": "#/defs/Transaction-Header"
            }
        },
        "required": [
            "Transaction-Header"
        ],
        "title": "Transaction"
    },
    "Transaction-Header": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
            "Workflow": {
                "type": "string",
                "enum": [
                    "Offer",
                    "Transfer",
                    "Acceptance",
                    "Rejection",
                    "Cancellation",
                    "Update"
                ]
            },
            "Job-Offer": {
                "$ref": "#/defs/JobOffer"
            }
        },
        "required": [
            "Workflow"
        ],
        "title": "Transaction-Header"
    },
    "JobOffer": {
        "description": "Job Offer.",
        "type": "object",
        "additionalProperties": true,
        "properties": {
            "Offer-Status": {
                "type": "string",
                "enum": [
                    "New",
                    ""
                ]
            },
            "Datetime-Offered": {
                "type": "string",
                "format": "date-time"
            },
            "Accepted": {
                "type": "string",
                "enum": [
                    "YES",
                    "NO",
                    ""
                ]
            },
            "Datetime-Accepted": {
                "type": "string",
                "format": "date-time"
            },
            "Reason-Rejected": {
                "type": "string",
                "minLength": 0,
                "maxLength": 30
            },
            "Offer-Cancelled": {
                "type": "string",
                "enum": [
                    "YES",
                    "NO",
                    ""
                ]
            },
            "Datetime-Cancelled": {
                "type": "string",
                "format": "date-time"
            }
        },
        "allOf": [
            { "$ref": "#/defs/JOBACCEPT" },
            { "$ref": "#/defs/JOBREJECT" }
        ],
        "required": [
            "Offer-Status"
        ],
        "title": "JobOffer"
    },
    "JOBACCEPT": {
        "properties": {
            "Workflow": { "enum": [ "Acceptance" ] }
        },
        "required": [ 
            "Accepted",
            "Datetime-Accepted" 
        ],
    },
    "JOBREJECT": {
        "properties": {
            "Workflow": { "enum": [ "Rejection" ] }
        },
        "required": [ 
            "Reason-Rejected" 
        ],
    }
}

}

What I am after is:

If the Workflow of "Acceptance" is selected, the fields under JOBACCEPT are required. If the Workflow of "Rejection" is selected, the fields under JOBREJECT are required.

I have tried many different combinations of oneOf, allOf, anyOf, if-then-else but nothing seems to work correctly.

Anyone have any ideas what needs to be done?

Re-worked json inline:

{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://example.com/product.schema.json",
"type": "object",
"properties": {
  "Transaction": {
    "type": "object",
    "properties": {
      "Transaction-Header": {
        "type": "object",
        "properties": {
          "Workflow": {
            "type": "string",
            "enum": [
              "Offer",
              "Transfer",
              "Acceptance",
              "Rejection",
              "Cancellation",
              "Update"
            ]
          },
          "Job-Offer": {
            "type": "object",
            "properties": {
              "Offer-Status": {
                "type": "string",
                "enum": [
                  "New",
                  ""
                ]
              },
              "Datetime-Offered": {
                "type": "string",
                "format": "date-time"
              },
              "Accepted": {
                "type": "string",
                "enum": [
                  "YES",
                  "NO",
                  ""
                ]
              },
              "Datetime-Accepted": {
                "type": "string",
                "format": "date-time"
              },
              "Reason-Rejected": {
                "type": "string",
                "minLength": 0,
                "maxLength": 30
              },
              "Offer-Cancelled": {
                "type": "string",
                "enum": [
                  "YES",
                  "NO",
                  ""
                ]
              },
              "Datetime-Cancelled": {
                "type": "string",
                "format": "date-time"
              }
            },
            "required": [
              "Offer-Status"
            ]
          },
          "readOnly": true
        },
        "required": [
          "Workflow"
        ]
      }
    },
    "required": [
      "Transaction-Header"
    ]
  }
},
"allOf": [
  {
    "if": {
      "properties": {
        "Transaction": {
          "properties": {
            "Transaction-Header": {
              "properties": {
                "Workflow": {
                  "enum": [
                    "Acceptance"
                  ]
                }
              },
              "required": [
                "Workflow"
              ]
            }
          }
        }
      }
    },
    "then": {
      "properties": {
        "Transaction": {
          "properties": {
            "Transaction-Header": {
              "properties": {
                "Job-Offer": {
                  "properties": {},
                  "required": [
                      "Accepted",
                      "Datetime-Accepted"
                    ]
                }
              }
            }
          }
        }
      }
    }
  }
],
"required": [
  "Transaction"
]}


Solution 1:[1]

You had the right idea. The problem is where you've placed the allOf with your conditionals. You have it in the "JobOffer" schema, but are trying to set constraints on the "Workflow" property which is in the "Transaction-Header" schema. There is no way to reference a property that is higher up in the JSON tree structure, so you need to move the allOf up into the "Transaction-Header" schema so you can set constraints on the "Workflow" property.

Now that it's in the right place, the best way to express your conditional constraints is with if/then. The context of the if/then is now the the "Transaction-Header" schema, so the then schemas needs to not just say what properties are required, they need to declare that those properties are in the "Job-Offer" object.

{
  ...

  "defs": {
    ...

    "Transaction-Header": {
      ...

      "allOf": [
        { "$ref": "#/defs/JOBACCEPT" },
        { "$ref": "#/defs/JOBREJECT" }
      ]
    },

    "JOBACCEPT": {
      "if": {
        "type": "object",
        "properties": {
          "Workflow": { "enum": ["Acceptance"] }
        },
        "required": ["Workflow"]
      },
      "then": {
        "properties": {
          "Job-Offer": {
            "required": ["Accepted", "Datetime-Accepted"]
          }
        }
      }
    },

    "JOBREJECT": { ... Similar to JOBACCEPT ... }
  }
}

Solution 2:[2]

You're abstracting everything away into definitions *, so it's tricky to express conditionals that reference things multiple layers deep. If you inline all the definitions, it gets a little easier to see what needs to be done.

The if/then keywords need to be at the level of 'Transaction'. In pseudocode:

"if the property 'Header' exists (i.e. required) and its value is ... (const), then require property ... with value (type=object, required properties=[...], property definitions=...)", and so on.

* by the way, in version 2020-12 the definitions keyword is $defs -- it may work the way you have it, but implementations will be unable to validate the schemas undef defs as they won't recognize them there, so some errors can slip through and be harder to find.

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 Jason Desrosiers
Solution 2 Ether