UNPKG

locutus

Version:

Locutus other languages' standard libraries to JavaScript for fun and educational purposes

206 lines (205 loc) 8.59 kB
import { setPhpRuntimeEntry } from "../_helpers/_phpRuntimeState.js"; const isJsonObject = (value) => !Array.isArray(value); const unwrapBoxedJsonScalar = (value) => { if (value instanceof Number) { return Number(value.valueOf()); } if (value instanceof String) { return String(value.valueOf()); } if (value instanceof Boolean) { return Boolean(value.valueOf()); } return value; }; export function json_encode(mixedVal) { // discuss at: https://phpjs.org/functions/json_encode/ // parity verified: PHP 8.3 // original by: Public Domain (https://www.json.org/json2.js) // reimplemented by: Kevin van Zonneveld (https://kevin.vanzonneveld.net) // improved by: Michael White // input by: felix // bugfixed by: Brett Zamir (https://brett-zamir.me) // example 1: json_encode('Kevin') // returns 1: '"Kevin"' /* https://www.JSON.org/json2.js 2008-11-19 Public Domain. NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK. See https://www.JSON.org/js.html */ const json = typeof JSON === 'object' && JSON !== null ? JSON : null; let retVal; try { if (typeof json === 'object' && json !== null) { const stringify = json.stringify; if (typeof stringify === 'function') { let hasNonFiniteNumber = false; const replacer = (_key, value) => { const normalizedValue = unwrapBoxedJsonScalar(value); if (typeof normalizedValue === 'number' && !Number.isFinite(normalizedValue)) { hasNonFiniteNumber = true; } return normalizedValue; }; // Errors will not be caught here if our own equivalent to resource retVal = json.stringify(mixedVal, replacer); if (hasNonFiniteNumber) { throw new SyntaxError('json_encode_inf_or_nan'); } if (retVal === undefined) { throw new SyntaxError('json_encode'); } setPhpRuntimeEntry('last_error_json', 0); return retVal; } } const value = mixedVal; const quote = function (string) { const escapeChars = [ '\u0000-\u001f', '\u007f-\u009f', '\u00ad', '\u0600-\u0604', '\u070f', '\u17b4', '\u17b5', '\u200c-\u200f', '\u2028-\u202f', '\u2060-\u206f', '\ufeff', '\ufff0-\uffff', ].join(''); const escapable = new RegExp('[\\"' + escapeChars + ']', 'g'); const meta = { // table of character substitutions '\b': '\\b', '\t': '\\t', '\n': '\\n', '\f': '\\f', '\r': '\\r', '"': '\\"', '\\': '\\\\', }; escapable.lastIndex = 0; return escapable.test(string) ? '"' + string.replace(escapable, function (a) { const c = meta[a]; return typeof c === 'string' ? c : '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); }) + '"' : '"' + string + '"'; }; const _str = function (key, holder) { let gap = ''; const indent = ' '; // The loop counter. let i = 0; // The member key. let k = ''; // The member value. let v = ''; let length = 0; const mind = gap; let partial = []; let value = Array.isArray(holder) ? holder[Number(key)] : holder[String(key)]; // If the value has a toJSON method, call it to obtain a replacement value. if (typeof value === 'object' && value !== null) { if ('toJSON' in value && typeof value.toJSON === 'function') { value = value.toJSON.call(value, key); } } value = unwrapBoxedJsonScalar(value); // What happens next depends on the value's type. switch (typeof value) { case 'string': return quote(value); case 'number': if (!isFinite(value)) { throw new SyntaxError('json_encode_inf_or_nan'); } return String(value); case 'boolean': // If the value is a boolean or null, convert it to a string. return String(value); case 'object': // If the type is 'object', we might be dealing with an object or an array or // null. // Due to a specification blunder in ECMAScript, typeof null is 'object', // so watch out for that case. if (!value) { return 'null'; } // Make an array to hold the partial results of stringifying this object value. gap += indent; partial = []; // Is the value an array? if (Array.isArray(value)) { // The value is an array. Stringify every element. Use null as a placeholder // for non-JSON values. length = value.length; for (i = 0; i < length; i += 1) { partial[i] = _str(i, value) || 'null'; } // Join all of the elements together, separated with commas, and wrap them in // brackets. v = partial.length === 0 ? '[]' : gap ? '[\n' + gap + partial.join(',\n' + gap) + '\n' + mind + ']' : '[' + partial.join(',') + ']'; // gap = mind // not used return v; } // Iterate through all of the keys in the object. if (!isJsonObject(value)) { throw new SyntaxError('json_encode'); } for (const [entryKey] of Object.entries(value)) { k = entryKey; v = _str(k, value) || ''; if (v) { partial.push(quote(k) + (gap ? ': ' : ':') + v); } } // Join all of the member texts together, separated with commas, // and wrap them in braces. v = partial.length === 0 ? '{}' : gap ? '{\n' + gap + partial.join(',\n' + gap) + '\n' + mind + '}' : '{' + partial.join(',') + '}'; // gap = mind // Not used return v; case 'undefined': case 'function': default: throw new SyntaxError('json_encode'); } }; // Make a fake root object containing our value under the key of ''. // Return the result of stringifying the value. const encoded = _str('', { '': value, }); if (typeof encoded !== 'string') { throw new SyntaxError('json_encode'); } setPhpRuntimeEntry('last_error_json', 0); return encoded; } catch (err) { // @todo: ensure error handling above throws a SyntaxError in all cases where it could // (i.e., when the JSON global is not available and there is an error) if (!(err instanceof SyntaxError)) { throw new Error('Unexpected error type in json_encode()'); } // usable by json_last_error() setPhpRuntimeEntry('last_error_json', err.message === 'json_encode_inf_or_nan' ? 7 : 4); return null; } }