Skip to content

Commit 6df4e2b

Browse files
committed
Add option to apply default values from the schema
1 parent 325a0f8 commit 6df4e2b

File tree

6 files changed

+168
-1
lines changed

6 files changed

+168
-1
lines changed

README.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,38 @@ is_bool($request->processRefund); // true
7979
is_int($request->refundAmount); // true
8080
```
8181

82+
### Default Values
83+
84+
If your schema contains default values, you can have these automatically applied during validation:
85+
86+
```php
87+
<?php
88+
89+
use JsonSchema\Validator;
90+
use JsonSchema\Constraints\Constraint;
91+
92+
$request = (object)[
93+
'refundAmount'=>17
94+
];
95+
96+
$validator = new Validator();
97+
98+
$validator->coerceDefault($request, (object)[
99+
"type"=>"object",
100+
"properties"=>(object)[
101+
"processRefund"=>(object)[
102+
"type"=>"boolean",
103+
"default"=>true
104+
]
105+
]
106+
]); //validates, and sets defaults for missing properties
107+
108+
is_bool($request->processRefund); // true
109+
$request->processRefund; // true
110+
```
111+
112+
*Note that setting default values also enables type coercion.*
113+
82114
### With inline references
83115

84116
```php

src/JsonSchema/Constraints/Constraint.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ abstract class Constraint implements ConstraintInterface
2727

2828
const CHECK_MODE_NORMAL = 0x00000001;
2929
const CHECK_MODE_TYPE_CAST = 0x00000002;
30+
const CHECK_MODE_APPLY_DEFAULTS = 0x00000004;
3031

3132
/**
3233
* @var Factory

src/JsonSchema/Constraints/Factory.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
use JsonSchema\SchemaStorageInterface;
1515
use JsonSchema\Uri\UriRetriever;
1616
use JsonSchema\UriRetrieverInterface;
17+
use JsonSchema\Constraints\Constraint;
1718

1819
/**
1920
* Factory for centralize constraint initialization.
@@ -148,4 +149,18 @@ public function getCheckMode()
148149
{
149150
return $this->checkMode;
150151
}
152+
153+
/**
154+
* Update apply defaults setting in checkmode
155+
*
156+
* @param boolean $applyDefaults
157+
*/
158+
public function setApplyDefaults($applyDefaults = true)
159+
{
160+
if ($applyDefaults) {
161+
$this->checkMode |= Constraint::CHECK_MODE_APPLY_DEFAULTS;
162+
} else {
163+
$this->checkMode &= ~Constraint::CHECK_MODE_APPLY_DEFAULTS;
164+
}
165+
}
151166
}

src/JsonSchema/Constraints/UndefinedConstraint.php

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,8 @@ public function validateTypes(&$value, $schema = null, JsonPointer $path, $i = n
6666
}
6767

6868
// check object
69-
if ($this->getTypeCheck()->isObject($value)) {
69+
if (TypeCheck\LooseTypeCheck::isObject($value)) { // Fixes failing assoc tests for default values - currently investigating
70+
//if ($this->getTypeCheck()->isObject($value)) { // to find the root cause of this, noting all other assoc tests pass.
7071
$this->checkObject(
7172
$value,
7273
isset($schema->properties) ? $this->factory->getSchemaStorage()->resolveRefSchema($schema->properties) : $schema,
@@ -118,6 +119,20 @@ protected function validateCommonProperties(&$value, $schema = null, JsonPointer
118119
}
119120
}
120121

122+
// Apply default values
123+
if ($coerce && $this->factory->getCheckMode() & self::CHECK_MODE_APPLY_DEFAULTS && isset($schema->properties)) {
124+
$definition = $this->factory->getSchemaStorage()->resolveRefSchema($schema->properties);
125+
foreach ($definition as $i => $propertyDefinition) {
126+
if (isset($propertyDefinition->default)) {
127+
if ($this->getTypeCheck()->isObject($value) && !property_exists($value, $i)) {
128+
$value->$i = $propertyDefinition->default;
129+
} elseif ($this->getTypeCheck()->isArray($value) && !array_key_exists($i, $value)) {
130+
$value[$i] = $propertyDefinition->default;
131+
}
132+
}
133+
}
134+
}
135+
121136
// Verify required values
122137
if ($this->getTypeCheck()->isObject($value)) {
123138
if (!($value instanceof UndefinedConstraint) && isset($schema->required) && is_array($schema->required)) {

src/JsonSchema/Validator.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,4 +52,17 @@ public function coerce(&$value, $schema = null, JsonPointer $path = null, $i = n
5252

5353
$this->addErrors(array_unique($validator->getErrors(), SORT_REGULAR));
5454
}
55+
56+
/**
57+
* Does everything that coerce does, but will also set values to their default, if the value is not
58+
* set and a default is available in the schema. Note that the first argumen is passwd by
59+
* reference, so you must pass in a variable.
60+
*
61+
* {@inheritDoc}
62+
*/
63+
public function coerceDefault(&$value, $schema = null, JsonPointer $path = null, $i = null)
64+
{
65+
$this->factory->setApplyDefaults(true);
66+
$this->coerce($value, $schema, $path, $i);
67+
}
5568
}
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the JsonSchema package.
5+
*
6+
* For the full copyright and license information, please view the LICENSE
7+
* file that was distributed with this source code.
8+
*/
9+
10+
namespace JsonSchema\Tests\Constraints;
11+
use JsonSchema\SchemaStorage;
12+
use JsonSchema\Validator;
13+
use JsonSchema\Constraints\Constraint;
14+
use JsonSchema\Constraints\Factory;
15+
16+
class DefaultPropertiesTest extends VeryBaseTestCase
17+
{
18+
public function getValidTests()
19+
{
20+
return [
21+
[ // default value for top-level property
22+
'{"propertyOne":"valueOne"}',
23+
'{"properties":{"propertyTwo":{"default":"valueTwo"}}}',
24+
'{"propertyOne":"valueOne","propertyTwo":"valueTwo"}'
25+
],
26+
[ // default value for sub-property
27+
'{"propertyOne":{}}',
28+
'{"properties":{"propertyOne":{"properties":{"propertyTwo":{"default":"valueTwo"}}}}}',
29+
'{"propertyOne":{"propertyTwo":"valueTwo"}}'
30+
],
31+
[ // default value for sub-property with sibling
32+
'{"propertyOne":{"propertyTwo":"valueTwo"}}',
33+
'{"properties":{"propertyOne":{"properties":{"propertyThree":{"default":"valueThree"}}}}}',
34+
'{"propertyOne":{"propertyTwo":"valueTwo","propertyThree":"valueThree"}}'
35+
],
36+
[ // default value for top-level property with type check
37+
'{"propertyOne":"valueOne"}',
38+
'{"properties":{"propertyTwo":{"default":"valueTwo","type":"string"}}}',
39+
'{"propertyOne":"valueOne","propertyTwo":"valueTwo"}'
40+
],
41+
[ // default value for top-level property with v3 required check
42+
'{"propertyOne":"valueOne"}',
43+
'{"properties":{"propertyTwo":{"default":"valueTwo","required":"true"}}}',
44+
'{"propertyOne":"valueOne","propertyTwo":"valueTwo"}'
45+
],
46+
[ // default value for top-level property with v4 required check
47+
'{"propertyOne":"valueOne"}',
48+
'{"properties":{"propertyTwo":{"default":"valueTwo"}},"required":["propertyTwo"]}',
49+
'{"propertyOne":"valueOne","propertyTwo":"valueTwo"}'
50+
],
51+
[ //default value for an already set property
52+
'{"propertyOne":"alreadySetValueOne"}',
53+
'{"properties":{"propertyOne":{"default":"valueOne"}}}',
54+
'{"propertyOne":"alreadySetValueOne"}'
55+
]
56+
];
57+
}
58+
59+
/**
60+
* @dataProvider getValidTests
61+
*/
62+
public function testValidCases($input, $schema, $expectOutput = null)
63+
{
64+
global $assoc;
65+
if (is_string($input)) {
66+
$assoc = false;
67+
$inputDecoded = json_decode($input);
68+
} else {
69+
$assoc = true;
70+
$inputDecoded = $input;
71+
}
72+
73+
$validator = new Validator();
74+
$validator->coerceDefault($inputDecoded, json_decode($schema));
75+
76+
$this->assertTrue($validator->isValid(), print_r($validator->getErrors(), true));
77+
78+
if($expectOutput !== null)
79+
$this->assertEquals($expectOutput, json_encode($inputDecoded));
80+
}
81+
82+
/**
83+
* @dataProvider getValidTests
84+
*/
85+
public function testValidCasesUsingAssoc($input, $schema, $expectOutput = null)
86+
{
87+
$input = json_decode($input, true);
88+
self::testValidCases($input, $schema, $expectOutput);
89+
}
90+
91+
}

0 commit comments

Comments
 (0)