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).