Add in ability to coerce to an array

master
will Farrell 2016-07-22 18:07:10 -06:00
parent b7535d4a69
commit 9e18e68ce7
6 changed files with 105 additions and 50 deletions

View File

@ -1,6 +1,6 @@
# Ajv type coercion rules
To enable type coercion pass option `coerceTypes` to Ajv (it is `false` by default). See [example](https://github.com/epoberezkin/ajv#coercing-data-types).
To enable type coercion pass option `coerceTypes` to Ajv with `true` or `array` (it is `false` by default). See [example](https://github.com/epoberezkin/ajv#coercing-data-types).
The coercion rules are different from JavaScript:
- to validate user input as expected
@ -11,6 +11,8 @@ Type coercion only happens if there is `type` keyword and if without coercion th
If there are multiple types allowed in `type` keyword the coercion will only happen if none of the types match the data and some of the scalar types are present (coercion to/from `object`/`array` is not possible). In this case the validating function will try coercing the data to each type in order until some of them succeeds.
If `coerceTypes` is set to `array`
Possible type coercions:
|from&nbsp;type&nbsp;&rarr;<br>to&nbsp;type&nbsp;&darr;|string|number|boolean|null|
@ -98,3 +100,9 @@ Unlike JavaScript, only these numbers can be coerced to `boolean`:
#### To boolean type
`null` coerces to `false`
## Coersion from array
Wraps and non-array input in an array.
- `"foo"` -> `[ "foo" ]`

2
lib/ajv.d.ts vendored
View File

@ -110,7 +110,7 @@ declare namespace ajv {
loadSchema?: (uri: string, cb: (err: Error, schema: Object) => any) => any;
removeAdditional?: boolean | string;
useDefaults?: boolean | string;
coerceTypes?: boolean;
coerceTypes?: boolean | string;
async?: boolean | string;
transpile?: string | ((code: string) => string);
meta?: boolean | Object;

View File

@ -75,16 +75,19 @@ function checkDataTypes(dataTypes, data) {
var COERCE_TO_TYPES = toHash([ 'string', 'number', 'integer', 'boolean', 'null' ]);
function coerceToTypes(dataTypes) {
function coerceToTypes(optionCoerceTypes, dataTypes) {
if (Array.isArray(dataTypes)) {
var types = [];
for (var i=0; i<dataTypes.length; i++) {
var t = dataTypes[i];
if (COERCE_TO_TYPES[t]) types[types.length] = t;
else if (optionCoerceTypes === 'array' && t === 'array') types[types.length] = t;
}
if (types.length) return types;
} else if (COERCE_TO_TYPES[dataTypes]) {
return [dataTypes];
} else if (optionCoerceTypes === 'array' && dataTypes === 'array') {
return ['array'];
}
}

View File

@ -13,6 +13,7 @@
{{ $bracesCoercion += '}'; }}
{{?}}
{{? $type == 'string' }}
if ({{=$dataType}} == 'number' || {{=$dataType}} == 'boolean')
{{=$coerced}} = '' + {{=$data}};
@ -30,6 +31,9 @@
{{?? $type == 'null' }}
if ({{=$data}} === '' || {{=$data}} === 0 || {{=$data}} === false)
{{=$coerced}} = null;
{{?? $type === 'array' }}
if ({{=$dataType}} !== 'array')
{{=$coerced}} = [{{=$data}}];
{{?}}
{{~}}

View File

@ -84,7 +84,7 @@
#}}
{{? $typeSchema && it.opts.coerceTypes }}
{{ var $coerceToTypes = it.util.coerceToTypes($typeSchema); }}
{{ var $coerceToTypes = it.util.coerceToTypes(it.opts.coerceTypes, $typeSchema); }}
{{? $coerceToTypes }}
{{# def.checkType }}
{{# def.coerceType }}

View File

@ -71,29 +71,29 @@ var coercionRules = {
{ from: [], to: undefined }
]
},
'boolean': {
'string': [
{ from: 'false', to: false },
{ from: 'true', to: true },
{ from: '', to: undefined },
{ from: 'abc', to: undefined },
],
'number': [
{ from: 0, to: false },
{ from: 1, to: true },
{ from: 2, to: undefined },
{ from: 2.5, to: undefined }
],
'null': [
{ from: null, to: false }
],
'object': [
{ from: {}, to: undefined }
],
'array': [
{ from: [], to: undefined }
]
},
'boolean': {
'string': [
{ from: 'false', to: false },
{ from: 'true', to: true },
{ from: '', to: undefined },
{ from: 'abc', to: undefined },
],
'number': [
{ from: 0, to: false },
{ from: 1, to: true },
{ from: 2, to: undefined },
{ from: 2.5, to: undefined }
],
'null': [
{ from: null, to: false }
],
'object': [
{ from: {}, to: undefined }
],
'array': [
{ from: [], to: undefined }
]
},
'null': {
'string': [
{ from: '', to: null },
@ -115,6 +115,15 @@ var coercionRules = {
{ from: [], to: undefined }
]
},
'array': {
'all': [
{ type: 'string', from: 'abc', to: undefined },
{ type: 'number', from: 1, to: undefined },
{ type: 'boolean', from: true, to: undefined },
{ type: 'null', from: null, to: undefined },
{ type: 'object', from: {}, to: undefined }
]
},
'object': {
'all': [
{ type: 'string', from: 'abc', to: undefined },
@ -123,18 +132,28 @@ var coercionRules = {
{ type: 'null', from: null, to: undefined },
{ type: 'array', from: [], to: undefined }
]
},
'array': {
'all': [
{ type: 'string', from: 'abc', to: undefined },
{ type: 'number', from: 1, to: undefined },
{ type: 'boolean', from: true, to: undefined },
{ type: 'null', from: null, to: undefined },
{ type: 'object', from: {}, to: undefined }
]
}
};
var coercionArrayRules = {
'array': {
'string': [
{ from: 'abc', to: ['abc'] }
],
'number': [
{ from: 1, to: [1] }
],
'boolean': [
{ from: true, to: [true] }
],
'null': [
{ from: null, to: [null] }
],
'object': [
{ from: {}, to: [{}] }
]
}
};
describe('Type coercion', function () {
var ajv, fullAjv, instances;
@ -147,18 +166,31 @@ describe('Type coercion', function () {
it('should coerce scalar values', function() {
testRules(function (test, schema, canCoerce, toType, fromType) {
testRules(coercionRules, function (test, schema, canCoerce, toType, fromType) {
instances.forEach(function (ajv) {
var valid = ajv.validate(schema, test.from);
// if (valid !== canCoerce) console.log(toType, fromType, test, ajv.errors);
//if (valid !== canCoerce) console.log('true', toType, fromType, test, ajv.errors);
valid. should.equal(canCoerce);
});
})
});
});
it('should coerce scalar values in array', function() {
ajv = new Ajv({ coerceTypes: 'array', verbose: true });
fullAjv = new Ajv({ coerceTypes: 'array', verbose: true, allErrors: true });
instances = [ ajv, fullAjv ];
testRules(coercionArrayRules, function (test, schema, canCoerce, toType, fromType) {
instances.forEach(function (ajv) {
var valid = ajv.validate(schema, test.from);
//if (valid !== canCoerce) console.log('array', toType, fromType, test, ajv.errors);
valid. should.equal(canCoerce);
});
});
});
it('should coerce values in objects/arrays and update properties/items', function() {
testRules(function (test, schema, canCoerce, toType, fromType) {
testRules(coercionRules, function (test, schema, canCoerce, toType, fromType) {
var schemaObject = {
type: 'object',
properties: {
@ -187,9 +219,9 @@ describe('Type coercion', function () {
var valid = ajv.validate(schema, fromData);
// if (valid !== canCoerce) console.log(schema, fromData, toData);
valid. should.equal(canCoerce);
if (valid) fromData .should.eql(toData);
if (valid) fromData.should.eql(toData);
}
})
});
});
@ -248,7 +280,7 @@ describe('Type coercion', function () {
var schema2 = {
type: 'array',
items: { type: 'number' }
}
};
instances.forEach(function (ajv) {
var data = { foo: '123', bar: 'bar' };
@ -330,15 +362,23 @@ describe('Type coercion', function () {
});
function testRules(cb) {
for (var toType in coercionRules) {
for (var fromType in coercionRules[toType]) {
var tests = coercionRules[toType][fromType];
function testRules(rules, cb) {
for (var toType in rules) {
for (var fromType in rules[toType]) {
var tests = rules[toType][fromType];
//if (toType === 'array') { console.log(toType, fromType, tests); }
tests.forEach(function (test) {
var canCoerce = test.to !== undefined;
var schema = canCoerce
? { type: toType, "enum": [ test.to ] }
: { type: toType };
var schema;
if (canCoerce) {
if (Array.isArray(test.to)) {
schema = {type: toType, "items": { "type": fromType, "enum":[test.from] }};
} else {
schema = {type: toType, "enum": [test.to]};
}
} else {
schema = { type: toType };
}
cb(test, schema, canCoerce, toType, fromType);
});
}