{"id":20986826,"url":"https://github.com/karmaniverous/serify-deserify","last_synced_at":"2025-04-06T21:16:38.998Z","repository":{"id":64986031,"uuid":"502248862","full_name":"karmaniverous/serify-deserify","owner":"karmaniverous","description":"Reversibly transform unserializable values into serializable ones. Includes Redux middleware.","archived":false,"fork":false,"pushed_at":"2024-11-18T16:20:43.000Z","size":1826,"stargazers_count":35,"open_issues_count":10,"forks_count":4,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-04-05T00:09:46.186Z","etag":null,"topics":["bigint","date","deserialize","json","map","parse","redux","serializable","serialize","set","state","stringify","type","typescript"],"latest_commit_sha":null,"homepage":"https://www.npmjs.com/package/@karmaniverous/serify-deserify","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/karmaniverous.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":null,"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},"funding":{"github":["karmaniverous"]}},"created_at":"2022-06-11T04:26:50.000Z","updated_at":"2025-03-07T15:11:32.000Z","dependencies_parsed_at":"2024-05-01T05:22:27.344Z","dependency_job_id":"4b7f7500-d8fe-4418-b5a1-ee88f3464b34","html_url":"https://github.com/karmaniverous/serify-deserify","commit_stats":{"total_commits":132,"total_committers":3,"mean_commits":44.0,"dds":"0.030303030303030276","last_synced_commit":"e70986ee4c9cb990aac9a0de84fba0e861befdfa"},"previous_names":[],"tags_count":48,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/karmaniverous%2Fserify-deserify","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/karmaniverous%2Fserify-deserify/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/karmaniverous%2Fserify-deserify/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/karmaniverous%2Fserify-deserify/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/karmaniverous","download_url":"https://codeload.github.com/karmaniverous/serify-deserify/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247550693,"owners_count":20956987,"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":["bigint","date","deserialize","json","map","parse","redux","serializable","serialize","set","state","stringify","type","typescript"],"created_at":"2024-11-19T06:15:00.731Z","updated_at":"2025-04-06T21:16:38.980Z","avatar_url":"https://github.com/karmaniverous.png","language":"TypeScript","funding_links":["https://github.com/sponsors/karmaniverous"],"categories":[],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\u003cimg src=\"./assets/flowchart.png\"\u003e\u003c/p\u003e\n\n**serify** - reversibly transform an unserializable value into a serializable one\n\n**deserify** - do the exact opposite\n\n## Why?\n\n`JSON.stringify` and `JSON.parse` are a notoriously bad serializer/deserializer combination. They don't support important Javascript types like `BigInt`, `Date`, `Map`, `Set`, and `unknown`. Thanks to backward compatibility risk, they probably never will.\n\nThere are tons of custom serializers that address this issue, notably [`serialize-javascript`](https://www.npmjs.com/package/serialize-javascript) and [`serializr`](https://www.npmjs.com/package/serializr). Unfortunately, some key Javascript tools like [Redux](https://redux.js.org) explicitly depend on `JSON.stringify` \u0026 `JSON.parse`. So if you use Redux, none of those fancy serializers will help you get a `Date` or a `BigInt` into your store and back out again in one piece.\n\n`serify` solves this problem by encoding those values (or structures containing them) into values that `JSON.stringify` _can_ serialize without throwing an exception. After these values are retrieved and deserialized with `JSON.parse`, `deserify` returns them to their original state.\n\n## Usage\n\nTo install the package, run this command:\n\n```bash\nnpm install @karmaniverous/serify-deserify\n```\n\nA simple example:\n\n```js\nimport {\n  serify,\n  deserify,\n  defaultOptions,\n} from '@karmaniverous/serify-deserify';\n\n// A BigInt test value.\nconst value = 42n;\n\nconst serified = serify(value, defaultOptions);\n// { serifyKey: null, type: 'BigInt', value: '42' }\n\nconst deserified = deserify(serified, defaultOptions);\n// 42n\n```\n\nReview the unit tests for more examples of how to use [`serify`](./src/serify/serify.test.ts) and [`deserify`](./src/deserify/deserify.test.ts).\n\nSee the [`createReduxMiddleware` unit tests](./src/createReduxMiddleware/createReduxMiddleware.test.ts) for a fully worked out example of how to configure \u0026 integrate the Redux middleware.\n\n## Serifiable Types\n\n`serify` and `deserify` will work on values of any serifiable type.\n\nA _serifiable type_ is any type that is:\n\n- reversibly supported by `JSON.stringify` and `JSON.parse`, i.e. booleans, numbers, strings, plain objects, and arrays.\n- natively supported by `serify`, i.e. `BigInt`, `Date`, `Map`, `Set`, and `unknown`.\n- added to `serify` as a custom type.\n- composed exclusively of any of the above (e.g. an array of BigInt-keyed Maps of objects containing Sets of custom class instances).\n\n## serifyKey\n\n`serify` works by converting unserializable values into structured objects that ARE serializable.\n\nConsider the highly unlikely event that some data you want to `deserify` contains objects with exactly this form that were _not_ produced by `serify`:\n\n```\n{\n  serifyKey: null,\n  type: 'Foo',\n  value: 'Bar'\n}\n```\n\nIf you are using the [default configuration](./src/options/defaultOptions.ts) (which does not support a `Foo` type), `deserify` will attempt to deserify this object and your process will either fail or produce an incorrect result.\n\nIn this case, simply add a non-null `serifyKey` of a serifiable primitive type (meaning a `boolean`, `number`, or `string`) to your `options` object, and everything will work again.\n\n## Options\n\n[Serifiable types](#serifiable-types) and the [`serifyKey`](#serifykey) are defined in an `options` object, which specifies the logic that converts each type to and from a serializable form.\n\n### Default Configuration\n\nOut of the box, the [`defaultOptions`](./src/options/defaultOptions.ts) object supports the `BigInt`, `Date`, `Map`, `Set`, and `unknown` types.\n\nIf you only need the default configuration, simply import the `defaultOptions` object and pass it to `serify` and `deserify`:\n\n```js\nimport {\n  serify,\n  deserify,\n  defaultOptions,\n} from '@karmaniverous/serify-deserify';\n\n// A BigInt test value.\nconst value = 42n;\n\nconst serified = serify(value, defaultOptions);\n// { serifyKey: null, type: 'BigInt', value: '42' }\n\nconst deserified = deserify(serified, defaultOptions);\n// 42n\n```\n\n### Custom Configuration\n\nIf you need to change the `serifyKey` or add custom types, you can create a new `options` object and pass it to `serify` and `deserify`.\n\nFor a custom class that doesn't use a [Static Type Property](#static-type-property), the key of the related `serify` type is its class name. For anything else, the logic that determines the key is [here](https://github.com/karmaniverous/serify-deserify/blob/cbf92286f255eac2b5fa2e651f1d5e26a638e737/src/serify/serify.ts#L24-L29).\n\n```js\nimport { serify, deserify, defaultOptions } from '@karmaniverous/serify-deserify';\n\n// A custom class.\nexport class Custom {\n  public p;\n\n  constructor(p) {\n    this.p = p;\n  }\n}\n\n// A serify options object including support for the new custom type.\nconst customOptions = {\n  ...defaultOptions\n  serifyKey: 42,\n  types: {\n    ...defaultOptions.types,\n    Custom: {\n      serifier: (value) =\u003e value.p,\n      deserifier: (value) =\u003e new Custom(value)\n    }\n  }\n}\n\n// A Custom test value.\nconst customAnswer = new Custom(42);\n\nconst serified = serify(customAnswer, customOptions);\n// { serifyKey: 42, type: 'Custom', value: 42 }\n\nconst deserified = deserify(serified, customOptions);\n// Custom { p: 42 }\n```\n\n### Static Type Property\n\nNormally, a type's key in the serify options object is the type's class name. If a class is dynamically generated, this value may not be known at compile time, so it would not be possible to configure it into the options object in a static manner.\n\nOne option is to alter the options object at runtime. _Go nuts!_\n\nAnother is to import the `serifyStaticTypeProperty` symbol to create a static property on your class. Use that as type key in your options object.\n\n```js\nimport {\n  serify,\n  deserify,\n  defaultOptions,\n  serifyStaticTypeProperty\n} from '@karmaniverous/serify-deserify';\n\n// A custom class.\nexport class CustomFoo {\n  static [serifyStaticTypeProperty] = 'Foo';\n  public p;\n\n  constructor(p) {\n    this.p = p;\n  }\n}\n\n// A serify options object including support for the new custom type.\nconst customOptions = {\n  ...defaultOptions\n  serifyKey: 42,\n  types: {\n    ...defaultOptions.types,\n    Foo: {\n      serifier: (value) =\u003e value.p,\n      deserifier: (value) =\u003e new CustomFoo(value)\n    }\n  }\n}\n\n// A Custom test value.\nconst customAnswer = new CustomFoo(42);\n\nconst serified = serify(customAnswer, customOptions);\n// { serifyKey: 42, type: 'Foo', value: 42 }\n\nconst deserified = deserify(serified, customOptions);\n// CustomFoo { p: 42 }\n```\n\n### Typescript\n\n`serify-deserify` is fully type-safe. If you are using TypeScript, you can define your custom types and options objects with full type checking.\n\nThis is accomplished by defining a special _type map_ interface that maps a type's name to its types before and after serification. See [`defaultOptions.ts`](./src/options/defaultOptions.ts) to review the default configuration as an example.\n\nHere's the last example again, but with TypeScript:\n\n```ts\nimport {\n  serify,\n  deserify,\n  defaultOptions,\n  serifyStaticTypeProperty,\n  type SerifiableTypeMap,\n  type SerifyOptions\n} from '@karmaniverous/serify-deserify';\n\n// A custom class.\nexport class CustomFoo {\n  static [serifyStaticTypeProperty] = 'Foo';\n\n  constructor(public p: number) {}\n}\n\n// Extend the default type map to include your new type.\n// The tuple indicates the type before and after serification.\ninterface FooTypeMap extends SerifiableTypeMap {\n  Foo: [CustomFoo, number]\n}\n\n// A serify options object including support for the new custom type.\nconst customOptions: SerifyOptions\u003cCustomFooTypeMap\u003e = {\n  ...defaultOptions\n  serifyKey: 42,\n  types: {\n    ...defaultOptions.types,\n    Foo: {\n      serifier: (value) =\u003e value.p,\n      deserifier: (value) =\u003e new CustomFoo(value)\n    }\n  }\n}\n\n// A Custom test value.\nconst customAnswer = new CustomFoo(42);\n\nconst serified = serify(customAnswer, customOptions);\n// { serifyKey: 42, type: 'Foo', value: 42 }\n\nconst deserified = deserify(serified, customOptions);\n// CustomFoo { p: 42 }\n```\n\n## Recursion\n\nIn the [Custom Configuration](#custom-configuration) example above, the `Custom` class contains a single property `p` that is populated with a primitive, serializable value (a `number`). So once a `Custom` value is serified with the `serifier` function defined above, there will be no difficulty serializing the `value` property of the resulting object.\n\nWhat if the `Custom` class contained a property that was itself not serializable? This is the case with the `Map` class, which can contain keys and values of any type, including unserializable ones.\n\nIf you look at the [`defaultOptions`](./src/options/defaultOptions.ts) object, you'll see that the `Map` type's `serifier` and `deserfier` functions are quite simple:\n\n```ts\nexport interface DefaultTypeMap extends SerifiableTypeMap {\n  ...;\n  Map: [Map\u003cunknown, unknown\u003e, [unknown, unknown][]];\n  ...;\n}\n\nexport const defaultOptions: SerifyOptions\u003cDefaultTypeMap\u003e = {\n  types: {\n    ...,\n    Map: {\n      serifier: (value) =\u003e [...value.entries()],\n      deserifier: (value) =\u003e new Map(value),\n    },\n    ...,\n  },\n};\n```\n\nThis works because the `serifier` and `deserifier` functions are applied recursively. They only need to support the direct transformation of a type into a serializable form and back again, without regard to the resulting _contents_... so long as those contents are _also_ composed of serifiable types.\n\n## Redux\n\nThe `createReduxMiddleware` function generates a Redux middleware that will\nserify every value pushed to your Redux store. If you use\n[Redux Toolkit](https://redux-toolkit.js.org/), leave the default\n`serializeCheck` middleware in place and it will notify you if you need to add a\nnew type to your serify options!\n\nWhen retrieving values from the Redux store, either deserify them explicitly or\nwrap your selectors in the `deserify` function.\n\nSee the [`createReduxMiddleware` unit tests](./src/createReduxMiddleware/createReduxMiddleware.test.ts) for a fully worked out example with custom types, or just try this for the out-of-the-box experience (H/T [@tuffstuff9](https://github.com/tuffstuff9)):\n\n```ts\nimport {\n  createReduxMiddleware,\n  defaultOptions,\n} from '@karmaniverous/serify-deserify';\n\n// Create middleware.\nconst serifyMiddleware = createReduxMiddleware(defaultOptions);\n\n// Construct slice.\nconst testSlice = createSlice({\n  name: 'test',\n  initialState,\n  reducers: {\n    setValue: (state, { payload }: PayloadAction\u003cTestState['value']\u003e) =\u003e {\n      state.value = payload;\n    },\n  },\n});\n\n// Configure redux store.\nconst store = configureStore({\n  reducer: combineReducers({\n    test: testSlice.reducer,\n  }),\n  middleware: (getDefaultMiddleware) =\u003e\n    getDefaultMiddleware().concat(serifyMiddleware),\n});\n```\n\n## Cloning in Deserify\n\n`deserify` will not mutate the input value. It clones the value while recursively deserifying its contents.\n\nIt is implicitly assumed that the input value is composed entirely of serializable types, otherwise why bother attempting to deserify it?\n\n---\n\nSee more great templates and other tools on\n[my GitHub Profile](https://github.com/karmaniverous)!\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkarmaniverous%2Fserify-deserify","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkarmaniverous%2Fserify-deserify","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkarmaniverous%2Fserify-deserify/lists"}