@rzl-zone/utils-js
Version:
A modern, lightweight set of JavaScript utility functions with TypeScript support for everyday development, crafted to enhance code readability and maintainability.
265 lines (257 loc) • 10.5 kB
JavaScript
/*!
* ====================================================
* Rzl Utils-JS.
* ----------------------------------------------------
* Version: 3.11.0.
* Author: Rizalvin Dwiky.
* Repository: https://github.com/rzl-zone/utils-js.
* ====================================================
*/
import { isEmptyArray, isEmptyObject } from './chunk-GOFINGT6.js';
import { noop } from './chunk-YWHHVDT4.js';
import { safeStableStringify } from './chunk-AXDYWO67.js';
import { isNonEmptyString, getPreciseType, isNull, isUndefined, isString, isArray, isObject, isNumber, isNaN as isNaN$1, isError, assertIsPlainObject, hasOwnProp, isBoolean, isFunction } from './chunk-MSUW5VHZ.js';
var parseCustomDate = (dateString, format) => {
if (!isNonEmptyString(dateString) || !isNonEmptyString(format)) {
throw new TypeError(
`Parameter \`dateString\` and \`format\` must be of type \`string\` and not empty-string, but received: "['dateString': \`${getPreciseType(
dateString
)}\` - (current value: \`${safeStableStringify(dateString, {
keepUndefined: true
})}\`), 'format': \`${getPreciseType(
format
)}\` - (current value: \`${safeStableStringify(format, {
keepUndefined: true
})}\`)]".`
);
}
const dateParts = dateString.split(/[-/]/).map(Number);
if (dateParts.length !== 3 || dateParts.some(isNaN)) return null;
let day, month, year;
if (format === "DD/MM/YYYY") {
[day, month, year] = dateParts;
} else if (format === "MM/DD/YYYY") {
[month, day, year] = dateParts;
} else {
return null;
}
month -= 1;
const date = new Date(year, month, day);
if (date.getFullYear() !== year || date.getMonth() !== month || date.getDate() !== day) {
return null;
}
return date;
};
var validateJsonParsingOptions = (optionsValue = {}) => {
assertIsPlainObject(optionsValue, {
message: ({ currentType, validType }) => `Second parameter (\`options\`) must be of type \`${validType}\`, but received: \`${currentType}\`.`
});
const convertBooleans = hasOwnProp(optionsValue, "convertBooleans") ? optionsValue.convertBooleans : false;
const convertDates = hasOwnProp(optionsValue, "convertDates") ? optionsValue.convertDates : false;
const convertNumbers = hasOwnProp(optionsValue, "convertNumbers") ? optionsValue.convertNumbers : false;
const loggingOnFail = hasOwnProp(optionsValue, "loggingOnFail") ? optionsValue.loggingOnFail : false;
const removeEmptyArrays = hasOwnProp(optionsValue, "removeEmptyArrays") ? optionsValue.removeEmptyArrays : false;
const removeEmptyObjects = hasOwnProp(optionsValue, "removeEmptyObjects") ? optionsValue.removeEmptyObjects : false;
const removeNulls = hasOwnProp(optionsValue, "removeNulls") ? optionsValue.removeNulls : false;
const removeUndefined = hasOwnProp(optionsValue, "removeUndefined") ? optionsValue.removeUndefined : false;
const strictMode = hasOwnProp(optionsValue, "strictMode") ? optionsValue.strictMode : false;
const checkSymbols = hasOwnProp(optionsValue, "checkSymbols") ? optionsValue.checkSymbols : false;
const convertNaN = hasOwnProp(optionsValue, "convertNaN") ? optionsValue.convertNaN : false;
const customDateFormats = hasOwnProp(optionsValue, "customDateFormats") ? optionsValue.customDateFormats : [];
const onError = hasOwnProp(optionsValue, "onError") ? optionsValue.onError : noop;
if (!(isBoolean(convertBooleans) && isBoolean(convertDates) && isBoolean(convertNumbers) && isBoolean(convertNaN) && isBoolean(checkSymbols) && isBoolean(loggingOnFail) && isBoolean(removeEmptyArrays) && isBoolean(removeEmptyObjects) && isBoolean(removeNulls) && isBoolean(removeUndefined) && isBoolean(strictMode) && isArray(customDateFormats) && isFunction(onError))) {
throw new TypeError(
`Invalid \`options\` parameter (second argument): \`convertBooleans\`, \`convertDates\`, \`convertNumbers\`, \`loggingOnFail\`, \`removeEmptyArrays\`, \`removeEmptyObjects\`, \`removeNulls\`, \`removeUndefined\`, \`strictMode\` expected to be a \`boolean\` type, \`customDateFormats\` expected to be a \`array\` type and \`onError\` expected to be a \`void function\` type. But received: ['convertBooleans': \`${getPreciseType(
convertBooleans
)}\`, 'convertDates': \`${getPreciseType(
convertDates
)}\`, 'convertNumbers': \`${getPreciseType(
convertNumbers
)}\`, 'loggingOnFail': \`${getPreciseType(
loggingOnFail
)}\`, 'removeEmptyArrays': \`${getPreciseType(
removeEmptyArrays
)}\`, 'removeEmptyObjects': \`${getPreciseType(
removeEmptyObjects
)}\`, 'removeNulls': \`${getPreciseType(
removeNulls
)}\`, 'removeUndefined': \`${getPreciseType(
removeUndefined
)}\`, 'strictMode': \`${getPreciseType(
strictMode
)}\`, 'customDateFormats': \`${getPreciseType(
customDateFormats
)}\`, 'onError': \`${getPreciseType(onError)}\`].`
);
}
return {
convertBooleans,
convertDates,
convertNumbers,
convertNaN,
loggingOnFail,
removeEmptyArrays,
removeEmptyObjects,
removeNulls,
removeUndefined,
strictMode,
customDateFormats,
onError,
checkSymbols
};
};
var cleanParsedData = (data, options = {}) => {
const validOptions = validateJsonParsingOptions(options);
if (isNull(data)) return validOptions.removeNulls ? void 0 : null;
if (isUndefined(data)) return validOptions.removeUndefined ? void 0 : void 0;
if (isString(data)) {
const trimmed = data.trim();
if (validOptions.convertNaN && trimmed === "NaN") return NaN;
if (validOptions.convertNumbers && !isNaN(Number(trimmed))) {
return Number(trimmed);
}
if (validOptions.convertBooleans) {
if (trimmed === "true") return true;
if (trimmed === "false") return false;
}
if (validOptions.convertDates) {
if (/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/.test(trimmed)) {
return new Date(trimmed);
}
if (validOptions.customDateFormats?.length) {
for (const format of validOptions.customDateFormats) {
const date = parseCustomDate(trimmed, format);
if (date) return date;
}
}
}
return validOptions.strictMode ? void 0 : trimmed;
}
if (isArray(data)) {
const cleanedArray = data.map((item) => cleanParsedData(item, validOptions)).filter((item) => !isUndefined(item));
return validOptions.removeEmptyArrays && isEmptyArray(cleanedArray) ? void 0 : cleanedArray;
}
if (isObject(data)) {
const cleanedObject = {};
for (const key in data) {
if (Object.prototype.hasOwnProperty.call(data, key)) {
const cleanedValue = cleanParsedData(data[key], validOptions);
if (!isUndefined(cleanedValue)) {
cleanedObject[key] = cleanedValue;
}
}
}
return validOptions.removeEmptyObjects && isEmptyObject(cleanedObject, { checkSymbols: validOptions.checkSymbols }) ? void 0 : cleanedObject;
}
return validOptions.strictMode ? void 0 : data;
};
var extractDigits = (value) => {
if (!isString(value) && !isNumber(value)) return 0;
const cleaned = String(value).trim().replace(/[^0-9]/g, "");
return Number(cleaned) || 0;
};
function fixSingleQuotesEscapeBackslash(input) {
const validEscapes = /* @__PURE__ */ new Set(["\\", '"', "/", "b", "f", "n", "r", "t", "u"]);
let output = "";
let inSingleQuote = false;
let inDoubleQuote = false;
let escapeNext = false;
for (let i = 0; i < input.length; i++) {
const c = input[i];
if (escapeNext) {
if (inSingleQuote) {
if (c === "'") {
output += "'";
} else if (validEscapes.has(c)) {
if (c === "\\") {
output += "\\\\";
} else if (c === '"') {
output += '\\"';
} else {
output += "\\" + c;
}
} else {
output += "\\\\" + c;
}
} else if (inDoubleQuote) {
if (c === '"') {
output += '\\"';
} else if (validEscapes.has(c)) {
output += "\\" + c;
} else {
output += "\\\\" + c;
}
} else {
output += "\\" + c;
}
escapeNext = false;
continue;
}
if (c === "\\") {
escapeNext = true;
continue;
}
if (!inSingleQuote && !inDoubleQuote) {
if (c === "'") {
output += '"';
inSingleQuote = true;
continue;
}
if (c === '"') {
output += '"';
inDoubleQuote = true;
continue;
}
} else if (inSingleQuote) {
if (c === "'") {
output += '"';
inSingleQuote = false;
continue;
}
} else if (inDoubleQuote) {
if (c === '"') {
output += '"';
inDoubleQuote = false;
continue;
}
}
output += c;
}
return output;
}
function safeJsonParse(value, options = {}) {
if (isNull(value)) return null;
const validOptions = validateJsonParsingOptions(options);
if (validOptions.convertNaN && (isNaN$1(value) || isNonEmptyString(value) && value === "NaN")) {
return NaN;
}
if (validOptions.convertNumbers && !isNaN$1(Number(value)) && isNumber(extractDigits(value))) {
return Number(value);
}
if (!isString(value)) return void 0;
try {
let normalized = fixSingleQuotesEscapeBackslash(value);
if (validOptions.removeUndefined) {
normalized = normalized.replace(/,\s*"[^"]*"\s*:\s*undefined(?=\s*[},])/g, "").replace(/"[^"]*"\s*:\s*undefined\s*(,)?/g, "");
} else {
normalized = normalized.replace(/:\s*undefined(?=\s*[,}])/g, ":null");
}
if (validOptions.convertNaN) {
normalized = normalized.replace(/:\s*NaN(?=\s*[,}])/g, ':"NaN"');
} else {
normalized = normalized.replace(/:\s*NaN(?=\s*[,}])/g, ':"NaN"').replace(/,\s*"[^"]*"\s*:\s*NaN(?=\s*[},])/g, "").replace(/"[^"]*"\s*:\s*NaN\s*(,)?/g, "");
}
normalized = normalized.replace(/,(\s*[}\]])/g, "$1");
const parsed = JSON.parse(normalized);
return cleanParsedData(parsed, validOptions);
} catch (error) {
if (validOptions.loggingOnFail) {
console.error("Failed to parsing at `safeJsonParse`:", error);
}
validOptions.onError(
isError(error) ? new Error(error.message.replace(/^JSON\.parse:/, "Failed to parsing")) : new Error(String(error))
);
return void 0;
}
}
export { cleanParsedData, extractDigits, parseCustomDate, safeJsonParse };