diff --git a/CHANGELOG.md b/CHANGELOG.md index b5536749..949bd104 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,7 +33,6 @@ It was time to do a full review and refactoring, which results in: - **`type` now required for array, object, const and enum validation schemas** - `JSONSchemaNull` removed (useless, `null` doesn't require any validation) -- `items` in arrays schemas no longer accepts an array of JSON schemas - `JSONSchema` no longer accepts extra properties - `getUnsafeItem()` is removed (was already deprecated in v7) diff --git a/docs/MIGRATION_TO_V8.md b/docs/MIGRATION_TO_V8.md index 996f379a..f660caeb 100644 --- a/docs/MIGRATION_TO_V8.md +++ b/docs/MIGRATION_TO_V8.md @@ -67,9 +67,6 @@ this.localStorage.getItem('test', { }) ``` -Also, `items` no longer accepts an array of JSON schemas, meaning arrays with multiple types -are no longer possible (and it's better for consistency, use an object if you mix types in a list). - ### Validation of objects **`type` option is now required.** diff --git a/docs/VALIDATION.md b/docs/VALIDATION.md index d9181854..ae9b5bc6 100644 --- a/docs/VALIDATION.md +++ b/docs/VALIDATION.md @@ -56,33 +56,52 @@ this.localStorage.getItem('test', { type: 'string' }) ```typescript this.localStorage.getItem('test', { type: 'array', - items: { type: 'boolean' } + items: { type: 'boolean' }, }) ``` ```typescript this.localStorage.getItem('test', { type: 'array', - items: { type: 'integer' } + items: { type: 'integer' }, }) ``` ```typescript this.localStorage.getItem('test', { type: 'array', - items: { type: 'number' } + items: { type: 'number' }, }) ``` ```typescript this.localStorage.getItem('test', { type: 'array', - items: { type: 'string' } + items: { type: 'string' }, }) ``` What's expected in `items` is another JSON schema. +## Tuples + +In most cases, an array is for a list with values of the *same type*. +In special cases, it can be useful to use arrays with values of different types. +It's called tuples in TypeScript. For example: `['test', 1]` + +```typescript +this.localStorage.getItem('test', { + type: 'array', + items: [ + { type: 'string' }, + { type: 'number' }, + ], +}) +``` + +Note a tuple has a fixed length: the number of values in the array and the number of schemas provided in `items` +must be exactly the same, otherwise the validation fails. + ## How to validate objects For example: diff --git a/projects/ngx-pwa/local-storage/src/lib/validation/json-schema.ts b/projects/ngx-pwa/local-storage/src/lib/validation/json-schema.ts index b296339f..2730fa7a 100644 --- a/projects/ngx-pwa/local-storage/src/lib/validation/json-schema.ts +++ b/projects/ngx-pwa/local-storage/src/lib/validation/json-schema.ts @@ -166,9 +166,9 @@ export interface JSONSchemaArray { type: 'array'; /** - * Schema for the values of an array. + * Schema for the values of an array, or array of schemas for a tuple. */ - items: JSONSchema; + items: JSONSchema | JSONSchema[]; /** * Check if an array length is lower or equal to this value. diff --git a/projects/ngx-pwa/local-storage/src/lib/validation/json-validation.spec.ts b/projects/ngx-pwa/local-storage/src/lib/validation/json-validation.spec.ts index 8d269ba6..02d83f43 100644 --- a/projects/ngx-pwa/local-storage/src/lib/validation/json-validation.spec.ts +++ b/projects/ngx-pwa/local-storage/src/lib/validation/json-validation.spec.ts @@ -534,6 +534,42 @@ describe(`JSONValidator`, () => { }); + describe('tuple', () => { + + it(`valid`, () => { + + const test = jsonValidator.validate(['test1', 1], { type: 'array', items: [{ type: 'string' }, { type: 'number' }] }); + + expect(test).toBe(true); + + }); + + it(`invalid`, () => { + + const test = jsonValidator.validate(['test1', 'test'], { type: 'array', items: [{ type: 'string' }, { type: 'number' }] }); + + expect(test).toBe(false); + + }); + + it(`special case: greater length`, () => { + + const test = jsonValidator.validate(['test1', 1, 2], { type: 'array', items: [{ type: 'string' }, { type: 'number' }] }); + + expect(test).toBe(false); + + }); + + it(`special case: lower length`, () => { + + const test = jsonValidator.validate(['test1'], { type: 'array', items: [{ type: 'string' }, { type: 'number' }] }); + + expect(test).toBe(false); + + }); + + }); + describe('arrays items', () => { it(`valid`, () => { diff --git a/projects/ngx-pwa/local-storage/src/lib/validation/json-validator.ts b/projects/ngx-pwa/local-storage/src/lib/validation/json-validator.ts index 8928ad33..4d9e0d3c 100644 --- a/projects/ngx-pwa/local-storage/src/lib/validation/json-validator.ts +++ b/projects/ngx-pwa/local-storage/src/lib/validation/json-validator.ts @@ -41,6 +41,7 @@ export class JSONValidator { * Validate a string * @param data Data to validate * @param schema Schema describing the string + * @returns If data is valid: `true`, if it is invalid: `false` */ private validateString(data: any, schema: JSONSchemaString): boolean { @@ -86,6 +87,7 @@ export class JSONValidator { * Validate a number or an integer * @param data Data to validate * @param schema Schema describing the number or integer + * @returns If data is valid: `true`, if it is invalid: `false` */ private validateNumber(data: any, schema: JSONSchemaNumber | JSONSchemaInteger): boolean { @@ -136,6 +138,7 @@ export class JSONValidator { * Validate a boolean * @param data Data to validate * @param schema Schema describing the boolean + * @returns If data is valid: `true`, if it is invalid: `false` */ private validateBoolean(data: any, schema: JSONSchemaBoolean): boolean { @@ -155,6 +158,7 @@ export class JSONValidator { * Validate an array * @param data Data to validate * @param schema Schema describing the array + * @returns If data is valid: `true`, if it is invalid: `false` */ private validateArray(data: any[], schema: JSONSchemaArray): boolean { @@ -181,6 +185,13 @@ export class JSONValidator { } + /* Specific test for tuples */ + if (Array.isArray(schema.items)) { + + return this.validateTuple(data, schema.items); + + } + /* Validate all the values in array */ for (const value of data) { @@ -194,10 +205,38 @@ export class JSONValidator { } + /** + * Validate a tuple (array with fixed length and multiple types) + * @param data Data to validate + * @param schemas Schemas describing the tuple + * @returns If data is valid: `true`, if it is invalid: `false` + */ + private validateTuple(data: any[], schemas: JSONSchema[]): boolean { + + /* Tuples have a fixed length */ + if (data.length !== schemas.length) { + + return false; + + } + + for (let i = 0; i < schemas.length; i += 1) { + + if (!this.validate(data[i], schemas[i])) { + return false; + } + + } + + return true; + + } + /** * Validate an object * @param data Data to validate * @param schema JSON schema describing the object + * @returns If data is valid: `true`, if it is invalid: `false` */ private validateObject(data: { [k: string]: any; }, schema: JSONSchemaObject): boolean { @@ -248,6 +287,7 @@ export class JSONValidator { * Validate a constant * @param data Data ta validate * @param schema JSON schema describing the constant + * @returns If data is valid: `true`, if it is invalid: `false` */ private validateConst(data: any, schema: JSONSchemaBoolean | JSONSchemaInteger | JSONSchemaNumber | JSONSchemaString): boolean { @@ -263,6 +303,7 @@ export class JSONValidator { * Validate an enum * @param data Data ta validate * @param schema JSON schema describing the enum + * @returns If data is valid: `true`, if it is invalid: `false` */ private validateEnum(data: any, schema: JSONSchemaInteger | JSONSchemaNumber | JSONSchemaString): boolean {