Add in ability to coerce to an array
parent
b7535d4a69
commit
9e18e68ce7
10
COERCION.md
10
COERCION.md
|
@ -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 type →<br>to type ↓|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" ]`
|
|
@ -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;
|
||||
|
|
|
@ -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'];
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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}}];
|
||||
{{?}}
|
||||
{{~}}
|
||||
|
||||
|
|
|
@ -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 }}
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue