4

My question has some overlap with JSON schema for data description vs data validation vs input validation, but enough differences that I felt it warranted a new question.

I am trying to understand the best way to design one schema that is usable for validating input and output on a single resource representation. Here's an example.

{
    "type": "object",
    "properties": {
        "id": {
            "type": "number",
            "readOnly": true
        },
        "title": {
            "type": "string"
        },
        "post": {
            "type": "string"
        }
    },
    "required": ["id", "title", "post"],
    "additionalProperties": false
};

The problem is that when validating output, we want id, title and post required. When validating input, one or more fields will not be required, and if they provide an id I want the validation to fail (due to the readOnly keyword)

I think the right way is to combine required with subschemas or boolean logic but I'm not sure exactly how to tie them together.

Is that the right path, or is there a better solution? Does anyone have an example that works with the latest version of JSON Schema (draft 7 at this time)

Thanks!

Dashron
  • 3,968
  • 2
  • 14
  • 21

1 Answers1

4

There are a couple of possible approaches here.

First, readOnly never causes JSON Schema's validation process to fail. It's not an assertion, just an annotation that applications can use to take action as they please. So your server application can handle that however it wants- raise an error if someone attempts to change it, or just silently ignore the new value.

Or look at the HTTP Prefer header and if handling=lenient is present, ignore any attempts to change read-only values ,but if handling=strict is present, error out on any attempts.

Or handle different fields differently. If you have a lastModified field and support an HTTP GET => modify => HTTP PUT workflow, then when you PUT a representation back, lastModified will inevitably be wrong but read-only at some point. You don't want to break PUT for an out of date auto-generated timestamp. But you probably do want to raise an error if someone thinks they can change the id field.

Given that, there are two general approaches: write a schema that works both ways and do additional checking in your application layer, or nail down everything for input and output separately.

The "works both ways" approach would remove id from required but document that, once created, a resource will always have an id. But this way it could be omitted on create or write without problems. To decide whether this is reasonable, think about the use cases for client-side validation of output. Do clients really need JSON Schema to check whether the server sent an id, or is that something that a client can reasonably assume, based on documentation, that the server will do correctly?

For the "lock down each way" approach, you could do something like this:

{
    "definitions": {
        "common": {
            "type": "object",
            "properties": {
                "id": {
                    "type": "number",
                    "readOnly": true
                },
                "title": {
                    "type": "string"
                },
                "post": {
                    "type": "string"
                }
            },
            "required": ["title", "post"],
            "additionalProperties": false
        },
        "input": {
            "allOf": [
                {"$ref": "#/definitions/common"},
                {"properties": {"id": false}}
            ]
        },
        "output": {
            "allOf": [
                {"$ref": "#/definitions/common"},
                {"required": ["id"]}
            ]
        }
    }
}

You need to define id in the common schema so that "additionalProperties": false knows about it (I try to avoid "additionalProperties": false as it makes evolving representations in a compatible way difficult, but if you want to use it, this is how to make it work).

For output, just make id required.

For input, use {"properties": {"id": false}} to forbid id. Even though it's defined in "common", the false boolean schema will always cause id to fail validation on input.

Of course, then you have to figure out how your application knows which schema to use, because again readOnly on its own never causes validation to fail. This is one reason that I prefer to have one schema for both read and write and let the application handle the differences (and document how that handling works).

Dashron
  • 3,968
  • 2
  • 14
  • 21
Henry Andrews
  • 663
  • 3
  • 6