1

I have the following Schema. I've implemented it as best I can, but it's still not working quite as I want it to.

{
    "$schema": "http://json-schema.org/draft-07/schema#",
    "type": "object",
    "title": "Ordering pizza",
    "propertyNames": {
        "enum": [
            "q-who-did-you-order-from",
            "q-did-they-accept-your-order",
            "q-how-much-was-the-bill",
            "q-why-didnt-they-accept"
        ]
    },
    "properties": {
        "q-who-did-you-order-from": {
            "type": "string",
            "title": "Who have you ordered pizza from?",
            "maxLength": 50,
            "errorMessages": {
                "required": "Enter who you ordered from",
                "maxLength":
                    "Who you ordered from must be 50 characters or less"
            }
        },
        "q-did-they-accept-your-order": {
            "title": "Have they accepted your order?",
            "type": "boolean",
            "errorMessages": {
                "required":
                    "Select yes if they have accepted your order"
            }
        },
        "q-how-much-was-the-bill": {
            "type": "string",
            "title": "How much was the bill?",
            "maxLength": 50,
            "errorMessages": {
                "required": "Enter an amount",
                "maxLength": "Amount must be 50 characters or less"
            }
        },
        "q-why-didnt-they-accept": {
            "type": "string",
            "title": "Why wasnt your order accepted?",
            "description":
                "If you do not know you can say so.",
            "maxLength": 50,
            "errorMessages": {
                "required": "Enter a reason",
                "maxLength": "Reason must be 50 characters or less"
            }
        }
    },
    "required": ["q-who-did-you-order-from", "q-did-they-accept-your-order"],
    "allOf": [
        {
            "$ref": "#/definitions/if-false-then-q-why-didnt-they-accept-is-required"
        },
        {
            "$ref": "#/definitions/if-true-then-q-how-much-was-the-bill-is-required"
        }
    ],
    "definitions": {
        "if-false-then-q-why-didnt-they-accept-is-required": {
            "if": {
                "properties": {
                    "q-did-they-accept-your-order": {
                        "const": false
                    }
                }
            },
            "then": {
                "required": ["q-why-didnt-they-accept"],
                "propertyNames": {
                    "enum": [
                        "q-who-did-you-order-from",
                        "q-did-they-accept-your-order",
                        "q-why-didnt-they-accept"
                    ]
                }
            }
        },
        "if-true-then-q-how-much-was-the-bill-is-required": {
            "if": {
                "properties": {
                    "q-did-they-accept-your-order": {
                        "const": true
                    }
                }
            },
            "then": {
                "required": ["q-how-much-was-the-bill"],
                "propertyNames": {
                    "enum": [
                        "q-who-did-you-order-from",
                        "q-did-they-accept-your-order",
                        "q-how-much-was-the-bill"
                    ]
                }
            }
        }
    }
}

The expectation is that the user will enter a value for q-who-did-you-order-from and q-did-they-accept-your-order, then only one of the remaining two questions based on their answer for q-did-they-accept-your-order.

So the following inputs should validate:

{
    "q-did-you-order-from": "Pizza hut",
    "q-did-they-accept-your-order": "true",
    "q-how-much-was-the-bill": "20"
}



{
    "q-did-you-order-from": "Pizza hut",
    "q-did-they-accept-your-order": "false",
    "q-why-didn't-they-accept": "Incorrect card details"
}

Similarly, I would expect the following inputs to fail validation and throw a 'required' error for the field containing a blank string. The first should throw an error because q-why-didn't-they-accept is empty:

{
    "q-did-you-order-from": "Pizza hut",
    "q-did-they-accept-your-order": "false",
    "q-why-didn't-they-accept": ""
}

And this one should throw an error because q-how-much-was-the-bill is empty.

{
    "q-did-you-order-from": "Pizza hut",
    "q-did-they-accept-your-order": "true",
    "q-how-much-was-the-bill": ""
}

And it does! This works as expected. However, we found a bug that arises from the user not entering an answer to q-did-they-accept-your-order. The answers to these questions are POSTed via the browser on form submission. In the browser, the boolean question is presented as yes/no radio buttons. As a result, when the user does not check either radio, but does try to submit the form, the answer for the radios is omitted entirely. The data object sent looks like this:

{
    "q-did-you-order-from": "Pizza hut",
    "q-how-much-was-the-bill": "",
    "q-why-didn't-they-accept": "",
}

My EXPECTED result here:

AJV throws only one 'required' error for q-did-they-accept-your-order. It shouldn't throw a 'required' error for anything else as both q-how-much-was-the-bill and q-why-didn't-they-accept aren't required unless the related value for q-did-they-accept-your-order is selected.

My ACTUAL result:

AJV Throws an error for all three empty inputs.

So my question is, how do I get AJV to validate this schema and for ONLY q-did-they-accept-your-order to throw a required error when the question is not answered.

EDIT:

The output from AJV is as follows:

[
    {
        "keyword": "required",
        "dataPath": "",
        "schemaPath": "#/required",
        "params": {
            "missingProperty": "q-did-they-accept-your-order"
        },
        "message": "should have required property 'q-did-they-accept-your-order'"
    },
    {
        "keyword": "required",
        "dataPath": "",
        "schemaPath": "#/definitions/if-false-then-q-why-didnt-they-accept-is-required",
        "params": {
            "missingProperty": "q-why-didnt-they-accept"
        },
        "message": "should have required property 'q-why-didnt-they-accept'"
    },
    {
        "keyword": "if",
        "dataPath": "",
        "schemaPath": "#/definitions/if-false-then-q-why-didnt-they-accept-is-required/if",
        "params": {
            "failingKeyword": "then"
        },
        "message": "should match \"then\" schema"
    },
    {
        "keyword": "required",
        "dataPath": "",
        "schemaPath": "#/definitions/if-true-then-q-how-much-was-the-bill-is-required/then/required",
        "params": {
            "missingProperty": "q-how-much-was-the-bill"
        },
        "message": "should have required property 'q-how-much-was-the-bill'"
    },
    {
        "keyword": "if",
        "dataPath": "",
        "schemaPath": "#/definitions/if-true-then-q-how-much-was-the-bill-is-required/if",
        "params": {
            "failingKeyword": "then"
        },
        "message": "should match \"then\" schema"
    }
]
Barry Piccinni
  • 1,685
  • 12
  • 23
  • What are the errors it throws? Could you edit your schema to be valid JSON as opposed to javascript please? (requires double quotes around keys). This will make it easier to debug and test using https://jsonschema.dev which uses ajv. – Relequestual Jun 03 '19 at 09:35
  • @Relequestual Thanks for your response, I have edited my question as per your request. – Barry Piccinni Jun 03 '19 at 13:15

1 Answers1

3

There's a part missing from your application of the if/then pattern. Let's use this simple schema as an example.

{
  "if": {
    "properties": {
      "foo": { "const": true }
    }
  },
  "then": {
    "required": ["bar"]
  }
}

If I validate {} against this schema, it will fail saying that the property "bar" is required. Because the /if schema doesn't require the "foo" property, {} is valid and therefore the /then schema applies. To fix this problem, you just need to make the "foo" property required.

{
  "if": {
    "properties": {
      "foo": { "const": true }
    },
    "required": ["foo"]
  },
  "then": {
    "required": ["bar"]
  }
}

Now, {} is valid against the schema. the /then schema will only apply if there is a "foo" property and it's value is true.

Jason Desrosiers
  • 22,479
  • 5
  • 47
  • 53