useDefaults: "empty" (#916)

* test: new value "empty" for useDefaults option

* feat: option useDefaults: "empty", closes #912
master
Evgeny Poberezkin 2019-01-03 21:47:28 +00:00 committed by GitHub
parent 9c0a36515e
commit 95edb4958d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 83 additions and 34 deletions

View File

@ -699,13 +699,11 @@ The schema above is also more efficient - it will compile into a faster function
With [option `useDefaults`](#options) Ajv will assign values from `default` keyword in the schemas of `properties` and `items` (when it is the array of schemas) to the missing properties and items.
With the option value `"empty"` properties and items equal to `null` or `""` (empty string) will be considered missing and assigned defaults.
This option modifies original data.
__Please note__: by default the default value is inserted in the generated validation code as a literal (starting from v4.0), so the value inserted in the data will be the deep clone of the default in the schema.
If you need to insert the default value in the data by reference pass the option `useDefaults: "shared"`.
Inserting defaults by reference can be faster (in case you have an object in `default`) and it allows to have dynamic values in defaults, e.g. timestamp, without recompiling the schema. The side effect is that modifying the default value in any validated data instance will change the default in the schema and in other validated data instances. See example 3 below.
__Please note__: the default value is inserted in the generated validation code as a literal, so the value inserted in the data will be the deep clone of the default in the schema.
Example 1 (`default` in `properties`):
@ -748,32 +746,6 @@ console.log(validate(data)); // true
console.log(data); // [ 1, "foo" ]
```
Example 3 (inserting "defaults" by reference):
```javascript
var ajv = new Ajv({ useDefaults: 'shared' });
var schema = {
properties: {
foo: {
default: { bar: 1 }
}
}
}
var validate = ajv.compile(schema);
var data = {};
console.log(validate(data)); // true
console.log(data); // { foo: { bar: 1 } }
data.foo.bar = 2;
var data2 = {};
console.log(validate(data2)); // true
console.log(data2); // { foo: { bar: 2 } }
```
`default` keywords in other cases are ignored:
- not in `properties` or `items` subschemas
@ -1115,8 +1087,9 @@ Defaults:
- `"failing"` - additional properties that fail schema validation will be removed (where `additionalProperties` keyword is `false` or schema).
- _useDefaults_: replace missing properties and items with the values from corresponding `default` keywords. Default behaviour is to ignore `default` keywords. This option is not used if schema is added with `addMetaSchema` method. See examples in [Assigning defaults](#assigning-defaults). Option values:
- `false` (default) - do not use defaults
- `true` - insert defaults by value (safer and slower, object literal is used).
- `"shared"` - insert defaults by reference (faster). If the default is an object, it will be shared by all instances of validated data. If you modify the inserted default in the validated data, it will be modified in the schema as well.
- `true` - insert defaults by value (object literal is used).
- `"empty"` - use defaults for properties and items that are present and equal to `null` or `""` (an empty string).
- `"shared"` (deprecated) - insert defaults by reference. If the default is an object, it will be shared by all instances of validated data. If you modify the inserted default in the validated data, it will be modified in the schema as well.
- _coerceTypes_: change data type of data to match `type` keyword. See the example in [Coercing data types](#coercing-data-types) and [coercion rules](https://github.com/epoberezkin/ajv/blob/master/COERCION.md). Option values:
- `false` (default) - no type coercion.
- `true` - coerce scalar data types.

View File

@ -1,5 +1,10 @@
{{## def.assignDefault:
if ({{=$passData}} === undefined)
if ({{=$passData}} === undefined
{{? it.opts.useDefaults == 'empty' }}
|| {{=$passData}} === null
|| {{=$passData}} === ''
{{?}}
)
{{=$passData}} = {{? it.opts.useDefaults == 'shared' }}
{{= it.useDefault($sch.default) }}
{{??}}

View File

@ -695,6 +695,77 @@ describe('Ajv Options', function () {
throw new Error('unknown useDefaults mode');
}
});
describe('defaults with "empty" values', function() {
var schema, data;
beforeEach(function() {
schema = {
properties: {
obj: {
properties: {
str: {default: 'foo'},
n1: {default: 1},
n2: {default: 2},
n3: {default: 3}
}
},
arr: {
items: [
{default: 'foo'},
{default: 1},
{default: 2},
{default: 3}
]
}
}
};
data = {
obj: {
str: '',
n1: null,
n2: undefined
},
arr: ['', null, undefined]
};
});
it('should NOT assign defaults when useDefaults is true/"shared"', function() {
test(new Ajv({useDefaults: true}));
test(new Ajv({useDefaults: 'shared'}));
function test(ajv) {
var validate = ajv.compile(schema);
validate(data) .should.equal(true);
data .should.eql({
obj: {
str: '',
n1: null,
n2: 2,
n3: 3
},
arr: ['', null, 2, 3]
});
}
});
it('should assign defaults when useDefaults = "empty"', function() {
var ajv = new Ajv({useDefaults: 'empty'});
var validate = ajv.compile(schema);
validate(data) .should.equal(true);
data .should.eql({
obj: {
str: 'foo',
n1: 1,
n2: 2,
n3: 3
},
arr: ['foo', 1, 2, 3]
});
});
});
});