Skip to content

Commit f63d938

Browse files
committed
Handle nulls properly - fixes issue #377
1 parent c4031e1 commit f63d938

File tree

2 files changed

+50
-28
lines changed

2 files changed

+50
-28
lines changed

src/JsonSchema/Constraints/UndefinedConstraint.php

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -239,7 +239,7 @@ protected function applyDefaultValues(&$value, $schema, $path)
239239
foreach ($schema->properties as $currentProperty => $propertyDefinition) {
240240
if (
241241
!LooseTypeCheck::propertyExists($value, $currentProperty)
242-
&& isset($propertyDefinition->default)
242+
&& property_exists($propertyDefinition, 'default')
243243
&& $this->shouldApplyDefaultValue($requiredOnly, $propertyDefinition, $currentProperty, $schema)
244244
) {
245245
// assign default value
@@ -255,8 +255,8 @@ protected function applyDefaultValues(&$value, $schema, $path)
255255
// $value is an array, and items are defined - treat as plain array
256256
foreach ($schema->items as $currentItem => $itemDefinition) {
257257
if (
258-
!isset($value[$currentItem])
259-
&& isset($itemDefinition->default)
258+
!array_key_exists($currentItem, $value)
259+
&& property_exists($itemDefinition, 'default')
260260
&& $this->shouldApplyDefaultValue($requiredOnly, $itemDefinition)) {
261261
if (is_object($itemDefinition->default)) {
262262
$value[$currentItem] = clone $itemDefinition->default;
@@ -267,8 +267,8 @@ protected function applyDefaultValues(&$value, $schema, $path)
267267
$path->setFromDefault();
268268
}
269269
} elseif (
270-
($value instanceof self || $value === null)
271-
&& isset($schema->default)
270+
$value instanceof self
271+
&& property_exists($schema, 'default')
272272
&& $this->shouldApplyDefaultValue($requiredOnly, $schema)) {
273273
// $value is a leaf, not a container - apply the default directly
274274
$value = is_object($schema->default) ? clone $schema->default : $schema->default;

tests/Constraints/DefaultPropertiesTest.php

Lines changed: 45 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -19,77 +19,84 @@ class DefaultPropertiesTest extends VeryBaseTestCase
1919
public function getValidTests()
2020
{
2121
return array(
22+
/*
23+
// This test case was intended to check whether a default value can be applied for the
24+
// entire object, however testing this case is impossible, because there is no way to
25+
// distinguish between a deliberate top-level NULL and a top level that contains nothing.
26+
// As such, the assumption is that a top-level NULL is deliberate, and should not be
27+
// altered by replacing it with a default value.
2228
array(// #0 default value for entire object
2329
'',
2430
'{"default":"valueOne"}',
2531
'"valueOne"'
2632
),
27-
array(// #1 default value in an empty object
33+
*/
34+
array(// #0 default value in an empty object
2835
'{}',
2936
'{"properties":{"propertyOne":{"default":"valueOne"}}}',
3037
'{"propertyOne":"valueOne"}'
3138
),
32-
array(// #2 default value for top-level property
39+
array(// #1 default value for top-level property
3340
'{"propertyOne":"valueOne"}',
3441
'{"properties":{"propertyTwo":{"default":"valueTwo"}}}',
3542
'{"propertyOne":"valueOne","propertyTwo":"valueTwo"}'
3643
),
37-
array(// #3 default value for sub-property
44+
array(// #2 default value for sub-property
3845
'{"propertyOne":{}}',
3946
'{"properties":{"propertyOne":{"properties":{"propertyTwo":{"default":"valueTwo"}}}}}',
4047
'{"propertyOne":{"propertyTwo":"valueTwo"}}'
4148
),
42-
array(// #4 default value for sub-property with sibling
49+
array(// #3 default value for sub-property with sibling
4350
'{"propertyOne":{"propertyTwo":"valueTwo"}}',
4451
'{"properties":{"propertyOne":{"properties":{"propertyThree":{"default":"valueThree"}}}}}',
4552
'{"propertyOne":{"propertyTwo":"valueTwo","propertyThree":"valueThree"}}'
4653
),
47-
array(// #5 default value for top-level property with type check
54+
array(// #4 default value for top-level property with type check
4855
'{"propertyOne":"valueOne"}',
4956
'{"properties":{"propertyTwo":{"default":"valueTwo","type":"string"}}}',
5057
'{"propertyOne":"valueOne","propertyTwo":"valueTwo"}'
5158
),
52-
array(// #6 default value for top-level property with v3 required check
59+
array(// #5 default value for top-level property with v3 required check
5360
'{"propertyOne":"valueOne"}',
5461
'{"properties":{"propertyTwo":{"default":"valueTwo","required":"true"}}}',
5562
'{"propertyOne":"valueOne","propertyTwo":"valueTwo"}'
5663
),
57-
array(// #7 default value for top-level property with v4 required check
64+
array(// #6 default value for top-level property with v4 required check
5865
'{"propertyOne":"valueOne"}',
5966
'{"properties":{"propertyTwo":{"default":"valueTwo"}},"required":["propertyTwo"]}',
6067
'{"propertyOne":"valueOne","propertyTwo":"valueTwo"}'
6168
),
62-
array(// #8 default value for an already set property
69+
array(// #7 default value for an already set property
6370
'{"propertyOne":"alreadySetValueOne"}',
6471
'{"properties":{"propertyOne":{"default":"valueOne"}}}',
6572
'{"propertyOne":"alreadySetValueOne"}'
6673
),
67-
array(// #9 default item value for an array
74+
array(// #8 default item value for an array
6875
'["valueOne"]',
6976
'{"type":"array","items":[{},{"type":"string","default":"valueTwo"}]}',
7077
'["valueOne","valueTwo"]'
7178
),
72-
array(// #10 default item value for an empty array
79+
array(// #9 default item value for an empty array
7380
'[]',
7481
'{"type":"array","items":[{"type":"string","default":"valueOne"}]}',
7582
'["valueOne"]'
7683
),
77-
array(// #11 property without a default available
84+
array(// #10 property without a default available
7885
'{"propertyOne":"alreadySetValueOne"}',
7986
'{"properties":{"propertyOne":{"type":"string"}}}',
8087
'{"propertyOne":"alreadySetValueOne"}'
8188
),
82-
array(// #12 default property value is an object
89+
array(// #11 default property value is an object
8390
'{"propertyOne":"valueOne"}',
8491
'{"properties":{"propertyTwo":{"default":{}}}}',
8592
'{"propertyOne":"valueOne","propertyTwo":{}}'
8693
),
87-
array(// #13 default item value is an object
94+
array(// #12 default item value is an object
8895
'[]',
8996
'{"type":"array","items":[{"default":{}}]}',
9097
'[{}]'
9198
),
92-
array(// #14 only set required values (draft-04)
99+
array(// #13 only set required values (draft-04)
93100
'{}',
94101
'{
95102
"properties": {
@@ -101,7 +108,7 @@ public function getValidTests()
101108
'{"propertyTwo":"valueTwo"}',
102109
Constraint::CHECK_MODE_ONLY_REQUIRED_DEFAULTS
103110
),
104-
array(// #15 only set required values (draft-03)
111+
array(// #14 only set required values (draft-03)
105112
'{}',
106113
'{
107114
"properties": {
@@ -112,21 +119,36 @@ public function getValidTests()
112119
'{"propertyTwo":"valueTwo"}',
113120
Constraint::CHECK_MODE_ONLY_REQUIRED_DEFAULTS
114121
),
115-
array(// #16 infinite recursion via $ref (object)
122+
array(// #15 infinite recursion via $ref (object)
116123
'{}',
117124
'{"properties":{"propertyOne": {"$ref": "#","default": {}}}}',
118125
'{"propertyOne":{}}'
119126
),
120-
array(// #17 infinite recursion via $ref (array)
127+
array(// #16 infinite recursion via $ref (array)
121128
'[]',
122129
'{"items":[{"$ref":"#","default":[]}]}',
123130
'[[]]'
124131
),
125-
array(// #18 default value for null
132+
array(// #17 default top value does not overwrite defined null
126133
'null',
127134
'{"default":"valueOne"}',
128-
'"valueOne"'
129-
)
135+
'null'
136+
),
137+
array(// #18 default property value does not overwrite defined null
138+
'{"propertyOne":null}',
139+
'{"properties":{"propertyOne":{"default":"valueOne"}}}',
140+
'{"propertyOne":null}'
141+
),
142+
array(// #19 default value in an object is null
143+
'{}',
144+
'{"properties":{"propertyOne":{"default":null}}}',
145+
'{"propertyOne":null}'
146+
),
147+
array(// #20 default value in an array is null
148+
'[]',
149+
'{"items":[{"default":null}]}',
150+
'[null]'
151+
),
130152
);
131153
}
132154

@@ -180,16 +202,16 @@ public function testValidCasesUsingAssocWithoutTypeCast($input, $schema, $expect
180202

181203
public function testNoModificationViaReferences()
182204
{
183-
$input = json_decode('');
184-
$schema = json_decode('{"default":{"propertyOne":"valueOne"}}');
205+
$input = json_decode('{}');
206+
$schema = json_decode('{"properties":{"propertyOne":{"default":"valueOne"}}}');
185207

186208
$validator = new Validator();
187209
$validator->validate($input, $schema, Constraint::CHECK_MODE_TYPE_CAST | Constraint::CHECK_MODE_APPLY_DEFAULTS);
188210

189211
$this->assertEquals('{"propertyOne":"valueOne"}', json_encode($input));
190212

191213
$input->propertyOne = 'valueTwo';
192-
$this->assertEquals('valueOne', $schema->default->propertyOne);
214+
$this->assertEquals('valueOne', $schema->properties->propertyOne->default);
193215
}
194216

195217
public function testLeaveBasicTypesAlone()

0 commit comments

Comments
 (0)