UNPKG

@exadel/esl

Version:

Exadel Smart Library (ESL) is the lightweight custom elements library that provide a set of super-flexible components

108 lines (107 loc) 5.21 kB
import { isObject } from '../object/types'; // --------------------------------------------------------------------------- // RegExp constants (hoisted for perf & clarity) // --------------------------------------------------------------------------- const RE_BOOL_NULL = /^(?:true|false|null)$/; // exact match of JSON literals const RE_TOP_LEVEL_STRING = /^(?:"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*')$/; // single OR double quoted const RE_NUMBER = /^[+-]?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?$/; // lenient number (leading zeros allowed) const RE_STRING_LITERALS = /(["'])(?:\\.|(?!\1).)*\1/g; // matches string literals, honoring escapes const RE_UNQUOTED_KEY = /([{,]\s*)([a-zA-Z_$][\w$]*|\d+)(?=\s*:)/g; // quote identifier or numeric keys const RE_TRAILING_COMMA = /,\s*([}\]])/g; // strip trailing comma before } or ] const RE_STASHED_STRING = /@__STR(\d+)__/g; // restore placeholder -> original string literal /** Transpile JS-style string literal to JSON string */ function toJSONString(src) { if (src[0] === '"') return src; const inner = src.slice(1, -1).replace(/"/g, '\\"'); return '"' + inner + '"'; } /** Transpile original {@link parseObject} input syntax to valid JSON */ function toJSON(raw) { let src = raw; // Add wrapping braces for shorthand object (when not starting with { or [) if (src[0] !== '{' && src[0] !== '[') src = '{' + src + '}'; // Extract & protect string literals (both quote styles). While stashed we can safely // mutate separators & keys. Also normalize single quoted literals -> double quoted JSON form. const stash = []; src = src.replace(RE_STRING_LITERALS, (m) => { stash.push(toJSONString(m)); return '@__STR' + (stash.length - 1) + '__'; }); // Separator normalization and key quoting sequence: // 1. Convert semicolons to commas // 2. Quote unquoted keys (identifier / numeric) appearing before ':' // 3. Remove trailing commas before a closing brace / bracket src = src .replace(/;/g, ',') .replace(RE_UNQUOTED_KEY, '$1"$2"') .replace(RE_TRAILING_COMMA, '$1'); // Restore previously stashed string literals src = src.replace(RE_STASHED_STRING, (_, i) => stash[+i]); return src; } /** * Extended object parser for lightweight config syntax (HTML attribute friendly). * * This function provides an extended parsing capability for strings representing * lightweight configuration syntax. It supports both strict JSON and a relaxed JSON-like * syntax, making it suitable for use in scenarios like HTML attributes or other * lightweight configuration formats. * * Supported features: * - **Strict JSON**: Fully supports JSON objects, arrays, primitives, and strings. * - **Relaxed JSON (JSONv5-lite)**: * - Allows unquoted keys (identifier or numeric). * - Supports single quotes for keys and string literals. * - Handles trailing commas gracefully. * - **Extra sugar**: * - Allows `;` as an alternative to `,` for entry separation in objects and arrays. * - Top-level object braces may be omitted (e.g., `a:1; b:2` is valid). * - **Top-level primitives**: Accepts boolean, null, numbers (including leading zeros or `+` sign), and strings. * * Not supported / intentionally omitted: * - **Expressions or identifiers as values**: For example, `{a: someVar}` is not supported. * - **Malformed escape sequences**: These may result in JSON parsing errors, consistent with `JSON.parse`. */ export function parseObject(value) { const src = String(value).trim(); // trimmed empty string -> null if (!src) throw TypeError('Cannot parse empty string'); // Boolean or null if (RE_BOOL_NULL.test(src)) return JSON.parse(src); // String literal if (RE_TOP_LEVEL_STRING.test(src)) return JSON.parse(toJSONString(src)); // Numeric literal if (RE_NUMBER.test(src)) return Number(src); // Object/array return JSON.parse(toJSON(src)); } /** * Safely parses a string into an object. * * This function is a safe wrapper around the {@link parseObject} function. It attempts * to parse the provided string using `parseObject`. If parsing fails (e.g., due to invalid * syntax), it catches the error and returns a fallback value instead of throwing an exception. * * @param value - The string to parse. It can represent JSON, relaxed JSON, or * lightweight config syntax. * @param fallback - The value to return if parsing fails, or function to execute if parsing fails. Defaults to `undefined`. * @param allowPrimitive - Whether to allow primitive values (e.g., numbers, strings) * as valid results. Defaults to `false`. * @returns The parsed object, array, or primitive value. If parsing fails, the * fallback value is returned. */ export function parseObjectSafe(value, fallback, allowPrimitive = false) { try { const parsed = parseObject(value); if (allowPrimitive || isObject(parsed)) return parsed; } catch ( /* noop */_a) { /* noop */ } return (typeof fallback === 'function') ? fallback(value) : fallback; }