export const fixJson = (ast, stack) => { const fixValue = last => { if (last.type === 'text' && /\d\.$/.test(last.value)) { last.value += '0'; } else if (last.type === 'text' && /tr?u?e?$/.test(last.value)) { last.value = 'true'; } else if (last.type === 'text' && /fa?l?s?e?$/.test(last.value)) { last.value = 'false'; } else if (last.type === 'text' && /nu?l?l?$/.test(last.value)) { last.value = 'null'; } }; while (stack.length > 1) { const block = stack[stack.length - 1]; const last = block.nodes[block.nodes.length - 1]; let siblings; let lastIndex; switch (block.name) { case 'string': block.nodes.push({ type: 'string_close', value: '"' }); break; case 'property': if (last.type === 'colon') { block.nodes.push({ type: 'text', value: '""' }); } else if (last.type === 'comma') { block.nodes.pop(); } lastIndex = Math.max(block.nodes.findLastIndex(node => node.type === 'comma'), 0); siblings = block.nodes.slice(lastIndex); if (siblings.length > 1 && !siblings.some(node => node.type === 'colon')) { block.nodes.push({ type: 'colon', value: ':' }); block.nodes.push({ type: 'text', value: '""' }); } else { fixValue(last); } block.nodes.push({ type: 'property_close', value: '}' }); break; case 'array': if (last.type === 'comma') { block.nodes.pop(); } else { fixValue(last); } block.nodes.push({ type: 'array_close', value: ']' }); break; default: { console.log(block.name); break; } } stack.pop(); } return ast; }; // eslint-disable-next-line complexity export const parseJson = (input: string, options: unknown) => { const source = input.trim(); const root = { type: 'root', nodes: [] as any[] }; const stack = [root]; let block = root; let prev; let last; const push = (node: any) => { if (node.type === 'text' && prev?.type === 'text') { prev.value += node.value || ''; return; } if (node.type === 'block') { block.nodes.push(node); stack.push(node); block = node; } else { block.nodes.push(node); } prev = node; }; for (let i = 0; i < source.length; i++) { const value = source[i]; if (value === '\\') { push({ type: 'text', value: value + (source[++i] || '\\') }); continue; } if (options?.whitespace !== false) { if (value === '\r' || value === '\n' || value === ' ' || value === '\t') { if (block.name !== 'string') { continue; } } } if (value === ':' && block.name === 'property') { push({ type: 'colon', value }); continue; } if (value === ',' && block.name === 'array') { push({ type: 'comma', value }); continue; } if (value === ',') { if (block.name === 'property') { push({ type: 'comma', value }); } else { push({ type: 'text', value }); } continue; } if (value === '"') { if (block.name === 'string') { block = stack.pop(); block.nodes.push({ type: 'string_close', value }); block = stack[stack.length - 1]; prev = block.nodes[block.nodes.length - 1]; continue; } push({ type: 'block', name: 'string', nodes: [] }); push({ type: 'string_open', value }); continue; } if (value === '[') { push({ type: 'block', name: 'array', nodes: [] }); push({ type: 'array_open', value }); continue; } if (value === ']' && block.name === 'array') { stack.pop(); block.nodes.push({ type: 'array_close', value }); block = stack[stack.length - 1]; prev = block.nodes[block.nodes.length - 1]; continue; } if (value === '{') { push({ type: 'block', name: 'property', nodes: [] }); push({ type: 'property_open', value }); continue; } if (value === '}' && block.name === 'property') { last = block.nodes[block.nodes.length - 1]; if (last?.type === 'text') { last = block.nodes.pop(); const string = { type: 'block', name: 'string', nodes: [] }; string.nodes.push({ type: 'string_open', value: '"' }); string.nodes.push(last); string.nodes.push({ type: 'string_close', value: '"' }); block.nodes.push(string); last = block.nodes[block.nodes.length - 1]; } switch (block.nodes.length) { case 2: block.nodes.push({ type: 'colon', value: ':' }); block.nodes.push({ type: 'text', value: '""' }); break; case 3: block.nodes.push({ type: 'text', value: '""' }); break; default: { break; } } stack.pop(); block.nodes.push({ type: 'property_close', value }); block = stack[stack.length - 1]; prev = block.nodes[block.nodes.length - 1]; continue; } if (block.name === 'string') { push({ type: 'text', value }); continue; } push({ type: 'text', value }); } if (options?.fix === true) { return fixJson(root.nodes[0], stack); } return root.nodes[0]; }; export const stringifyJson = (node: any) => { if (!node?.type) return '{}'; if (node.type === 'block') { return node.nodes.map(n => stringifyJson(n)).join(''); } return node.value; };