{"id":18612428,"url":"https://github.com/avivcarmis/smoke-screen","last_synced_at":"2025-04-10T23:31:20.954Z","repository":{"id":57364237,"uuid":"105386483","full_name":"avivcarmis/smoke-screen","owner":"avivcarmis","description":"Strong typing validation for JavaScript runtime","archived":false,"fork":false,"pushed_at":"2018-06-04T07:29:47.000Z","size":62,"stargazers_count":4,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-03-25T06:22:05.554Z","etag":null,"topics":["instantiation","javscript","runtime-validation","strongly-typed","typescript"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/avivcarmis.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2017-09-30T17:13:56.000Z","updated_at":"2021-12-12T20:56:25.000Z","dependencies_parsed_at":"2022-09-13T21:11:06.829Z","dependency_job_id":null,"html_url":"https://github.com/avivcarmis/smoke-screen","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/avivcarmis%2Fsmoke-screen","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/avivcarmis%2Fsmoke-screen/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/avivcarmis%2Fsmoke-screen/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/avivcarmis%2Fsmoke-screen/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/avivcarmis","download_url":"https://codeload.github.com/avivcarmis/smoke-screen/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248316070,"owners_count":21083376,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["instantiation","javscript","runtime-validation","strongly-typed","typescript"],"created_at":"2024-11-07T03:17:08.261Z","updated_at":"2025-04-10T23:31:19.433Z","avatar_url":"https://github.com/avivcarmis.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Smoke Screen\n\n[![SmokeScreen Build Status at Travis CI](https://api.travis-ci.org/avivcarmis/smoke-screen.svg?branch=master)](\"https://api.travis-ci.org/avivcarmis/smoke-screen.svg?branch=master\")\n\nStrongly typed validation for JavaScript runtime.\n\n## In a Nutshell\n\nSmoke Screen is a lightweight JS library allowing seamless schema validation and class instantiation.\nSmoke Screen is designed to serialize and deserialize JavaScript objects and JSON strings while enforcing validation, and performing property filtering and modification.\n\n## Getting Started\n\nInstallation via npm:\n\n`$ npm install smoke-screen --save`\n\nThe following sections explain the main features of Smoke Screen.\n\n\u003e Comparability Note: Smoke Screen library depends on EcmaScript decorators. While \nEcmaScript doesn't officially support decorators yet, the examples below are \nimplemented in TypeScript, but may also be implemented in any other way that compiles \ndecorators.\n\n### Basic Serialization and Deserialization\n\nBy default, all properties are transient, meaning they will not get exposed unless explicitly decorated with an `@exposed` decorator.\n\nSince JavaScript does not keep typing information at runtime, if we would like to perform runtime validations, we will have to explicitly pass validation information to the runtime environment. In the following examples we will see how to do it. For now, let's just see how the basic serialization and deserialization works.\n\n```typescript\nclass Person {\n\n    @exposed\n    name: string;\n\n    transientProperty: string;\n\n    whatsMyName() {\n        console.log(this.name);\n    }\n\n}\n\n// let's serialize a Person object into a JSON string\nconst person = new Person();\nperson.name = \"john\";\nperson.transientProperty = \"will not get exposed\";\nconst smokeScreen = new SmokeScreen();\nsmokeScreen.toJSON(person); // -\u003e '{\"name\":\"john\"}'\n\n// let's deserialize a JSON string into a Person object\nconst json = JSON.stringify({name: \"steve\", age: 57.3, transientProperty: \"value\"});\nconst person2 = smokeScreen.fromJSON(json, Person);\nconsole.log(person2); // -\u003e Person { name: 'steve' }\nperson2.whatsMyName(); // -\u003e 'steve'\n```\n\n### Exposure Settings\n\nBy default, properties are not validated or translated in any way.\n\nTo allow for those, we have to pass some information to JS runtime. We can do it by passing an `ExposureSettings` object to the `@exposed` decorator.\n\n```typescript\nclass Person {\n\n    @exposed({\n        as: \"myAge\",\n        type: Number,\n        validator: value =\u003e {\n            if (value \u003c 18) {\n                throw new Error(\"must be at least 18\");\n            }\n        }\n    })\n    age: number;\n\n}\n\n// let's serialize a Person object into a JSON string\nconst person = new Person();\nperson.age = 56.8;\nconst smokeScreen = new SmokeScreen();\nsmokeScreen.toJSON(person); // -\u003e '{\"myAge\":56.8}'\n\n// let's deserialize a JSON string into a Person object\nlet json = JSON.stringify({myAge: 19});\nconst person2 = smokeScreen.fromJSON(json, Person);\nconsole.log(person2); // -\u003e Person { age: 19 }\n\n// let's see the typing validation in action\njson = JSON.stringify({myAge: \"oops\"});\nsmokeScreen.fromJSON(json, Person); // Error: illegal input - property 'myAge' must be a number\n\n// let's see the custom validator in action\njson = JSON.stringify({myAge: 17});\nsmokeScreen.fromJSON(json, Person); // Error: illegal input - property 'myAge' must be at least 18\n\n// let's see property naming in action\njson = JSON.stringify({age: 27});\nsmokeScreen.fromJSON(json, Person); // Error: illegal input - property 'myAge' is required\n\n// unless otherwise specified, exposed properties are required\njson = JSON.stringify({});\nsmokeScreen.fromJSON(json, Person); // Error: illegal input - property 'myAge' is required\n\n// unless otherwise specified, exposed properties may not be null\njson = JSON.stringify({myAge: null});\nsmokeScreen.fromJSON(json, Person); // Error: illegal input - property 'myAge' may not be null\n```\n\nAs can be seen in the example, `exposed` properties are by default required and non-nullable. A property can become optional by setting the optional flag to true, and optionally set the default property value in the constructor, like so:\n\n```typescript\nclass Person {\n\n    @exposed({\n        as: \"myAge\",\n        type: Number,\n        optional: true \n    })\n    age = 42.3;\n\n}\n\nconst json = JSON.stringify({});\nconst person = smokeScreen.fromJSON(json, Person);\nconsole.log(person); // -\u003e Person { age: 42.3 }\n```\n\nA property can also become nullable:\n\n```typescript\nclass Person {\n\n    @exposed({\n        as: \"myAge\",\n        type: Number,\n        nullable: true\n    })\n    age: number | null;\n\n}\n\nconst json = JSON.stringify({myAge: null});\nconst person = smokeScreen.fromJSON(json, Person);\nconsole.log(person); // -\u003e Person { age: null }\n```\n\n### Property Types\n\nSetting the exposure type allows us to enforce strong typing and to translate input and output values.\nA property type must be an object implementing the `PropertyType` interface. This is very simple to implement in case you want to achieve any custom behavior you like; However, Smoke Screen provides out-of-the-box implementation for all major types under the `PropertyTypes` namespace:\n- `StringPropertyType`\n- `NumberPropertyType`\n- `BooleanPropertyType`\n- `ObjectPropertyType`\n- `ArrayPropertyType`\n- `EnumPropertyType`\n- `MapPropertyType`\n- `SetPropertyType`\n\nFor example:\n\n```typescript\nclass Pet {\n\n    @exposed({type: new StringPropertyType()})\n    name: string;\n    \n}\n\nclass Person {\n\n    @exposed({type: new NumberPropertyType()})\n    age: number;\n    \n    @exposed({type: new ArrayPropertyType(new ObjectPropertyType(Pet))})\n    pets: Pet[];\n    \n    @exposed({type: new SetPropertyType(new StringPropertyType())})\n    favoriteFoods: Set\u003cstring\u003e;\n    \n    @exposed({type: new MapPropertyType(new StringPropertyType(), new BooleanPropertyType())})\n    likesAndDislikes: Map\u003cstring, boolean\u003e;\n\n}\n````\n\nNote that instead of referencing these `PropertyType` classes directly, its possible to use a short writing as follows:\n\n- Instead of referencing `StringPropertyType`, we can simply reference the native `String` class.\n- Instead of referencing `NumberPropertyType`, we can simply reference the native `Number` class.\n- Instead of referencing `BooleanPropertyType`, we can simply reference the native `Boolean` class.\n- Instead of referencing `ObjectPropertyType`, we can simply reference object class itself.\n- Instead of referencing `EnumPropertyType`, we can simply reference enum class itself.\n- Instead of referencing `ArrayPropertyType`, we can simply create an array containing exactly one property type, stating the array type.\n\nLet's see all of this in action:\n\n```typescript\nenum Mood {\n\n    HAPPY, SAD\n\n}\n\nclass Animal {\n\n    @exposed({type: String})\n    name: string;\n\n}\n\nclass Person {\n\n    @exposed({type: String}) // string short writing\n    name: string;\n\n    @exposed({type: Number}) // number short writing\n    age: number;\n\n    @exposed({type: Boolean}) // boolean short writing\n    isFunny: boolean;\n\n    @exposed({type: Mood}) // enum short writing\n    mood: Mood;\n\n    @exposed({type: Animal}) // object short writing\n    favoritePet: Animal;\n\n    @exposed({type: [Animal]}) // array and object short writing\n    pets: Animal[];\n\n    @exposed({type: [String]}) // array and string short writing\n    speaks: string[];\n    \n    @exposed({type: new SetPropertyType(String)}) // string short writing\n    favoriteFoods: Set\u003cstring\u003e;\n    \n    @exposed({type: new MapPropertyType(String, Boolean)}) // string and boolean short writing\n    likesAndDislikes: Map\u003cstring, boolean\u003e;\n\n}\n```\n\nTo enable custom property types, any implementation of the `PropertyType` interface may be passed to the `@exposed` `type` field.\n\n### Naming Translators\n\nSmoke screen allows for automatic translation of property names. For instance you may want to expose all property names using `camel_case`.\nTo achieve any custom translation, an implementation of a `NamingTranslator` function must be created which receive an internal property name, and returns the external name to expose; However, Smoke Screen provides out-of-the-box implementation for all major naming conventions under the `NamingTranslators` namespace:\n- `upperCamelCase` - WhichLooksLikeThis\n- `lowerSnakeCase` - which_looks_like_this\n- `upperSnakeCase` - WHICH_LOOKS_LIKE_THIS\n- `lowerKebabCase` - which-looks-like-this\n- `upperKebabCase` - WHICH-LOOKS-LIKE-THIS\nLet's see that in action:\n\n```typescript\nclass Person {\n\n    @exposed\n    firstName: string;\n\n    @exposed\n    lastName: string;\n\n}\n\n// let's see the result without any naming translation\nconst person = new Person();\nperson.firstName = \"John\";\nperson.lastName = \"Doe\";\nlet smokeScreen = new SmokeScreen();\nconsole.log(smokeScreen.toJSON(person)); // -\u003e '{\"firstName\":\"John\",\"lastName\":\"Doe\"}'\n\n// let's see the result with lower snake case naming translation\nsmokeScreen = new SmokeScreen(NamingTranslators.lowerSnakeCase);\nconsole.log(smokeScreen.toJSON(person)); // -\u003e '{\"first_name\":\"John\",\"last_name\":\"Doe\"}'\n\n// let's see the result with upper kebab case naming translation\nsmokeScreen = new SmokeScreen(NamingTranslators.upperKebabCase);\nconsole.log(smokeScreen.toJSON(person)); // -\u003e '{\"FIRST-NAME\":\"John\",\"LAST-NAME\":\"Doe\"}'\nconst json = JSON.stringify({\"FIRST-NAME\": \"John\", \"LAST-NAME\": \"Doe\"});\nconsole.log(smokeScreen.fromJSON(json, Person)); // -\u003e Person { firstName: 'John', lastName: 'Doe' }\n```\n\nTo enable custom naming translators, any implementation of a `NamingTranslator` type may be passed when instantiating a new `SmokeScreen` object.\n\n## Exposing Properties\n\nExposing properties is done using the `@exposed` decorator, which accepts an optional \n`ExposureSettings` object:\n\n- `as?: string` - May be specified to override the exposed property key\n- `type?: PropertyType` - A property type to perform typing validation and translation.\n (Further reading in [`PropertyType` the JSDocs](https://github.com/avivcarmis/smoke-screen/blob/master/src/PropertyType.ts#L8 \"`PropertyType` the JSDocs\"))\n- `validator?: (value: any) =\u003e any` - A further validation function to perform a more \nspecific validation and translation if needed. Note that a validation is performed only on deserialization and *not* on serialization.\nValidation may be performed by inspecting the input value parameter, if the value is invalid, the function should throw an error describing the invalidity.\nTranslation may be performed by returning a value different than the given one. Skipping translation may be performed by simply not returning any value from the function, or by returning the given one.\n- `optional?: boolean` - May be used to allow the property to not appear in the source of the deserialization process.\nBy default, exposed properties are required on deserialization, unless this is set to true.\n- `nullable?: boolean` - May be used to allow the property a null value when \ndeserializing. By default, exposed properties are may not receive null value on deserialization, unless this is set to true.\n\n## Smoke Screen Lifecycle\n\nThe `exposed` decorator allows for simple and flexible validation of each property; However, it does not provide any means of validating the entire object. For that end, Smoke Screen provides an additional\ninterface `SmokeScreenLifecycle` which allows to register for certain events in the lifecycle of the screening process:\n\n- `beforeSerialize` - To validate an entire object before its being serialized, a zero arguments `beforeSerialize` method must be implemented, validating the object, and throwing an error in case it is not valid.\n- `afterDeserialize` - To validate an entire object after it has been deserialized, a zero arguments `afterDeserialize` method must be implemented, validating the object, and throwing an error in case it is not valid.\n\n```typescript\n// note that it is not required to state `implements SmokeScreenLifecycle`,\n// but merely to implement any of it's methods\nclass Person implements SmokeScreenLifecycle {\n\n    @exposed\n    age: number;\n\n    @exposed\n    drinksAlcohol: boolean;\n\n    constructor(age: number, drinksAlcohol: boolean) {\n        this.age = age;\n        this.drinksAlcohol = drinksAlcohol;\n    }\n\n    beforeSerialize() {\n        if (this.age \u003c 18 \u0026\u0026 this.drinksAlcohol) {\n            throw new Error(\"invalid during serialization\");\n        }\n    }\n\n    afterDeserialize() {\n        if (this.age \u003c 18 \u0026\u0026 this.drinksAlcohol) {\n            throw new Error(\"invalid during deserialization\");\n        }\n    }\n\n}\n\nconst smokeScreen = new SmokeScreen();\n\n// validate on deserialize\nconst json = JSON.stringify({age: 17, drinksAlcohol: true});\nsmokeScreen.fromJSON(json, Person); // -\u003e Error: invalid during deserialization\n\n// validate on serialize\nconst person = new Person(17, true);\nsmokeScreen.toJSON(person); // -\u003e Error: invalid during serialization\n```\n\n## The Smoke Screen Interface\n\nTo use Smoke Screen features, e.g. serialization and deserialization, an instance of \n`SmokeScreen` class must be first created. Once an instance is available, it provides \nthe following methods:\n\n- `toJSON(object: any): string` - Serializes the given object to a JSON string.\nFiltering properties and translating property names and their values if needed.\n- `fromJSON\u003cT\u003e(json: string, instanceClass: Constructable\u003cT\u003e): T` - Deserializes the given\nJSON string into a fresh instance of the given class and returns it. \nFiltering properties, validates and translates the property names and their values if\nneeded. throws an Error in case of invalid input.\n- `updateFromJSON\u003cT\u003e(json: string, instance: T): void` - Deserializes the given JSON\nstring into the given instance. Filtering properties, validates and translates the \nproperty names and their values if needed. throws an Error in case of invalid input.\n- `toObject(object: any): {[key: string]: any}` - Serializes the given object to a generic\nJS object containing the exposure. Filtering properties and translating property names\nand their values if needed.\n- `fromObject\u003cT\u003e(exposure: {[key: string]: any}, instanceClass: Constructable\u003cT\u003e)` - \nDeserializes the given generic JS object into a fresh instance of the given class\nand returns it. Filtering properties, validates and translates the property names\nand their values if needed. throws an Error in case of invalid input.\n- `updateFromObject\u003cT\u003e(exposure: {[key: string]: any}, instance: T): void` - Deserializes\nthe given generic JS object into the given instance. Filtering properties, validates \nand translates the property names and their values if needed. throws an Error in case \nof invalid input.\n\n## Useful Links\n- [The project GitHub page](https://github.com/avivcarmis/smoke-screen \"The project GitHub page\")\n- [The project Issue Tracker on GitHub](https://github.com/avivcarmis/smoke-screen/issues \"The project Issue Tracker on GitHub\")\n- [The project build Status at Travis CI](https://travis-ci.org/avivcarmis/smoke-screen \"The project build Status at Travis CI\")\n\n## License\nSmoke Screen is registered under \u003ca href=\"https://github.com/avivcarmis/smoke-screen/blob/master/LICENSE\" target=\"_blank\"\u003eMIT\u003c/a\u003e license.\n\n## Contribution\nReally, any kind of contribution will be warmly accepted. (:\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Favivcarmis%2Fsmoke-screen","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Favivcarmis%2Fsmoke-screen","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Favivcarmis%2Fsmoke-screen/lists"}