diff --git a/README.md b/README.md index bcf2be5..24afc89 100644 --- a/README.md +++ b/README.md @@ -118,3 +118,23 @@ isSubset( ```
You can find more examples in the unit tests. + +### Options +#### noAdditionalProperties +Add `additionalProperties=false` to all object types +```ts +import { createSchema } from 'genson-js'; +const schema = createSchema({ one: 1, two: 'second' }, { noAdditionalProperties: true }); +``` +The following schema will be created: +```ts +{ + type: 'object', + additionalProperties: false, + properties: { one: { type: 'integer' }, two: { type: 'string' } }, + required: [ + "one", + "two", + ] +} +``` \ No newline at end of file diff --git a/src/schema-builder.ts b/src/schema-builder.ts index 6510d3b..e92fb56 100644 --- a/src/schema-builder.ts +++ b/src/schema-builder.ts @@ -28,6 +28,9 @@ function createSchemaForArray(arr: Array, options?: SchemaGenOptions): Sche } const elementSchemas = arr.map((value) => createSchemaFor(value, options)); const items = combineSchemas(elementSchemas); + if(options?.noAdditionalProperties){ + addNoAddtionalProperties(items); + } return { type: ValueType.Array, items }; } @@ -47,6 +50,9 @@ function createSchemaForObject(obj: Object, options?: SchemaGenOptions): Schema if (!options?.noRequired) { schema.required = keys; } + if(options?.noAdditionalProperties){ + addNoAddtionalProperties(schema); + } return schema; } @@ -251,3 +257,18 @@ export function createCompoundSchema(values: any[], options?: SchemaGenOptions): const schemas = values.map((value) => createSchema(value, options)); return mergeSchemas(schemas, options); } + + +function addNoAddtionalProperties(schema: Schema) { + if (schema.type == 'object') { + schema.additionalProperties = false; + const keys = Object.keys(schema.properties); + schema.properties = keys.reduce((properties, key) => { + properties[key] = addNoAddtionalProperties(schema.properties[key]); + return properties; + }, schema.properties); + } else if (schema.type == 'array') { + schema.items = addNoAddtionalProperties(schema.items); + } + return schema; +} diff --git a/src/types.ts b/src/types.ts index 0e82179..792e8a3 100644 --- a/src/types.ts +++ b/src/types.ts @@ -14,10 +14,12 @@ export type Schema = { properties?: Record; required?: string[]; anyOf?: Array; + additionalProperties?: boolean; }; export type SchemaGenOptions = { - noRequired: boolean; + noRequired?: boolean; + noAdditionalProperties?: boolean; }; export type SchemaComparisonOptions = { diff --git a/tests/schema-builder.spec.ts b/tests/schema-builder.spec.ts index bfd2425..bda5e58 100644 --- a/tests/schema-builder.spec.ts +++ b/tests/schema-builder.spec.ts @@ -93,6 +93,18 @@ describe('SchemaBuilder', () => { properties: { one: { type: 'integer' }, two: { type: 'string' } }, }); }); + it('it should generate schema for object with additionalProperties=false ', () => { + const schema = createSchema({ one: 1, two: 'second' }, { noAdditionalProperties: true }); + expect(schema).toEqual({ + type: 'object', + additionalProperties: false, + properties: { one: { type: 'integer' }, two: { type: 'string' } }, + required: [ + "one", + "two", + ] + }); + }); }); describe('nested array', () => { @@ -254,6 +266,54 @@ describe('SchemaBuilder', () => { }); }); + it('should consider value with additionalProperties=false in all objects', async () => { + const val = [ + { + arr: [ + { + prop1: 'test string', + }, + { + prop2: 'test string', + }, + ], + }, + { + arr: [ + { + prop1: 'test', + }, + ], + }, + ]; + const schema = createSchema(val, { noAdditionalProperties: true }); + expect(schema).toEqual({ + type: 'array', + items: { + type: 'object', + additionalProperties: false, + properties: { + arr: { + type: 'array', + items: { + type: 'object', + additionalProperties: false, + properties: { + prop1: { + type: 'string', + }, + prop2: { + type: 'string', + }, + }, + }, + }, + }, + required: ['arr'], + }, + }); + }); + it('should generate schema for array of objects w/o required', () => { const schema = createSchema( [