slow-json-stringify
Version:
The slowest JSON stringifier in the galaxy (:
1 lines • 10.8 kB
Source Map (JSON)
{"version":3,"file":"sjs.mjs","sources":["../src/_prepare.js","../src/_utils.mjs","../src/_makeQueue.mjs","../src/_makeChunks.mjs","../src/_select.js","../src/sjs.mjs"],"sourcesContent":["\n/**\n * `_prepare` - aims to normalize the schema provided by the user.\n * It will convert the schema in both a parseable string and an object\n * useable for making the chunks needed for the serialization part.\n * @param {object} schema - user provided schema\n */\nconst _prepare = (schema) => {\n const preparedString = JSON.stringify(schema, (_, value) => {\n if (!value.isSJS) return value;\n return `${value.type}__sjs`;\n });\n\n const preparedSchema = JSON.parse(preparedString);\n\n return {\n preparedString,\n preparedSchema,\n };\n};\n\nexport default _prepare;\n","\n/**\n * `_find` is a super fast deep property finder.\n * It dynamically build the function needed to reach the desired\n * property.\n *\n * e.g.\n * obj = {a: {b: {c: 1}}}\n * _find(['a','b','c']) => (obj) => (((obj || {}).a || {}).b || {}).c\n *\n * @param {array} path - path to reach object property.\n */\nconst _find = (path) => {\n const { length } = path;\n\n let str = 'obj';\n\n for (let i = 0; i < length; i++) {\n str = str.replace(/^/, '(');\n str += ` || {}).${path[i]}`;\n }\n\n return eval(`((obj) => ${str})`);\n};\n\n/**\n * `_makeArraySerializer` is simply a wrapper of another `sjs` schema\n * used for the serialization of arrais.\n *\n * @param {array} array - Array to serialize.\n * @param {any} method - `sjs` serializer.\n */\nconst _makeArraySerializer = (serializer) => {\n if (serializer instanceof Function) {\n return (array) => {\n // Stringifying more complex array using the provided sjs schema\n let acc = '';\n const { length } = array;\n for (let i = 0; i < length - 1; i++) {\n acc += `${serializer(array[i])},`;\n }\n\n // Prevent slice for removing unnecessary comma.\n acc += serializer(array[length - 1]);\n return `[${acc}]`;\n };\n }\n\n return array => JSON.stringify(array);\n};\n\nconst TYPES = [\n 'number',\n 'string',\n 'boolean',\n 'array',\n 'null',\n];\n\nconst attr = (type, serializer) => {\n if (!TYPES.includes(type)) {\n throw new Error(`Expected one of: \"number\", \"string\", \"boolean\", \"null\". received \"${type}\" instead`);\n }\n\n const usedSerializer = serializer || (value => value);\n\n return {\n isSJS: true,\n type,\n serializer: type === 'array'\n ? _makeArraySerializer(serializer)\n : usedSerializer,\n };\n};\n\n// Little utility for escaping convenience.\n// => if no regex is provided, a default one will be used.\nconst defaultRegex = new RegExp('\\\\n|\\\\r|\\\\t|\\\\\"|\\\\\\\\', 'gm');\nconst escape = (regex = defaultRegex) => str => str.replace(regex, char => '\\\\' + char);\n\nexport {\n _find,\n escape,\n attr,\n};\n","import { _find } from './_utils';\n\n/**\n * @param {object} preparedSchema - schema already validated\n * with modified prop values to avoid clashes.\n * @param {object} originalSchema - User provided schema\n * => contains array stringification serializers that are lost during preparation.\n */\nexport default (preparedSchema, originalSchema) => {\n const queue = [];\n\n // Defining a function inside an other function is slow.\n // However it's OK for this use case as the queue creation is not time critical.\n (function scoped(obj, acc = []) {\n if (/__sjs/.test(obj)) {\n const usedAcc = Array.from(acc);\n const find = _find(usedAcc);\n const { serializer } = find(originalSchema);\n\n queue.push({\n serializer,\n find,\n name: acc[acc.length - 1],\n });\n return;\n }\n\n // Recursively going deeper.\n // NOTE: While going deeper, the current prop is pushed into the accumulator\n // to keep track of the position inside of the object.\n return Object\n .keys(obj)\n .map(prop => scoped(obj[prop], [...acc, prop]));\n })(preparedSchema);\n\n return queue;\n};\n","/**\n * @param {string} str - prepared string already validated.\n * @param {array} queue - queue containing the property name to match\n * (used for building dynamic regex) needed for the preparation of\n * chunks used in different scenarios.\n */\nexport default (str, queue) => str\n // Matching prepared properties and replacing with target with or without\n // double quotes.\n // => Avoiding unnecessary concatenation of doublequotes during serialization.\n .replace(/\"\\w+__sjs\"/gm, type => (/string/.test(type) ? '\"__par__\"' : '__par__'))\n .split('__par__')\n .map((chunk, index, chunks) => {\n // Using dynamic regex to ensure that only the correct property\n // at the end of the string it's actually selected.\n // => e.g. ,\"a\":{\"a\": => ,\"a\":{\n const matchProp = `(\"${(queue[index] || {}).name}\":(\\\"?))$`;\n const matchWhenLast = `(\\,?)${matchProp}`;\n\n // Check if current chunk is the last one inside a nested property\n const isLast = /^(\"}|})/.test(chunks[index + 1] || '');\n\n // If the chunk is the last one the `isUndef` case should match\n // the preceding comma too.\n const matchPropRe = new RegExp(isLast ? matchWhenLast : matchProp);\n\n // 3 possibilities after arbitrary property:\n // - \", => non-last string property\n // - , => non-last non-string property\n // - \" => last string property\n const matchStartRe = /^(\\\"\\,|\\,|\\\")/;\n\n return {\n // notify that the chunk preceding the current one has not\n // its corresponding property undefined.\n // => This is a V8 optimization as it's way faster writing\n // the value of a property than writing the entire property.\n flag: false,\n pure: chunk,\n // Without initial part\n prevUndef: chunk.replace(matchStartRe, ''),\n // Without property chars\n isUndef: chunk.replace(matchPropRe, ''),\n // Only remaining chars (can be zero chars)\n bothUndef: chunk\n .replace(matchStartRe, '')\n .replace(matchPropRe, ''),\n };\n });\n","/**\n * `select` function takes all the possible chunks from the\n * current index and set the more appropriate one in relation\n * to the current `value` and the `flag` state.\n *\n * => This approach avoids the use of Regex during serialization.\n *\n * @param {any} value - value to serialize.\n * @param {number} index - position inside the queue.\n */\nconst _select = chunks => (value, index) => {\n const chunk = chunks[index];\n\n if (typeof value !== 'undefined') {\n if (chunk.flag) {\n return chunk.prevUndef + value;\n }\n return chunk.pure + value;\n }\n\n // If the current value is undefined set a flag on the next\n // chunk stating that the previous prop is undefined.\n chunks[index + 1].flag = true;\n\n if (chunk.flag) {\n return chunk.bothUndef;\n }\n return chunk.isUndef;\n};\n\nexport default _select;\n","import _prepare from './_prepare';\nimport _makeQueue from './_makeQueue';\nimport _makeChunks from './_makeChunks';\nimport _select from './_select';\nimport { attr, escape } from './_utils';\n\n// Doing a lot of preparation work before returning the final function responsible for\n// the stringification.\nconst sjs = (schema) => {\n const { preparedString, preparedSchema } = _prepare(schema);\n\n // Providing preparedSchema for univocal correspondence between created queue and chunks.\n // Provided original schema to keep track of the original properties that gets destroied\n // during schema preparation => e.g. array stringification method.\n const queue = _makeQueue(preparedSchema, schema);\n const chunks = _makeChunks(preparedString, queue);\n const selectChunk = _select(chunks);\n\n const { length } = queue;\n\n // Exposed function\n return (obj) => {\n let temp = '';\n\n // Ditching old implementation for a **MUCH** faster while\n let i = 0;\n while (true) {\n if (i === length) break;\n const { serializer, find } = queue[i];\n const raw = find(obj);\n\n temp += selectChunk(serializer(raw), i);\n\n i += 1;\n }\n\n const { flag, pure, prevUndef } = chunks[chunks.length - 1];\n\n return temp + (flag ? prevUndef : pure);\n };\n};\n\nexport {\n sjs,\n attr,\n escape,\n};\n"],"names":["const","_prepare","schema","preparedString","JSON","stringify","_","value","isSJS","parse","_find","path","length","str","i","replace","eval","_makeArraySerializer","serializer","Function","array","acc","TYPES","attr","type","includes","Error","usedSerializer","defaultRegex","RegExp","escape","regex","char","preparedSchema","originalSchema","queue","scoped","obj","test","Object","keys","map","prop","usedAcc","Array","from","find","push","name","split","chunk","index","chunks","matchProp","matchWhenLast","isLast","matchPropRe","matchStartRe","flag","pure","prevUndef","isUndef","bothUndef","_select","sjs","_makeQueue","_makeChunks","selectChunk","temp","raw"],"mappings":"AAOAA,IAAMC,kBAAYC,OACVC,EAAiBC,KAAKC,UAAUH,WAASI,EAAGC,UAC3CA,EAAMC,MACDD,eADeA,UAMpB,gBACLJ,iBAHqBC,KAAKK,MAAMN,KCD9BO,eAASC,UACLC,uBAEJC,IAAM,MAEDC,EAAI,EAAGA,EAAIF,OAAQE,IAC1BD,IAAMA,IAAIE,QAAQ,IAAK,KACvBF,KAAQ,WAAUF,KAAKG,UAGlBE,kBAAkBH,UAUrBI,8BAAwBC,UACxBA,aAAsBC,kBAChBC,WAEFC,EAAM,cAEDP,EAAI,EAAGA,EAAIF,EAAS,EAAGE,IAC9BO,GAAUH,EAAWE,EAAMN,mBAI7BO,GAAOH,EAAWE,EAAMR,EAAS,mBAK9BQ,UAAShB,KAAKC,UAAUe,KAG3BE,MAAQ,CACZ,SACA,SACA,UACA,QACA,QAGIC,cAAQC,EAAMN,OACbI,MAAMG,SAASD,SACZ,IAAIE,2EAA2EF,mBAGjFG,EAAiBT,YAAeX,UAASA,SAExC,CACLC,OAAO,OACPgB,EACAN,WAAqB,UAATM,EACRP,qBAAqBC,GACrBS,IAMFC,aAAe,IAAIC,OAAO,uBAAwB,MAClDC,gBAAUC,yBAAQH,uBAAiBf,UAAOA,EAAIE,QAAQgB,WAAOC,SAAQ,KAAOA,0BCtElEC,EAAgBC,OACxBC,EAAQ,mBAIJC,EAAOC,EAAKhB,qBAAM,KACtB,QAAQiB,KAAKD,UAgBVE,OACJC,KAAKH,GACLI,aAAIC,UAAQN,EAAOC,EAAIK,GAAWrB,UAAKqB,WAjBlCC,EAAUC,MAAMC,KAAKxB,GACrByB,EAAOpC,MAAMiC,KACIG,EAAKZ,GAE5BC,EAAMY,KAAK,8BAETD,EACAE,KAAM3B,EAAIA,EAAIT,OAAS,MAW1BqB,GAEIE,wBC7BOtB,EAAKsB,UAAUtB,EAI5BE,QAAQ,wBAAgBS,SAAS,SAASc,KAAKd,GAAQ,YAAc,YACrEyB,MAAM,WACNR,aAAKS,EAAOC,EAAOC,OAIZC,EAAa,MAAKlB,EAAMgB,IAAU,oBAClCG,EAAiB,OAAOD,EAGxBE,EAAS,UAAUjB,KAAKc,EAAOD,EAAQ,IAAM,IAI7CK,EAAc,IAAI3B,OAAO0B,EAASD,EAAgBD,GAMlDI,EAAe,sBAEd,CAKLC,MAAM,EACNC,KAAMT,EAENU,UAAWV,EAAMnC,QAAQ0C,EAAc,IAEvCI,QAASX,EAAMnC,QAAQyC,EAAa,IAEpCM,UAAWZ,EACRnC,QAAQ0C,EAAc,IACtB1C,QAAQyC,EAAa,QCpCxBO,iBAAUX,mBAAW7C,EAAO4C,OAC1BD,EAAQE,EAAOD,eAEA,IAAV5C,EACL2C,EAAMQ,KACDR,EAAMU,UAAYrD,EAEpB2C,EAAMS,KAAOpD,GAKtB6C,EAAOD,EAAQ,GAAGO,MAAO,EAErBR,EAAMQ,KACDR,EAAMY,UAERZ,EAAMW,WCnBTG,aAAO9D,SACgCD,SAASC,sBAK9CiC,EAAQ8B,4BAA2B/D,GACnCkD,EAASc,YAAY/D,EAAgBgC,GACrCgC,EAAcJ,QAAQX,8BAKpBf,WACF+B,EAAO,GAGPtD,EAAI,EAEFA,IAAMF,GADC,OAEkBuB,EAAMrB,kBAC7BuD,GAAMvB,UAAKT,GAEjB+B,GAAQD,EAAYjD,EAAWmD,GAAMvD,GAErCA,GAAK,QAG2BsC,EAAOA,EAAOxC,OAAS,UAElDwD"}