Merge pull request #246 from willfarrell/feature/coerceArray

Add in ability to coerce an array
master
Evgeny Poberezkin 2016-07-27 09:17:49 +01:00 committed by GitHub
commit cdac201ac2
6 changed files with 120 additions and 34 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
@ -98,3 +98,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

@ -4,6 +4,10 @@
, $coerced = 'coerced' + $lvl;
}}
var {{=$dataType}} = typeof {{=$data}};
{{? it.opts.coerceTypes == 'array'}}
if ({{=$dataType}} == 'object' && Array.isArray({{=$data}})) {{=$dataType}} = 'array';
{{?}}
var {{=$coerced}} = undefined;
{{ var $bracesCoercion = ''; }}
@ -13,6 +17,14 @@
{{ $bracesCoercion += '}'; }}
{{?}}
{{? it.opts.coerceTypes == 'array' && $type != 'array' }}
if ({{=$dataType}} == 'array' && {{=$data}}.length == 1) {
{{=$coerced}} = {{=$data}} = {{=$data}}[0];
{{=$dataType}} = typeof {{=$data}};
/*if ({{=$dataType}} == 'object' && Array.isArray({{=$data}})) {{=$dataType}} = 'array';*/
}
{{?}}
{{? $type == 'string' }}
if ({{=$dataType}} == 'number' || {{=$dataType}} == 'boolean')
{{=$coerced}} = '' + {{=$data}};
@ -30,6 +42,9 @@
{{?? $type == 'null' }}
if ({{=$data}} === '' || {{=$data}} === 0 || {{=$data}} === false)
{{=$coerced}} = null;
{{?? it.opts.coerceTypes == 'array' && $type == 'array' }}
if ({{=$dataType}} == 'string' || {{=$dataType}} == 'number' || {{=$dataType}} == 'boolean' || {{=$data}} == null)
{{=$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

@ -22,7 +22,8 @@ var coercionRules = {
{ from: {}, to: undefined }
],
'array': [
{ from: [], to: undefined }
{ from: [], to: undefined },
{ from: [1], to: undefined }
]
},
'number': {
@ -45,7 +46,8 @@ var coercionRules = {
{ from: {}, to: undefined }
],
'array': [
{ from: [], to: undefined }
{ from: [], to: undefined },
{ from: [true], to: undefined }
]
},
'integer': {
@ -68,7 +70,8 @@ var coercionRules = {
{ from: {}, to: undefined }
],
'array': [
{ from: [], to: undefined }
{ from: [], to: undefined },
{ from: ['1'], to: undefined }
]
},
'boolean': {
@ -76,7 +79,7 @@ var coercionRules = {
{ from: 'false', to: false },
{ from: 'true', to: true },
{ from: '', to: undefined },
{ from: 'abc', to: undefined },
{ from: 'abc', to: undefined }
],
'number': [
{ from: 0, to: false },
@ -91,7 +94,8 @@ var coercionRules = {
{ from: {}, to: undefined }
],
'array': [
{ from: [], to: undefined }
{ from: [], to: undefined },
{ from: [0], to: undefined }
]
},
'null': {
@ -112,16 +116,8 @@ var coercionRules = {
{ from: {}, to: undefined }
],
'array': [
{ from: [], to: undefined }
]
},
'object': {
'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: 'array', from: [], to: undefined }
{ from: [], to: undefined },
{ from: [null], to: undefined }
]
},
'array': {
@ -132,9 +128,55 @@ var coercionRules = {
{ type: 'null', from: null, to: undefined },
{ type: 'object', from: {}, to: undefined }
]
},
'object': {
'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: 'array', from: [], to: undefined }
]
}
};
var coercionArrayRules = JSON.parse(JSON.stringify(coercionRules));
coercionArrayRules.string.array = [
{ from: ['abc'], to: 'abc' }
];
coercionArrayRules.number.array = [
{ from: [1.5], to: 1.5 }
];
coercionArrayRules.integer.array = [
{ from: [1], to: 1 }
];
coercionArrayRules.boolean.array = [
{ from: [true], to: true }
];
coercionArrayRules.null.array = [
{ from: [null], to: null }
];
coercionArrayRules.object.array = [
{ from: [{}], to: undefined }
];
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: undefined}
]
};
describe('Type coercion', function () {
var ajv, fullAjv, instances;
@ -147,18 +189,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 (coerceTypes = 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(toType, '.', fromType, test, schema, 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: {
@ -185,11 +240,11 @@ describe('Type coercion', function () {
function testCoercion(schema, fromData, toData) {
var valid = ajv.validate(schema, fromData);
// if (valid !== canCoerce) console.log(schema, fromData, toData);
//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 +303,7 @@ describe('Type coercion', function () {
var schema2 = {
type: 'array',
items: { type: 'number' }
}
};
instances.forEach(function (ajv) {
var data = { foo: '123', bar: 'bar' };
@ -330,15 +385,22 @@ 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];
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);
});
}