{"id":13448537,"url":"https://github.com/Rich-Harris/devalue","last_synced_at":"2025-03-22T09:31:26.405Z","repository":{"id":44892826,"uuid":"124701545","full_name":"Rich-Harris/devalue","owner":"Rich-Harris","description":"Gets the job done when JSON.stringify can't","archived":false,"fork":false,"pushed_at":"2024-09-25T20:10:53.000Z","size":150,"stargazers_count":2276,"open_issues_count":32,"forks_count":61,"subscribers_count":13,"default_branch":"main","last_synced_at":"2025-03-18T18:06:57.177Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"https://svelte.dev/repl/138d70def7a748ce9eda736ef1c71239","language":"JavaScript","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/Rich-Harris.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","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":"2018-03-10T21:53:48.000Z","updated_at":"2025-03-17T06:22:26.000Z","dependencies_parsed_at":"2022-09-14T04:51:51.845Z","dependency_job_id":"7e59f8da-67bd-4232-90d4-7d356b9bbaa7","html_url":"https://github.com/Rich-Harris/devalue","commit_stats":{"total_commits":107,"total_committers":11,"mean_commits":9.727272727272727,"dds":"0.44859813084112155","last_synced_commit":"99e66d0643f92f3b01ac58237ba40672eed325d1"},"previous_names":[],"tags_count":28,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Rich-Harris%2Fdevalue","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Rich-Harris%2Fdevalue/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Rich-Harris%2Fdevalue/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Rich-Harris%2Fdevalue/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Rich-Harris","download_url":"https://codeload.github.com/Rich-Harris/devalue/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":244937751,"owners_count":20535124,"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":[],"created_at":"2024-07-31T05:01:48.345Z","updated_at":"2025-03-22T09:31:25.743Z","avatar_url":"https://github.com/Rich-Harris.png","language":"JavaScript","readme":"# devalue\n\nLike `JSON.stringify`, but handles\n\n- cyclical references (`obj.self = obj`)\n- repeated references (`[value, value]`)\n- `undefined`, `Infinity`, `NaN`, `-0`\n- regular expressions\n- dates\n- `Map` and `Set`\n- `BigInt`\n- `ArrayBuffer` and Typed Arrays\n- custom types via replacers, reducers and revivers\n\nTry it out [here](https://svelte.dev/repl/138d70def7a748ce9eda736ef1c71239?version=3.49.0).\n\n## Goals:\n\n- Performance\n- Security (see [XSS mitigation](#xss-mitigation))\n- Compact output\n\n## Non-goals:\n\n- Human-readable output\n- Stringifying functions\n\n## Usage\n\nThere are two ways to use `devalue`:\n\n### `uneval`\n\nThis function takes a JavaScript value and returns the JavaScript code to create an equivalent value — sort of like `eval` in reverse:\n\n```js\nimport * as devalue from 'devalue';\n\nlet obj = { message: 'hello' };\ndevalue.uneval(obj); // '{message:\"hello\"}'\n\nobj.self = obj;\ndevalue.uneval(obj); // '(function(a){a.message=\"hello\";a.self=a;return a}({}))'\n```\n\nUse `uneval` when you want the most compact possible output and don't want to include any code for parsing the serialized value.\n\n### `stringify` and `parse`\n\nThese two functions are analogous to `JSON.stringify` and `JSON.parse`:\n\n```js\nimport * as devalue from 'devalue';\n\nlet obj = { message: 'hello' };\n\nlet stringified = devalue.stringify(obj); // '[{\"message\":1},\"hello\"]'\ndevalue.parse(stringified); // { message: 'hello' }\n\nobj.self = obj;\n\nstringified = devalue.stringify(obj); // '[{\"message\":1,\"self\":0},\"hello\"]'\ndevalue.parse(stringified); // { message: 'hello', self: [Circular] }\n```\n\nUse `stringify` and `parse` when evaluating JavaScript isn't an option.\n\n### `unflatten`\n\nIn the case where devalued data is one part of a larger JSON string, `unflatten` allows you to revive just the bit you need:\n\n```js\nimport * as devalue from 'devalue';\n\nconst json = `{\n  \"type\": \"data\",\n  \"data\": ${devalue.stringify(data)}\n}`;\n\nconst data = devalue.unflatten(JSON.parse(json).data);\n```\n\n## Custom types\n\nYou can serialize and deserialize custom types by passing a second argument to `stringify` containing an object of types and their _reducers_, and a second argument to `parse` or `unflatten` containing an object of types and their _revivers_:\n\n```js\nclass Vector {\n\tconstructor(x, y) {\n\t\tthis.x = x;\n\t\tthis.y = y;\n\t}\n\n\tmagnitude() {\n\t\treturn Math.sqrt(this.x * this.x + this.y * this.y);\n\t}\n}\n\nconst stringified = devalue.stringify(new Vector(30, 40), {\n\tVector: (value) =\u003e value instanceof Vector \u0026\u0026 [value.x, value.y]\n});\n\nconsole.log(stringified); // [[\"Vector\",1],[2,3],30,40]\n\nconst vector = devalue.parse(stringified, {\n\tVector: ([x, y]) =\u003e new Vector(x, y)\n});\n\nconsole.log(vector.magnitude()); // 50\n```\n\nIf a function passed to `stringify` returns a truthy value, it's treated as a match.\n\nYou can also use custom types with `uneval` by specifying a custom replacer:\n\n```js\ndevalue.uneval(vector, (value, uneval) =\u003e {\n\tif (value instanceof Vector) {\n\t\treturn `new Vector(${value.x},${value.y})`;\n\t}\n}); // `new Vector(30,40)`\n```\n\nNote that any variables referenced in the resulting JavaScript (like `Vector` in the example above) must be in scope when it runs.\n\n## Error handling\n\nIf `uneval` or `stringify` encounters a function or a non-POJO that isn't handled by a custom replacer/reducer, it will throw an error. You can find where in the input data the offending value lives by inspecting `error.path`:\n\n```js\ntry {\n\tconst map = new Map();\n\tmap.set('key', function invalid() {});\n\n\tuneval({\n\t\tobject: {\n\t\t\tarray: [map]\n\t\t}\n\t});\n} catch (e) {\n\tconsole.log(e.path); // '.object.array[0].get(\"key\")'\n}\n```\n\n## XSS mitigation\n\nSay you're server-rendering a page and want to serialize some state, which could include user input. `JSON.stringify` doesn't protect against XSS attacks:\n\n```js\nconst state = {\n\tuserinput: `\u003c/script\u003e\u003cscript src='https://evil.com/mwahaha.js'\u003e`\n};\n\nconst template = `\n\u003cscript\u003e\n  // NEVER DO THIS\n  var preloaded = ${JSON.stringify(state)};\n\u003c/script\u003e`;\n```\n\nWhich would result in this:\n\n```html\n\u003cscript\u003e\n\t// NEVER DO THIS\n\tvar preloaded = {\"userinput\":\"\n\u003c/script\u003e\n\u003cscript src=\"https://evil.com/mwahaha.js\"\u003e\n\t\"};\n\u003c/script\u003e\n```\n\nUsing `uneval` or `stringify`, we're protected against that attack:\n\n```js\nconst template = `\n\u003cscript\u003e\n  var preloaded = ${uneval(state)};\n\u003c/script\u003e`;\n```\n\n```html\n\u003cscript\u003e\n\tvar preloaded = {\n\t\tuserinput:\n\t\t\t\"\\\\u003C\\\\u002Fscript\\\\u003E\\\\u003Cscript src='https:\\\\u002F\\\\u002Fevil.com\\\\u002Fmwahaha.js'\\\\u003E\"\n\t};\n\u003c/script\u003e\n```\n\nThis, along with the fact that `uneval` and `stringify` bail on functions and non-POJOs, stops attackers from executing arbitrary code. Strings generated by `uneval` can be safely deserialized with `eval` or `new Function`:\n\n```js\nconst value = (0, eval)('(' + str + ')');\n```\n\n## Other security considerations\n\nWhile `uneval` prevents the XSS vulnerability shown above, meaning you can use it to send data from server to client, **you should not send user data from client to server** using the same method. Since it has to be evaluated, an attacker that successfully submitted data that bypassed `uneval` would have access to your system.\n\nWhen using `eval`, ensure that you call it _indirectly_ so that the evaluated code doesn't have access to the surrounding scope:\n\n```js\n{\n\tconst sensitiveData = 'Setec Astronomy';\n\teval('sendToEvilServer(sensitiveData)'); // pwned :(\n\t(0, eval)('sendToEvilServer(sensitiveData)'); // nice try, evildoer!\n}\n```\n\nUsing `new Function(code)` is akin to using indirect eval.\n\n## See also\n\n- [lave](https://github.com/jed/lave) by Jed Schmidt\n- [arson](https://github.com/benjamn/arson) by Ben Newman. The `stringify`/`parse` approach in `devalue` was inspired by `arson`\n- [oson](https://github.com/KnorpelSenf/oson) by Steffen Trog\n- [tosource](https://github.com/marcello3d/node-tosource) by Marcello Bastéa-Forte\n- [serialize-javascript](https://github.com/yahoo/serialize-javascript) by Eric Ferraiuolo\n- [jsesc](https://github.com/mathiasbynens/jsesc) by Mathias Bynens\n- [superjson](https://github.com/blitz-js/superjson) by Blitz\n- [next-json](https://github.com/iccicci/next-json) by Daniele Ricci\n\n## License\n\n[MIT](LICENSE)\n","funding_links":[],"categories":["TypeScript","JavaScript","*.js","others","Packages"],"sub_categories":["General"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FRich-Harris%2Fdevalue","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FRich-Harris%2Fdevalue","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FRich-Harris%2Fdevalue/lists"}