{"id":16886188,"url":"https://github.com/mvila/transformable","last_synced_at":"2025-03-20T06:16:29.380Z","repository":{"id":138239543,"uuid":"573607466","full_name":"mvila/transformable","owner":"mvila","description":"Transforms plain objects to class instances and vice versa (a lightweight alternative to 'class-transformer')","archived":false,"fork":false,"pushed_at":"2022-12-02T23:41:06.000Z","size":10,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-01-25T07:41:27.060Z","etag":null,"topics":["class","class-transformer","deserialization","instance","plain-object","serialization","transformation"],"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/mvila.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-12-02T22:05:56.000Z","updated_at":"2022-12-02T23:48:07.000Z","dependencies_parsed_at":null,"dependency_job_id":"ad6036ce-d8c7-4552-9052-16af06f392f7","html_url":"https://github.com/mvila/transformable","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/mvila%2Ftransformable","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mvila%2Ftransformable/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mvila%2Ftransformable/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mvila%2Ftransformable/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mvila","download_url":"https://codeload.github.com/mvila/transformable/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":244560393,"owners_count":20472220,"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":["class","class-transformer","deserialization","instance","plain-object","serialization","transformation"],"created_at":"2024-10-13T16:38:38.043Z","updated_at":"2025-03-20T06:16:29.370Z","avatar_url":"https://github.com/mvila.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Transformable\n\nTransforms plain objects to class instances and vice versa.\n\n## Why?\n\nI wanted something similar to [class-transformer](https://github.com/typestack/class-transformer), but which can work in the frontend in addition to the backend. [class-transformer](https://github.com/typestack/class-transformer) is a great library, but it uses TypeScript decorator metadata, which is not supported by some modern bundler tools such as [esbuild](https://esbuild.github.io/), which is used by [Vite](https://vitejs.dev/).\n\nSo, I built this package as a lightweight alternative to [class-transformer](https://github.com/typestack/class-transformer), and I didn't use TypeScript decorator metadata so that it can be used anywhere.\n\nNote that I only implemented what I needed, so the feature set is much more limited than [class-transformer](https://github.com/typestack/class-transformer). If you need additional features, feel free to open an issue or submit a pull request.\n\n## Installation\n\n```\nnpm install transformable\n```\n\n## Example\n\n### Transforming a plain object into an instance of a class\n\n```ts\nimport {\n  Transformable,\n  TransformDate,\n  TransformSet,\n  TransformInstance,\n  TransformInstances,\n  ExcludeOutput\n} from 'transformable';\n\nclass User extends Transformable {\n  email!: string;\n\n  @ExcludeOutput()\n  password!: string;\n\n  @TransformInstance(() =\u003e Organization)\n  organization!: Organization;\n\n  @TransformSet()\n  roles!: Set\u003cstring\u003e;\n\n  @TransformInstances(() =\u003e AccessToken)\n  accessTokens!: AccessToken[];\n\n  @TransformDate()\n  createdOn!: Date;\n}\n\nclass Organization extends Transformable {\n  name!: string;\n\n  @TransformDate()\n  createdOn!: Date;\n}\n\nclass AccessToken extends Transformable {\n  value!: string;\n\n  @TransformDate()\n  createdOn!: Date;\n}\n\nconst user = User.fromPlain({\n  email: 'john@acme.inc',\n  password: 'sEcReT',\n  organization: {name: 'Acme Inc.', createdOn: '2022-05-02T17:15:12.087Z'},\n  roles: ['viewer', 'editor'],\n  accessTokens: [\n    {value: 'abc123', createdOn: '2022-05-03T23:02:50.540Z'},\n    {value: 'def456', createdOn: '2022-05-05T09:05:11.241Z'}\n  ],\n  createdOn: '2022-05-03T22:33:09.015Z'\n});\n\nconsole.log(user);\n```\n\nIt should output the following:\n\n```\nUser {\n  email: 'john@acme.inc',\n  password: 'sEcReT',\n  organization: Organization {\n    name: 'Acme Inc.',\n    createdOn: 2022-05-02T17:15:12.087Z\n  },\n  roles: Set(2) { 'viewer', 'editor' },\n  accessTokens: [\n    AccessToken {\n      value: 'abc123',\n      createdOn: 2022-05-03T23:02:50.540Z\n    },\n    AccessToken {\n      value: 'def456',\n      createdOn: 2022-05-05T09:05:11.241Z\n    }\n  ],\n  createdOn: 2022-05-03T22:33:09.015Z\n}\n```\n\nNote that:\n\n- `user` is a `User` instance.\n- `user.organization` is an `Organization` instance.\n- `user.roles` is a set of strings.\n- `user.accessTokens` is an array of `AccessToken` instances.\n- `createdOn` attributes are some `Date` instances.\n\n### Transforming a class instance into a plain object\n\n```ts\nconst plainUser = user.toPlain();\n\nconsole.log(plainUser);\n```\n\nIt should output the following:\n\n```\n{\n  email: 'john@acme.inc',\n  organization: { name: 'Acme Inc.', createdOn: 2022-05-02T17:15:12.087Z },\n  roles: [ 'viewer', 'editor' ],\n  accessTokens: [\n    { value: 'abc123', createdOn: 2022-05-03T23:02:50.540Z },\n    { value: 'def456', createdOn: 2022-05-05T09:05:11.241Z }\n  ],\n  createdOn: 2022-05-03T22:33:09.015Z\n}\n```\n\nNote that:\n\n- `plainUser` is a plain object.\n- `plainUser.password` is missing because it has been excluded thanks to the [`@ExcludeOutput()`](#excludeoutput-decorator) decorator in the `User` class.\n- `plainUser.organization` is a plain object.\n- `plainUser.roles` is an array of string.\n- `plainUser.accessTokens` is an array of plain objects.\n- `createdOn` attributes are still `Date` instances, and this is not an issue because they can be automatically transformed into strings when [`JSON.stringify()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify) is called implicitly or explicitly (see below).\n\n### Transforming a plain object into a string\n\n```ts\nconst stringifiedUser = JSON.stringify(plainUser, undefined, 2);\n\nconsole.log(stringifiedUser);\n```\n\nIt should output the following:\n\n```json\n{\n  \"email\": \"john@acme.inc\",\n  \"organization\": {\n    \"name\": \"Acme Inc.\",\n    \"createdOn\": \"2022-05-02T17:15:12.087Z\"\n  },\n  \"roles\": [\"viewer\", \"editor\"],\n  \"accessTokens\": [\n    {\n      \"value\": \"abc123\",\n      \"createdOn\": \"2022-05-03T23:02:50.540Z\"\n    },\n    {\n      \"value\": \"def456\",\n      \"createdOn\": \"2022-05-05T09:05:11.241Z\"\n    }\n  ],\n  \"createdOn\": \"2022-05-03T22:33:09.015Z\"\n}\n```\n\nNote that the `createdOn` attributes have been transformed into strings.\n\n## API\n\n### `Transformable` class\n\nA convenience class that you can extend to implement the classes that you need in your app.\n\nNote that you don't need to use the `Transformable` class if you don't want to base your classes on it. Instead, you can use the [`plainToInstance()`](#plaintoinstancesourceplain-targetclass-sourcecontext) and [`instanceToPlain()`](#instancetoplainsourceinstance-targetcontext) functions that work with any class.\n\nThe `Transformable` class brings the following methods to your classes.\n\n#### `fromPlain(sourceObject, sourceContext?)` class method\n\nTransforms a plain object into a class instance.\n\nOptionally, you can pass a `sourceContext` string to specify the source of the plain object (see the [`Transform()`](#transformtransformation-decorator) decorator to learn more about contexts).\n\n#### `toPlain(targetContext?)` instance method\n\nTransforms a class instance into a plain object.\n\nOptionally, you can pass a `targetContext` string to specify the target of the plain object (see the [`Transform()`](#transformtransformation-decorator) decorator to learn more about contexts).\n\n#### `toJSON(targetContext?)` instance method\n\nAn alias of the `toPlain()` instance method.\n\nHaving a `toJSON()` instance method is helpful because it is automatically called by [`JSON.stringify()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify). So, in most cases, you will not have to call the `toPlain()` instance method explicitly when your instances are serialized to be transported between the frontend and the backend of your app.\n\n### Decorators\n\nYou can use the following decorators in any class to automatically transform the attributes when `fromPlain()` or `toPlain()` is called.\n\nNote that you can only use one decorator per attribute. Also, when you decorate an attribute of a subclass, if the base class attribute is decorated, the subclass attribute decorator overrides the base class attribute decorator.\n\n#### `@TransformDate()` decorator\n\nWhen `fromPlain()` is called, transforms a string into a `Date`. Note that if a value is already a `Date`, a copy of the `Date` is returned.\n\nWhen `toPlain()` is called, no transformation is performed (i.e., the `Date` object remains as it is).\n\n#### `@TransformSet()` decorator\n\nWhen `fromPlain()` is called, transforms an `Array` into a `Set`. Note that if a value is already a `Set`, a copy of the `Set` is returned.\n\nWhen `toPlain()` is called, transforms a `Set` into an `Array`.\n\n#### `@TransformInstance(classProvider, {excludeOutput?})` decorator\n\nWhen `fromPlain()` is called, transforms a plain object into an instance of the class returned by the `classProvider` function.\n\nWhen `toPlain()` is called, transforms a class instance into a plain object. If the `excludeOutput` option is set to `true`, no transformation is performed, and `undefined` is returned.\n\n#### `@TransformInstances(classProvider, {excludeOutput?})` decorator\n\nWhen `fromPlain()` is called, transforms an array of plain objects into an array of instances of the class returned by the `classProvider` function.\n\nWhen `toPlain()` is called, transforms an array of class instances into an array of plain objects. If the `excludeOutput` option is set to `true`, no transformation is performed, and `undefined` is returned.\n\n#### `@ExcludeOutput()` decorator\n\nWhen `toPlain()` is called, transforms any value to `undefined`.\n\nThis decorator is helpful when you want to protect sensitive data (e.g., a password in the backend).\n\n#### `@Transform(transformation)` decorator\n\nA generic decorator that allows you to implement any transformation.\n\nThe `transformation` parameter should be an object of type `{input?: Transformer; output?: Transformer}` where `Transformer` should be a function of type `(value: any, context: {source?: string; target?: string}) =\u003e any`.\n\nThe `input` function will be called when `fromPlain()` is called, and the `output` function will be called when `toPlain()` is called. Both functions receive a value and should return a transformed value.\n\nOptionally, you can use the `context` object to apply different transformations depending on the `source` or `target` context.\n\nFor example, in your backend, you may want to transform a value differently when the `source` or `target` context is `'database'`.\n\nLet's say that you have a `User` class with an `isAdmin` boolean attribute, but for some legacy reasons, you need to transform it into a `0` or `1` number when it is stored in the database.\n\nYou could implement the `User` class as follows:\n\n```ts\nclass User extends Transformable {\n  email!: string;\n\n  @Transform({\n    input(value, {source}) {\n      if (source === 'database') {\n        return value === 1;\n      } else {\n        return value;\n      }\n    },\n    output(value, {target}) {\n      if (target === 'database') {\n        return value ? 1 : 0;\n      } else {\n        return value;\n      }\n    }\n  })\n  isAdmin!: boolean;\n}\n```\n\nThen, when you read from the database, you can transform the database object to a `User` instance as follows:\n\n```ts\nconst userFromDatabase = {email: 'john@acme.inc', isAdmin: 1};\n\nconst user = User.fromPlain(userFromDatabase, 'database');\n\nconsole.log(user.isAdmin); // =\u003e `true`\n```\n\nAnd, when you write to the database, you can transform the `User` instance to a database object as follows:\n\n```ts\nconst userForDatabase = user.toPlain('database');\n\nconsole.log(userForDatabase.isAdmin); // =\u003e `1`\n```\n\nFinally, when you serve the frontend, you can transform the `User` instance to a plain object as follows:\n\n```ts\nconst userForFrontend = user.toPlain();\n\nconsole.log(userForFrontend.isAdmin); // =\u003e true\n```\n\n### Functions\n\nYou can transform from or to any class instance (whether the class is based on [`Transformable`](#transformable-class) or not) with the following functions.\n\n#### `plainToInstance(sourcePlain, targetClass, sourceContext?)`\n\nTransforms the `sourcePlain` plain object into an instance of `targetClass`.\n\nOptionally, you can pass a `sourceContext` string to specify the source of the plain object (see the [`Transform()`](#transformtransformation-decorator) decorator to learn more about contexts).\n\n#### `instanceToPlain(sourceInstance, targetContext?)`\n\nTransforms the `sourceInstance` class instance into a plain object.\n\nOptionally, you can pass a `targetContext` string to specify the target of the plain object (see the [`Transform()`](#transformtransformation-decorator) decorator to learn more about contexts).\n\n## License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmvila%2Ftransformable","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmvila%2Ftransformable","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmvila%2Ftransformable/lists"}