Skip to content

Commit 0b67f59

Browse files
committed
Revise unevaluatedProperties
Signed-off-by: Juan Cruz Viotti <[email protected]>
1 parent 3aa9d07 commit 0b67f59

File tree

1 file changed

+89
-169
lines changed

1 file changed

+89
-169
lines changed

content/2020-12/unevaluated/unevaluatedProperties.markdown

Lines changed: 89 additions & 169 deletions
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,14 @@ occurences of [`properties`]({{< ref "2020-12/applicator/properties" >}}),
3636
[`patternProperties`]({{< ref "2020-12/applicator/patternproperties" >}}),
3737
[`additionalProperties`]({{< ref "2020-12/applicator/additionalproperties"
3838
>}}), and [`unevaluatedProperties`]({{< ref
39-
"2020-12/unevaluated/unevaluatedproperties" >}}) itself as long as the evaluate
40-
path that led to [`unevaluatedProperties`]({{< ref
41-
"2020-12/unevaluated/unevaluatedproperties" >}}) is a prefix of the evaluate
39+
"2020-12/unevaluated/unevaluatedproperties" >}}) itself, as long as the
40+
evaluate path that led to [`unevaluatedProperties`]({{< ref
41+
"2020-12/unevaluated/unevaluatedproperties" >}}) is a _prefix_ of the evaluate
4242
path of the others.
4343

44+
Given its evaluation-dependent nature, this keyword is evaluated after every
45+
other keyword from every other vocabulary.
46+
4447
{{<best-practice>}}
4548

4649
There are two common use cases for this keyword, both for reducing duplication:
@@ -50,12 +53,12 @@ There are two common use cases for this keyword, both for reducing duplication:
5053
keywords behind conditional logic without duplicating the
5154
[`additionalProperties`]({{< ref "2020-12/applicator/additionalproperties"
5255
>}}) keyword in every possible branch. (2) Re-using
53-
the [`properties`]({{< ref "2020-12/applicator/properties" >}}) and
54-
[`patternProperties`]({{< ref "2020-12/applicator/patternproperties" >}})
55-
keywords, or the [`additionalProperties`]({{< ref
56-
"2020-12/applicator/additionalproperties" >}}) keyword, as helpers while
57-
specialising the helpers with other related keywords as needed in specific
58-
locations without having to inline the entire helper.
56+
subschemas that consist of the [`properties`]({{< ref
57+
"2020-12/applicator/properties" >}}) and [`patternProperties`]({{< ref
58+
"2020-12/applicator/patternproperties" >}}) keywords, or the
59+
[`additionalProperties`]({{< ref "2020-12/applicator/additionalproperties" >}})
60+
keyword, as helpers while specialising the helpers with other related keywords
61+
as needed in specific locations without having to inline the entire helper.
5962

6063
{{</best-practice>}}
6164

@@ -78,230 +81,147 @@ dependencies, leading to schemas that are simpler to evaluate.
7881

7982
## Examples
8083

81-
{{<schema `Schema with 'unevaluatedProperties' set to boolean true`>}}
84+
{{<schema `A schema that conditionally constrains object instances to define certain properties, with string additional properties in both cases`>}}
8285
{
8386
"$schema": "https://json-schema.org/draft/2020-12/schema",
84-
"unevaluatedProperties": true
87+
"if": { "maxProperties": 2 },
88+
"then": { "properties": { "foo": true } },
89+
"else": { "patternProperties": { "^@": true } },
90+
"unevaluatedProperties": { "type": "string" }
8591
}
8692
{{</schema>}}
8793

88-
{{<instance-pass `All object instances pass against the true schema`>}}
89-
{ "foo": "bar", "baz": 33 }
94+
{{<instance-pass `An object value that defines a "foo" property and other string properties is valid`>}}
95+
{ "foo": 1, "bar": "baz" }
9096
{{</instance-pass>}}
9197

92-
{{<instance-pass `'unevaluatedProperties' does not have any effect on instances other than an object`>}}
93-
"John Doe"
98+
{{<instance-annotation>}}
99+
{ "keyword": "/then/properties", "instance": "", "value": [ "foo" ] }
100+
{ "keyword": "/unevaluatedProperties", "instance": "", "value": [ "bar" ] }
101+
{{</instance-annotation>}}
102+
103+
{{<instance-pass `An object value that defines multiple properties that start with "@" and other string properties is valid`>}}
104+
{ "@foo": 1, "@bar": 2, "baz": "qux" }
94105
{{</instance-pass>}}
95106

96107
{{<instance-annotation>}}
97-
{ "keyword": "/unevaluatedProperties", "instance": "", "value": [ "foo", "baz" ] }
108+
{ "keyword": "/else/patternProperties", "instance": "", "value": [ "@foo", "@bar" ] }
109+
{ "keyword": "/unevaluatedProperties", "instance": "", "value": [ "baz" ] }
98110
{{</instance-annotation>}}
99111

100-
* Here, no properties are defined in the above schema. Consequently, all properties in an object instance are considered unevaluated, and the `unevaluatedProperties` subschema applies to them. Since the subschema here is a boolean true, an instance with unevaluated properties, regardless of their value, is considered valid.
101-
102-
{{<schema `Schema with 'unevaluatedProperties' set to boolean false`>}}
103-
{
104-
"$schema": "https://json-schema.org/draft/2020-12/schema",
105-
"unevaluatedProperties": false
106-
}
107-
{{</schema>}}
112+
{{<instance-fail `An object value that defines a "foo" property and other non-string properties is invalid`>}}
113+
{ "foo": 1, "bar": 2 }
114+
{{</instance-fail>}}
108115

109-
{{<instance-fail `All object instances fail against the false schema`>}}
110-
{ "foo": "bar" }
116+
{{<instance-fail `An object value that defines multiple properties that start with "@" and other non-string properties is invalid`>}}
117+
{ "@foo": 1, "@bar": 2, "baz": 3 }
111118
{{</instance-fail>}}
112119

113-
{{<instance-pass `'unevaluatedProperties' does not have any effect on instances other than an object`>}}
114-
[ "John", 46, false ]
120+
{{<instance-pass `An empty object value is valid`>}}
121+
{}
115122
{{</instance-pass>}}
116123

117-
{{<schema `Schema with 'unevaluatedProperties', 'properties', and 'patternProperties', with unevaluatedProperties set to boolean false`>}}
124+
{{<instance-pass `A non-object value is valid`>}}
125+
"Hello World"
126+
{{</instance-pass>}}
127+
128+
{{<schema `A schema that constraints object instances to only allow extension keywords that start with "@" using a helper`>}}
118129
{
119130
"$schema": "https://json-schema.org/draft/2020-12/schema",
120-
"properties": {
121-
"foo": { "type": "string" }
122-
},
123-
"patternProperties": {
124-
"^b": { "type": "number" }
125-
},
126-
"unevaluatedProperties": false
131+
"properties": { "foo": true },
132+
"$ref": "#/$defs/allow-extensions",
133+
"unevaluatedProperties": false,
134+
"$defs": {
135+
"allow-extensions": {
136+
"patternProperties": { "^@": true }
137+
}
138+
}
127139
}
128140
{{</schema>}}
129141

130-
{{<instance-fail `An instance with unevaluated properties is invalid`>}}
131-
{ "foo": "foo", "bar": 36, "fooBar": false }
132-
{{</instance-fail>}}
133-
134-
{{<instance-pass `An instance with no unevaluated properties is valid`>}}
135-
{ "foo": "foo", "bar": 36 }
142+
{{<instance-pass `An object value that only defines a "foo" property is valid`>}}
143+
{ "foo": 1 }
136144
{{</instance-pass>}}
137145

138146
{{<instance-annotation>}}
139147
{ "keyword": "/properties", "instance": "", "value": [ "foo" ] }
140-
{ "keyword": "/patternProperties", "instance": "", "value": [ "bar" ] }
141148
{{</instance-annotation>}}
142149

143-
* For the first instance, the annotation result of `properties` is [ "foo" ], and the annotation result of `patternProperties` is [ "bar" ]. However, the 'fooBar' property remains unevaluated, so the `unevaluatedProperties` subschema applies to it. This subschema fails (as any instance against a false schema is always invalid), leading to the failure of the entire schema.
144-
* For the second instance, the annotation result of `properties` is [ "foo" ], and the annotation result of `patternProperties` is [ "bar" ]. No properties remain unevaluated; hence, the instance is considered valid.
145-
146-
{{<schema `Schema with 'unevaluatedProperties', 'properties', and 'patternProperties', with unevaluatedProperties set to an object subschema`>}}
147-
{
148-
"$schema": "https://json-schema.org/draft/2020-12/schema",
149-
"properties": {
150-
"foo": { "type": "string" }
151-
},
152-
"patternProperties": {
153-
"^b": { "type": "number" }
154-
},
155-
"unevaluatedProperties": { "type": "boolean" }
156-
}
157-
{{</schema>}}
158-
159-
{{<instance-pass `An instance with no unevaluated properties is valid`>}}
160-
{ "foo": "foo", "bar": 36 }
150+
{{<instance-pass `An object value that only defines a "foo" property and other properties that start with "@" is valid`>}}
151+
{ "foo": 1, "@bar": 2, "@baz": 3 }
161152
{{</instance-pass>}}
162153

163154
{{<instance-annotation>}}
164155
{ "keyword": "/properties", "instance": "", "value": [ "foo" ] }
165-
{ "keyword": "/patternProperties", "instance": "", "value": [ "bar" ] }
156+
{ "keyword": "/$defs/allow-extensions/patternProperties", "instance": "", "value": [ "@bar", "@baz" ] }
166157
{{</instance-annotation>}}
167158

168-
{{<instance-pass `An instance with unevaluated properties that conform to the 'unevaluatedProperties' subschema is valid`>}}
169-
{ "foo": "foo", "bar": 36, "fooBar": false }
159+
{{<instance-pass `An object value that only defines properties that start with "@" is valid`>}}
160+
{ "@foo": 1, "@bar": 2, "@baz": 3 }
170161
{{</instance-pass>}}
171162

172163
{{<instance-annotation>}}
173-
{ "keyword": "/properties", "instance": "", "value": [ "foo" ] }
174-
{ "keyword": "/patternProperties", "instance": "", "value": [ "bar" ] }
175-
{ "keyword": "/unevaluatedProperties", "instance": "", "value": [ "fooBar" ] }
164+
{ "keyword": "/$defs/allow-extensions/patternProperties", "instance": "", "value": [ "@foo", "@bar", "@baz" ] }
176165
{{</instance-annotation>}}
177166

178-
{{<instance-fail `An instance with unevaluated properties that do not conform to the 'unevaluatedProperties' subschema is invalid`>}}
179-
{ "foo": "foo", "bar": 36, "fooBar": "string" }
167+
{{<instance-fail `An object value that only defines a "foo" property and other properties that do not start with "@" is invalid`>}}
168+
{ "foo": 1, "bar": 2 }
180169
{{</instance-fail>}}
181-
* For the first instance, there are no unevaluated properties.
182-
* For the second instance, 'fooBar' is unevaluated, and the `unevaluatedProperties` subschema applies to it. 'fooBar' conforms to this subschema, and hence the instance is valid. The annotations produced by applicators are: `properties`[ "foo" ], `patternProperties`[ "bar" ], and `unevaluatedProperties`[ "fooBar" ].
183170

184-
{{<schema `Schema with 'unevaluatedProperties' and 'allOf' keyword`>}}
185-
{
186-
"$schema": "https://json-schema.org/draft/2020-12/schema",
187-
"properties": {
188-
"foo": { "type": "string" }
189-
},
190-
"allOf": [
191-
{
192-
"patternProperties": {
193-
"^b": { "type": "number" }
194-
}
195-
}
196-
],
197-
"unevaluatedProperties": { "type": "boolean" }
198-
}
199-
{{</schema>}}
200-
201-
{{<instance-pass `An instance with unevaluated properties that conform to the 'unevaluatedProperties' subschema is valid`>}}
202-
{ "foo": "foo", "bar": 36, "fooBar": false }
171+
{{<instance-pass `An empty object value is valid`>}}
172+
{}
203173
{{</instance-pass>}}
204174

205-
{{<instance-annotation>}}
206-
{ "keyword": "/properties", "instance": "", "value": [ "foo" ] }
207-
{ "keyword": "/allOf/0/patternProperties", "instance": "", "value": [ "foo" ] }
208-
{ "keyword": "/unevaluatedProperties", "instance": "", "value": [ "fooBar" ] }
209-
{{</instance-annotation>}}
210-
211-
{{<instance-fail `An instance with unevaluated properties that do not conform to the 'unevaluatedProperties' subschema is invalid`>}}
212-
{ "foo": "foo", "bar": 36, "fooBar": "string" }
213-
{{</instance-fail>}}
214-
For the above two instances, the annotation result of `properties` is [ "foo" ], and the annotation result of nested `patternProperties` is [ "bar" ]. The `unevaluatedProperties` recognizes the annotations from `properties` as well as `patternProperties` (as it can see through adjacent and nested applicators as only the produced annotations matter, not the schema structure) and ensures that 'fooBar' remains unevaluated and its subschema applies to 'fooBar'.
215-
* The first instance passes as it conforms to the unevaluated subschema.
216-
* The second instance fails as it does not conform to the unevaluated subschema.
175+
{{<instance-pass `A non-object value is valid`>}}
176+
"Hello World"
177+
{{</instance-pass>}}
217178

218-
{{<schema `Schema with 'unevaluatedProperties' and 'allOf' keyword`>}}
179+
{{<schema `A schema that constraints object instances to not define any properties, as both object keywords are cousins`>}}
219180
{
220181
"$schema": "https://json-schema.org/draft/2020-12/schema",
221-
"properties": {
222-
"foo": { "type": "string" }
223-
},
224182
"allOf": [
225-
{
226-
"additionalProperties": true
227-
}
228-
],
229-
"unevaluatedProperties": false
183+
{ "properties": { "foo": true } },
184+
{ "unevaluatedProperties": false }
185+
]
230186
}
231187
{{</schema>}}
232188

233-
{{<instance-pass `An instance with no unevaluated properties is valid`>}}
234-
{ "foo": "foo" }
235-
{{</instance-pass>}}
189+
{{<instance-fail `An object value that only defines a "foo" property is invalid as the schema prohibits unevaluated properties`>}}
190+
{ "foo": 1 }
191+
{{</instance-fail>}}
236192

237-
{{<instance-annotation>}}
238-
{ "keyword": "/properties", "instance": "", "value": [ "foo" ] },
239-
{ "keyword": "/allOf/0/additionalProperties", "instance": "", "value": [ "foo" ] }
240-
{{</instance-annotation>}}
193+
{{<instance-fail `An object value that defines any other property is invalid as the schema prohibits unevaluated properties`>}}
194+
{ "bar": 2 }
195+
{{</instance-fail>}}
241196

242-
{{<instance-pass `An instance with no unevaluated properties is valid`>}}
243-
{ "foo": "foo", "bar": "bar" }
197+
{{<instance-pass `An empty object value is valid`>}}
198+
{}
244199
{{</instance-pass>}}
245200

246-
{{<instance-annotation>}}
247-
{ "keyword": "/properties", "instance": "", "value": [ "foo" ] }
248-
{ "keyword": "/allOf/0/additionalProperties", "instance": "", "value": [ "foo", "bar" ] }
249-
{{</instance-annotation>}}
250-
251-
* In the first case, there are no unevaluated properties.
252-
* In the second case, the nested `{ additionalProperties: true }` evaluated all the remaining properties. So there's nothing left unevaluated.
201+
{{<instance-pass `A non-object value is valid`>}}
202+
"Hello World"
203+
{{</instance-pass>}}
253204

254-
{{<schema `Schema with 'unevaluatedProperties' and '#ref' keyword`>}}
205+
{{<schema `A schema that constraints object instances to define arbitrary properties`>}}
255206
{
256207
"$schema": "https://json-schema.org/draft/2020-12/schema",
257-
"properties": {
258-
"foo": { "type": "string" }
259-
},
260-
"$ref": "#/$defs/bar",
261-
"unevaluatedProperties": false,
262-
"$defs": {
263-
"bar": {
264-
"properties": {
265-
"bar": { "type": "string" }
266-
}
267-
}
268-
}
208+
"allOf": [ { "unevaluatedProperties": true } ],
209+
"unevaluatedProperties": false
269210
}
270211
{{</schema>}}
271212

272-
{{<instance-pass `An instance with no unevaluated properties is valid`>}}
273-
{ "foo": "foo", "bar": "bar" }
213+
{{<instance-pass `An object value that defines any property is valid as the nested applicator takes precedence`>}}
214+
{ "foo": 1, "bar": 2, "baz": 3 }
274215
{{</instance-pass>}}
275216

276217
{{<instance-annotation>}}
277-
{ "keyword": "/properties", "instance": "", "value": [ "foo" ] }
278-
{ "keyword": "/$ref/properties", "instance": "", "value": [ "bar" ] }
218+
{ "keyword": "/allOf/0/unevaluatedProperties", "instance": "", "value": [ "foo", "bar", "baz" ] }
279219
{{</instance-annotation>}}
280220

281-
{{<instance-fail `An instance with unevaluated properties is invalid`>}}
282-
{ "foo": "foo", "bar": "bar", "baz": "baz" }
283-
{{</instance-fail>}}
284-
285-
{{<schema `Schema with nested 'unevaluatedProperties' keyword`>}}
286-
{
287-
"$schema": "https://json-schema.org/draft/2020-12/schema",
288-
"properties": {
289-
"foo": { "type": "string" }
290-
},
291-
"allOf": [
292-
{
293-
"unevaluatedProperties": true
294-
}
295-
],
296-
"unevaluatedProperties": false
297-
}
298-
{{</schema>}}
299-
300-
{{<instance-pass `No properties remain unevaluated for the top-level 'unevaluatedProperties'`>}}
301-
{ "foo": "foo", "bar": 101 }
221+
{{<instance-pass `An empty object value is valid`>}}
222+
{}
302223
{{</instance-pass>}}
303224

304-
{{<instance-annotation>}}
305-
{ "keyword": "/properties", "instance": "", "value": [ "foo" ] }
306-
{ "keyword": "/allOf/0/unevaluatedProperties", "instance": "", "value": [ "foo", "bar" ] }
307-
{{</instance-annotation>}}
225+
{{<instance-pass `A non-object value is valid`>}}
226+
"Hello World"
227+
{{</instance-pass>}}

0 commit comments

Comments
 (0)