{"id":15608724,"url":"https://github.com/hypercubed/smykowski","last_synced_at":"2025-04-28T11:46:53.767Z","repository":{"id":39634072,"uuid":"124993610","full_name":"Hypercubed/smykowski","owner":"Hypercubed","description":" I deal with the goddamn objects so JSON doesn’t have to!! I have object skills!!","archived":false,"fork":false,"pushed_at":"2023-04-17T09:55:28.000Z","size":406,"stargazers_count":4,"open_issues_count":12,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-04-24T00:38:59.676Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"https://hypercubed.github.io/smykowski/","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/Hypercubed.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}},"created_at":"2018-03-13T04:45:50.000Z","updated_at":"2022-06-06T20:20:44.000Z","dependencies_parsed_at":"2023-01-21T23:16:40.699Z","dependency_job_id":null,"html_url":"https://github.com/Hypercubed/smykowski","commit_stats":null,"previous_names":[],"tags_count":10,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Hypercubed%2Fsmykowski","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Hypercubed%2Fsmykowski/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Hypercubed%2Fsmykowski/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Hypercubed%2Fsmykowski/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Hypercubed","download_url":"https://codeload.github.com/Hypercubed/smykowski/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":251309522,"owners_count":21568840,"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-10-03T05:22:01.346Z","updated_at":"2025-04-28T11:46:53.743Z","avatar_url":"https://github.com/Hypercubed.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Smykowski\n\n*I deal with the goddamn objects so JSON doesn’t have to!! I have object skills!!*\n\n## The Story\n\n![](https://i.imgflip.com/26xqmj.jpg)\n\nSo what you do is you take the JavaScript objects and you pass them down to the JSON?\n\n*That, that's right.*\n\nWell, then I gotta ask, then why can't the objects go directly to JSON, huh?\n\n*Well, uh, uh, uh, because, uh, JSON is not good at dealing with some objects.*\n\nYou convert the JS objects for JSON?\n\n*Well, no, my, my plugins do that, or, or your plugins do.*\n\nAh. Then you must then convert the JSON back to objects.\n\n*Well...no. Yeah, I mean, sometimes.*\n\nWell, what would you say… you do here?\n\n*Well, look, I already told you. I deal with the goddamn objects so \nJSON doesn’t have to!! I have object skills!! I am good at \ndealing with objects!!! Can't you understand that?!? WHAT THE HELL IS \nWRONG WITH YOU PEOPLE?!!!!!!!*\n\n## Goals\n\nCreate a pluggable tool to encode and decode JavaScript objects to and from data structures compatible with the JSON.  Non-goals include parsing/stringifying to/from non-JSON strings.\n\n## Current plugin features (see [Supplied plugins](#supplied-plugins) below)\n\n* cyclical and repeated references\n* `undefined`, ±`Infinity`, `NaN`, `-0`\n* regexp, dates, buffers, maps and sets\n* deterministic (sorted) hash of objects\n* `toJSON` and `fromJSON`\n* registered classes\n\n## Install\n\n```bash\nnpm i smykowski\n```\n\n## Usage\n\n```js\nimport {  Smykowski, defaultEncoders, defaultDecoders } from 'smykowski';\n\nconst asjon = new Smykowski()\n  .use(defaultEncoders)\n  .use(defaultDecoders);\n\nconst obj = {\n  _id: '5aa882d3638a0f580d92c677',\n  index: 0,\n  name: {\n    first: 'Valenzuela',\n    last: 'Valenzuela'\n  },\n  registered: new Date(2014, 0, 1),\n  symbol: Symbol('banana'),\n  range: [\n    -Infinity, 0, 1, 2, 3, 4, 5, 6, 7, 8, Infinity\n  ],\n  friends: [\n    {\n      id: -0,\n      name: 'Benton Chase'\n    },\n    {\n      id: 1,\n      name: 'Mccarthy Morgan'\n    },\n    {\n      id: NaN,\n      name: 'Kaufman Price'\n    }\n  ]\n};\n\nconst stringified = asjon.stringify(obj, null, 2);\nconsole.log(stringified);\n```\n\nyields:\n\n```json\n{\n  \"_id\": \"5aa882d3638a0f580d92c677\",\n  \"index\": 0,\n  \"name\": {\n    \"first\": \"Valenzuela\",\n    \"last\": \"Valenzuela\"\n  },\n  \"registered\": {\n    \"$date\": \"2014-01-01T07:00:00.000Z\"\n  },\n  \"symbol\": {\n    \"$symbol\": \"banana\"\n  },\n  \"range\": [\n    {\n      \"$numberDecimal\": \"-Infinity\"\n    },\n    0,\n    1,\n    2,\n    3,\n    4,\n    5,\n    6,\n    7,\n    8,\n    {\n      \"$numberDecimal\": \"Infinity\"\n    }\n  ],\n  \"friends\": [\n    {\n      \"id\": {\n        \"$numberDecimal\": \"-0\"\n      },\n      \"name\": \"Benton Chase\"\n    },\n    {\n      \"id\": 1,\n      \"name\": \"Mccarthy Morgan\"\n    },\n    {\n      \"id\": {\n        \"$numberDecimal\": \"NaN\"\n      },\n      \"name\": \"Kaufman Price\"\n    }\n  ]\n}\n```\n\nthen\n\n```ts\nconst parsed = asjon.parse(stringified);\nconsole.log(parsed);\n```\n\nreturns the a new object \"equal\" to the first.\n\n## Plugins\n\nIn `Smykowski` we have a concept of encoders, decoders, and plugins.  Encoders and decoders are functions that take no arguments and return a \"replacer\" or \"reviver\" function with the signature `(value: any, path: Array\u003cstring | number\u003e)`.  For encoders the \"replacer\" function should return the encoded JS object that will continue to be processed by additional plugins.  For decoders the \"reviver\" function returns the decoded JS object.  The `path` value may be user to write more advanced encoders/decoders.\n\nFor example, a very simple encoder that replaces all values that are not arrays with `\"foo\"`:\n\n```ts\nconst foo = () =\u003e {\n  const FOO = 'foo';\n  return value =\u003e {\n    return Array.isArray(value) ? v : FOO;\n  };\n};\n```\n\nA more complex examples is an encoder that replaces duplicate values with JSON pointers:\n\n```ts\nexport const jsonPointer = () =\u003e {\n  const repeated = new WeakMap();\n  return (v, path: Path) =\u003e {\n    if (v !== null \u0026\u0026 typeof v === 'object') {\n      if (repeated.has(v)) {\n        return { $ref: '#' + jsonpointer.compile(repeated.get(v)) };\n      }\n      repeated.set(v, path);\n    }\n    return v;\n  };\n};\n```\n\nThe order of the plugins does matter.  In this case `jsonPointer` should come last:\n\n```ts\nconst asjon = new Smykowski()\n  .addEncoder(foo)\n  .addEncoder(jsonPointer);\n\nconst arr = [1, 2, 3];\nasjon.stringify([arr, arr]);\n```\n\nyields:\n\n```json\n[[\"foo\",\"foo\",\"foo\"],{\"$ref\":\"foo\"}]\n```\n\nDecoders work similarly using the `addDecoder` method.  A plug-in is a set of encoders and/or decoders like so:\n\n```ts\nfunction myPlugin(_: Smykowski) {\n  return _\n    .addEncoder(recurseArrays)\n    .addEncoder(foo)\n    .addDecoder(myDecoder);\n}\n\nconst asjon = new Smykowski()\n  .use(myPlugin);\n```\n\nNote: `use(fn)` is sugar for `fn(asjon)`.\n\n## Supplied plugins\n\n### `defaultEncoders`\n\n* `encodeJSONPointer`: Replaces cycles and repeated objects with [JSON Pointers](https://tools.ietf.org/html/rfc6901).\n* `encodeBuffers`: Encodes buffers n the form of `{ $binary: '...' }` where the string is the Base64 encoded value.\n* `encodeMap`: Encodes Maps returning the result in the form of `{ $map: [[...]] }`\n* `encodeSet`: Encodes plain Sets returning the result in the form of `{ $set: [...] }`\n* `encodeSpecialNumbers`: Returns special numeric values (`-0`, `NaN` and +/-`Infinity`) as a strict [MongoDB Extended JSON numberDecimal](https://docs.mongodb.com/manual/reference/mongodb-extended-json/#numberdecimal) (`{ $numberDecimal: \"...\" }`).\n* `encodeUndefined`: Returns undefined values as a strict [MongoDB Extended JSON undefined](https://docs.mongodb.com/manual/reference/mongodb-extended-json/#undefined-type) (`{ $undefined: true }`).\n* `encodeRegexps`: Returns regular expression values as a strict [MongoDB Extended JSON Regular Expressions](https://docs.mongodb.com/manual/reference/mongodb-extended-json/#regular-expression) (`{ \"$regex\": \"...\", \"$options\": \"...\" }`).\n* `encodeDates`: Returns dates as a strict [MongoDB Extended JSON Regular Date](https://docs.mongodb.com/manual/reference/mongodb-extended-json/#date) (`{ \"$date\": \"...\" }`).\n* `encodeSymbols`: Returns symbols in the form of `{ $symbol: \"...\" }`\n* `toJSON`: Returns the result of the `toJSON` method for objects whose `toJSON` property is a function\n* `stableObject`: Sorts object properties by key (get a consistent hash from objects)\n\n### `defaultDecoders`\n\n* `decodeSpecialNumbers`: Decodes special numeric values.\n* `decodeUndefined`: Decodes `undefined`.\n* `decodeRegexps`: Decodes `Regexp` values.\n* `decodeDates`: Decodes `Date`s.\n* `decodeSymbols`: Decodes `Symbol`s.\n* `decodeMap`: Decodes `Map`s.\n* `decodeSet`: Decodes `Set`s.\n* `decodeBuffers`: Decodes `Buffer`.\n* `decodeJSONPointers`: Decodes JSON pointers.\n\n### `classSerializer`\n\nThis plugin registers classes for encoding/decoding using typed hints.  For example:\n\n```ts\nclass Person {\n  constructor(public first: string, public last: string) {\n\n  }\n\n  getFullname() {\n    return this.first + ' ' + this.last;\n  }\n}\n\nclass Employee extends Person {\n  constructor(public first: string, public last: string, public id: string) {\n    super(first, last);\n\n  }\n}\n\nconst ajson = new Smykowski()\n  .use(classSerializer, { Person, Employee })\n  .use(defaultEncoders)\n  .use(defaultDecoders);\n\nconst str = ajson.stringify([new Person('John', 'Doe'), new Employee('Jane', 'Doe', 'A123')]);\n/*\n  [\n    {\n      \"@@Person\": {\n        \"first\": \"John\",\n        \"last\": \"Doe\"\n      }\n    },\n    {\n      \"@@Employee\": {\n        \"first\": \"Jane\",\n        \"id\": \"A123\",\n        \"last\": \"Doe\"\n    }\n  ]\n*/\n\nconst [ john, jane ] = ajson.parse(str) as [Person, Employee];\n\njohn instanceof Person; // true\njane instanceof Employee; // true\njane.getFullname(); // \"Jane Doe\"\n```\n\n## Alternatives\n\n* [Rich-Harris/devalue](https://github.com/Rich-Harris/devalue)\n* [benjamn/arson](https://github.com/benjamn/arson)\n* [tj/eson](https://github.com/tj/eson)\n* [substack/json-stable-stringify](https://github.com/substack/json-stable-stringify)\n* [JohnWeisz/TypedJSON](https://github.com/JohnWeisz/TypedJSON)\n\n## License\n\nThis project is licensed under the MIT License - see the LICENSE file for details\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhypercubed%2Fsmykowski","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fhypercubed%2Fsmykowski","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhypercubed%2Fsmykowski/lists"}