diff --git a/CHANGELOG.md b/CHANGELOG.md index da1cc799..54c0f46c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - Add required permissions for welcome action ([#789](https://github.com/jsonrainbow/json-schema/pull/789)) - Upgrade php cs fixer to latest ([#783](https://github.com/jsonrainbow/json-schema/pull/783)) +- Create deep copy before checking each sub schema in oneOf ([#791](https://github.com/jsonrainbow/json-schema/pull/791)) ### Changed - Used PHPStan's int-mask-of type where applicable ([#779](https://github.com/jsonrainbow/json-schema/pull/779)) diff --git a/src/JsonSchema/Constraints/UndefinedConstraint.php b/src/JsonSchema/Constraints/UndefinedConstraint.php index 4d955cff..9b6c68ad 100644 --- a/src/JsonSchema/Constraints/UndefinedConstraint.php +++ b/src/JsonSchema/Constraints/UndefinedConstraint.php @@ -15,6 +15,7 @@ use JsonSchema\Constraints\TypeCheck\LooseTypeCheck; use JsonSchema\Entity\JsonPointer; use JsonSchema\Exception\ValidationException; +use JsonSchema\Tool\DeepCopy; use JsonSchema\Uri\UriResolver; /** @@ -352,14 +353,18 @@ protected function validateOfProperties(&$value, $schema, JsonPointer $path, $i if (isset($schema->oneOf)) { $allErrors = []; - $matchedSchemas = 0; + $matchedSchemas = []; $startErrors = $this->getErrors(); + $coerce = $this->factory->getConfig(self::CHECK_MODE_COERCE_TYPES); + foreach ($schema->oneOf as $oneOf) { try { $this->errors = []; - $this->checkUndefined($value, $oneOf, $path, $i); - if (count($this->getErrors()) == 0) { - $matchedSchemas++; + + $oneOfValue = $coerce ? DeepCopy::copyOf($value) : $value; + $this->checkUndefined($oneOfValue, $oneOf, $path, $i); + if (count($this->getErrors()) === 0) { + $matchedSchemas[] = ['schema' => $oneOf, 'value' => $oneOfValue]; } $allErrors = array_merge($allErrors, array_values($this->getErrors())); } catch (ValidationException $e) { @@ -367,11 +372,12 @@ protected function validateOfProperties(&$value, $schema, JsonPointer $path, $i // other schema options in the OneOf field. } } - if ($matchedSchemas !== 1) { + if (count($matchedSchemas) !== 1) { $this->addErrors(array_merge($allErrors, $startErrors)); $this->addError(ConstraintError::ONE_OF(), $path); } else { $this->errors = $startErrors; + $value = $matchedSchemas[0]['value']; } } } diff --git a/src/JsonSchema/Tool/DeepCopy.php b/src/JsonSchema/Tool/DeepCopy.php new file mode 100644 index 00000000..8b09156f --- /dev/null +++ b/src/JsonSchema/Tool/DeepCopy.php @@ -0,0 +1,38 @@ + + */ + public function getValidTests(): array + { + return [ + 'oneOf with type coercion should not affect value passed to each sub schema (#790)' => [ + 'input' => << << Constraint::CHECK_MODE_COERCE_TYPES + ] + ]; + } +} diff --git a/tests/Tool/DeepCopyTest.php b/tests/Tool/DeepCopyTest.php new file mode 100644 index 00000000..4dcfce76 --- /dev/null +++ b/tests/Tool/DeepCopyTest.php @@ -0,0 +1,53 @@ + 'bar']; + + $result = DeepCopy::copyOf($input); + + self::assertEquals($input, $result); + self::assertNotSame($input, $result); + } + + public function testCanDeepCopyObjectWithChildObject(): void + { + $child = (object) ['bar' => 'baz']; + $input = (object) ['foo' => $child]; + + $result = DeepCopy::copyOf($input); + + self::assertEquals($input, $result); + self::assertNotSame($input, $result); + self::assertEquals($input->foo, $result->foo); + self::assertNotSame($input->foo, $result->foo); + } + + public function testCanDeepCopyArray(): void + { + $input = ['foo' => 'bar']; + + $result = DeepCopy::copyOf($input); + + self::assertEquals($input, $result); + } + + public function testCanDeepCopyArrayWithNestedArray(): void + { + $nested = ['bar' => 'baz']; + $input = ['foo' => $nested]; + + $result = DeepCopy::copyOf($input); + + self::assertEquals($input, $result); + } +}