diff --git a/src/JsonSchema/Constraints/UndefinedConstraint.php b/src/JsonSchema/Constraints/UndefinedConstraint.php index 67afbe60..ba624566 100644 --- a/src/JsonSchema/Constraints/UndefinedConstraint.php +++ b/src/JsonSchema/Constraints/UndefinedConstraint.php @@ -239,7 +239,7 @@ protected function applyDefaultValues(&$value, $schema, $path) foreach ($schema->properties as $currentProperty => $propertyDefinition) { if ( !LooseTypeCheck::propertyExists($value, $currentProperty) - && isset($propertyDefinition->default) + && property_exists($propertyDefinition, 'default') && $this->shouldApplyDefaultValue($requiredOnly, $propertyDefinition, $currentProperty, $schema) ) { // assign default value @@ -255,8 +255,8 @@ protected function applyDefaultValues(&$value, $schema, $path) // $value is an array, and items are defined - treat as plain array foreach ($schema->items as $currentItem => $itemDefinition) { if ( - !isset($value[$currentItem]) - && isset($itemDefinition->default) + !array_key_exists($currentItem, $value) + && property_exists($itemDefinition, 'default') && $this->shouldApplyDefaultValue($requiredOnly, $itemDefinition)) { if (is_object($itemDefinition->default)) { $value[$currentItem] = clone $itemDefinition->default; @@ -267,8 +267,8 @@ protected function applyDefaultValues(&$value, $schema, $path) $path->setFromDefault(); } } elseif ( - ($value instanceof self || $value === null) - && isset($schema->default) + $value instanceof self + && property_exists($schema, 'default') && $this->shouldApplyDefaultValue($requiredOnly, $schema)) { // $value is a leaf, not a container - apply the default directly $value = is_object($schema->default) ? clone $schema->default : $schema->default; diff --git a/tests/Constraints/DefaultPropertiesTest.php b/tests/Constraints/DefaultPropertiesTest.php index 508e284b..a3b9c4e2 100644 --- a/tests/Constraints/DefaultPropertiesTest.php +++ b/tests/Constraints/DefaultPropertiesTest.php @@ -19,77 +19,84 @@ class DefaultPropertiesTest extends VeryBaseTestCase public function getValidTests() { return array( + /* + // This test case was intended to check whether a default value can be applied for the + // entire object, however testing this case is impossible, because there is no way to + // distinguish between a deliberate top-level NULL and a top level that contains nothing. + // As such, the assumption is that a top-level NULL is deliberate, and should not be + // altered by replacing it with a default value. array(// #0 default value for entire object '', '{"default":"valueOne"}', '"valueOne"' ), - array(// #1 default value in an empty object + */ + array(// #0 default value in an empty object '{}', '{"properties":{"propertyOne":{"default":"valueOne"}}}', '{"propertyOne":"valueOne"}' ), - array(// #2 default value for top-level property + array(// #1 default value for top-level property '{"propertyOne":"valueOne"}', '{"properties":{"propertyTwo":{"default":"valueTwo"}}}', '{"propertyOne":"valueOne","propertyTwo":"valueTwo"}' ), - array(// #3 default value for sub-property + array(// #2 default value for sub-property '{"propertyOne":{}}', '{"properties":{"propertyOne":{"properties":{"propertyTwo":{"default":"valueTwo"}}}}}', '{"propertyOne":{"propertyTwo":"valueTwo"}}' ), - array(// #4 default value for sub-property with sibling + array(// #3 default value for sub-property with sibling '{"propertyOne":{"propertyTwo":"valueTwo"}}', '{"properties":{"propertyOne":{"properties":{"propertyThree":{"default":"valueThree"}}}}}', '{"propertyOne":{"propertyTwo":"valueTwo","propertyThree":"valueThree"}}' ), - array(// #5 default value for top-level property with type check + array(// #4 default value for top-level property with type check '{"propertyOne":"valueOne"}', '{"properties":{"propertyTwo":{"default":"valueTwo","type":"string"}}}', '{"propertyOne":"valueOne","propertyTwo":"valueTwo"}' ), - array(// #6 default value for top-level property with v3 required check + array(// #5 default value for top-level property with v3 required check '{"propertyOne":"valueOne"}', '{"properties":{"propertyTwo":{"default":"valueTwo","required":"true"}}}', '{"propertyOne":"valueOne","propertyTwo":"valueTwo"}' ), - array(// #7 default value for top-level property with v4 required check + array(// #6 default value for top-level property with v4 required check '{"propertyOne":"valueOne"}', '{"properties":{"propertyTwo":{"default":"valueTwo"}},"required":["propertyTwo"]}', '{"propertyOne":"valueOne","propertyTwo":"valueTwo"}' ), - array(// #8 default value for an already set property + array(// #7 default value for an already set property '{"propertyOne":"alreadySetValueOne"}', '{"properties":{"propertyOne":{"default":"valueOne"}}}', '{"propertyOne":"alreadySetValueOne"}' ), - array(// #9 default item value for an array + array(// #8 default item value for an array '["valueOne"]', '{"type":"array","items":[{},{"type":"string","default":"valueTwo"}]}', '["valueOne","valueTwo"]' ), - array(// #10 default item value for an empty array + array(// #9 default item value for an empty array '[]', '{"type":"array","items":[{"type":"string","default":"valueOne"}]}', '["valueOne"]' ), - array(// #11 property without a default available + array(// #10 property without a default available '{"propertyOne":"alreadySetValueOne"}', '{"properties":{"propertyOne":{"type":"string"}}}', '{"propertyOne":"alreadySetValueOne"}' ), - array(// #12 default property value is an object + array(// #11 default property value is an object '{"propertyOne":"valueOne"}', '{"properties":{"propertyTwo":{"default":{}}}}', '{"propertyOne":"valueOne","propertyTwo":{}}' ), - array(// #13 default item value is an object + array(// #12 default item value is an object '[]', '{"type":"array","items":[{"default":{}}]}', '[{}]' ), - array(// #14 only set required values (draft-04) + array(// #13 only set required values (draft-04) '{}', '{ "properties": { @@ -101,7 +108,7 @@ public function getValidTests() '{"propertyTwo":"valueTwo"}', Constraint::CHECK_MODE_ONLY_REQUIRED_DEFAULTS ), - array(// #15 only set required values (draft-03) + array(// #14 only set required values (draft-03) '{}', '{ "properties": { @@ -112,21 +119,36 @@ public function getValidTests() '{"propertyTwo":"valueTwo"}', Constraint::CHECK_MODE_ONLY_REQUIRED_DEFAULTS ), - array(// #16 infinite recursion via $ref (object) + array(// #15 infinite recursion via $ref (object) '{}', '{"properties":{"propertyOne": {"$ref": "#","default": {}}}}', '{"propertyOne":{}}' ), - array(// #17 infinite recursion via $ref (array) + array(// #16 infinite recursion via $ref (array) '[]', '{"items":[{"$ref":"#","default":[]}]}', '[[]]' ), - array(// #18 default value for null + array(// #17 default top value does not overwrite defined null 'null', '{"default":"valueOne"}', - '"valueOne"' - ) + 'null' + ), + array(// #18 default property value does not overwrite defined null + '{"propertyOne":null}', + '{"properties":{"propertyOne":{"default":"valueOne"}}}', + '{"propertyOne":null}' + ), + array(// #19 default value in an object is null + '{}', + '{"properties":{"propertyOne":{"default":null}}}', + '{"propertyOne":null}' + ), + array(// #20 default value in an array is null + '[]', + '{"items":[{"default":null}]}', + '[null]' + ), ); } @@ -180,8 +202,8 @@ public function testValidCasesUsingAssocWithoutTypeCast($input, $schema, $expect public function testNoModificationViaReferences() { - $input = json_decode(''); - $schema = json_decode('{"default":{"propertyOne":"valueOne"}}'); + $input = json_decode('{}'); + $schema = json_decode('{"properties":{"propertyOne":{"default":"valueOne"}}}'); $validator = new Validator(); $validator->validate($input, $schema, Constraint::CHECK_MODE_TYPE_CAST | Constraint::CHECK_MODE_APPLY_DEFAULTS); @@ -189,7 +211,7 @@ public function testNoModificationViaReferences() $this->assertEquals('{"propertyOne":"valueOne"}', json_encode($input)); $input->propertyOne = 'valueTwo'; - $this->assertEquals('valueOne', $schema->default->propertyOne); + $this->assertEquals('valueOne', $schema->properties->propertyOne->default); } public function testLeaveBasicTypesAlone()