ngs-json-utils
Version:
A set of Angular utilities for deep cloning, serialization, and JSON manipulation.
424 lines (419 loc) • 16.7 kB
JavaScript
import * as i0 from '@angular/core';
import { Injectable } from '@angular/core';
/**
* A utility service for performing common JSON-related operations safely and effectively.
*/
class NgsJsonUtilsService {
/**
* Safely converts a JavaScript value to a JSON string.
* If an error occurs during serialization, returns `undefined` instead of throwing an error.
*
* @param data - The value to serialize into a JSON string.
* @param replacer - Optional function to filter or transform properties.
* @param space - Optional number of spaces for formatting the output JSON.
* @returns A JSON string representation of the data, or `undefined` if serialization fails.
*/
stringify(data, replacer, space) {
try {
return JSON.stringify(data, replacer, space);
}
catch (error) {
console.error('Error during JSON.stringify:', error);
return undefined;
}
}
/**
* Safely parses a JSON string into a JavaScript object or value.
* If parsing fails, returns the provided default value (or `null` if not provided).
*
* @param json - The JSON string to parse.
* @param defaultValue - A fallback value to return if parsing fails (defaults to `null`).
* @returns The parsed object/value, or the default value if parsing fails.
*/
parse(json, defaultValue = null) {
try {
return JSON.parse(json);
}
catch (error) {
console.error('Error during JSON.parse:', error);
return defaultValue;
}
}
/**
* Creates a deep copy of a given value using JSON serialization.
* It relies on the JSON.stringify and JSON.parse methods to safely clone an object.
* This approach doesn't handle cyclic references and non-serializable properties (like functions).
*
* @param data - The value to create a deep copy of.
* @returns A deep copy of the input value, or undefined if cloning fails.
*/
deepCopy(data) {
try {
// Serializing and immediately deserializing the object to create a deep copy
const jsonString = JSON.stringify(data);
return JSON.parse(jsonString);
}
catch (error) {
console.error('Error during deep copy (JSON serialization)', error);
return undefined;
}
}
/**
* Safely validates if a given string is in valid JSON format.
* If the string is invalid, it logs the error and returns `false`.
*
* @param jsonString - The string to validate.
* @returns `true` if the string is valid JSON; otherwise, `false`.
*/
isValidJSON(jsonString) {
if (!jsonString) {
console.error('Invalid JSON string: empty or undefined input');
return false;
}
try {
JSON.parse(jsonString);
return true;
}
catch (error) {
console.error('Invalid JSON string:', error);
return false;
}
}
/**
* Compares two JSON-compatible values for equality using serialization.
* Logs an error to the console if serialization fails.
*
* @param obj1 - The first JSON-compatible value to compare.
* @param obj2 - The second JSON-compatible value to compare.
* @returns `true` if the two values are equal, `false` otherwise.
*/
equalJSON(obj1, obj2) {
try {
return JSON.stringify(obj1) === JSON.stringify(obj2);
}
catch (error) {
console.error('Failed to serialize objects for comparison', {
obj1,
obj2,
error,
});
return false;
}
}
/**
* Safely compares two values for deep equality, adhering strictly to JSON-compatible types.
* It does not handle cyclic references or non-JSON-serializable objects.
*
* @param obj1 - The first value to compare.
* @param obj2 - The second value to compare.
* @returns `true` if the two values are deeply equal, `false` otherwise.
*/
deepEqualJSON(obj1, obj2) {
const isObject = (val) => typeof val === 'object' && val !== null;
const deepEqualHelper = (a, b) => {
if (a === b)
return true;
if (Array.isArray(a) && Array.isArray(b)) {
if (a.length !== b.length)
return false;
return a.every((item, index) => deepEqualHelper(item, b[index]));
}
if (isObject(a) && isObject(b)) {
const keysA = Object.keys(a);
const keysB = Object.keys(b);
if (keysA.length !== keysB.length)
return false;
return keysA.every((key) => keysB.includes(key) && deepEqualHelper(a[key], b[key]));
}
return false;
};
return deepEqualHelper(obj1, obj2);
}
/**
* Safely performs a deep merge of two objects using JSON serialization.
* Handles cyclic references by catching errors gracefully.
* Source properties will overwrite corresponding target properties.
*
* @param target - The target object to be updated.
* @param source - The source object providing updates.
* @returns A new object with merged properties, or the original target if an error occurs.
*/
deepMerge(target, source) {
try {
const targetClone = JSON.stringify(target);
const sourceClone = JSON.stringify(source);
const merged = JSON.parse(`{${targetClone.slice(1, -1)},${sourceClone.slice(1, -1)}}`);
return merged;
}
catch (error) {
console.error('Error during safe deep merge:', error);
return target;
}
}
/**
* Safely filters an object's properties, keeping only those specified in the allowed keys.
* Handles cyclic references and non-serializable objects gracefully.
* Logs errors if any occur.
*
* @param obj - The object to filter.
* @param allowedKeys - An array of keys to retain in the filtered object.
* @returns A new object containing only the allowed keys, or an empty object if an error occurs.
*/
filterKeys(obj, allowedKeys) {
try {
const serializedObj = JSON.stringify(obj, (key, value) => {
if (typeof value === 'function') {
return undefined;
}
return value;
});
const parsedObj = JSON.parse(serializedObj);
return allowedKeys.reduce((acc, key) => {
if (key in parsedObj) {
acc[key] = parsedObj[key];
}
return acc;
}, {});
}
catch (error) {
console.error('Error during filtering object:', error);
return {};
}
}
/**
* Safely recursively updates the target object with values from the updates object.
* Handles serialization of objects and logs errors if any occur (e.g., with non-serializable data).
* If an error occurs during the update, the target remains unchanged.
*
* @param target - The object to be updated.
* @param updates - The object containing updates to apply.
* @returns A new object with updated values, or the original target if an error occurs.
*/
deepUpdate(target, updates) {
try {
const serializedUpdates = JSON.stringify(updates, (key, value) => {
if (typeof value === 'function' || value === undefined) {
return undefined;
}
return value;
});
const parsedUpdates = JSON.parse(serializedUpdates);
return Object.keys(parsedUpdates).reduce((acc, key) => {
const value = parsedUpdates[key];
if (value && typeof value === 'object' && !Array.isArray(value)) {
acc[key] = this.deepUpdate(acc[key] || {}, value);
}
else {
acc[key] = value;
}
return acc;
}, { ...target });
}
catch (error) {
console.error('Error during deep update:', error);
return target;
}
}
/**
* Searches for a value in a nested object by its key.
*
* @param obj - The object to search within.
* @param key - The key to look for.
* @returns The value associated with the key, or `undefined` if not found.
*/
findValueByKey(obj, key) {
if (key in obj) {
return obj[key];
}
for (const k in obj) {
if (typeof obj[k] === 'object' && obj[k] !== null) {
const result = this.findValueByKey(obj[k], key);
if (result !== undefined) {
return result;
}
}
}
return undefined;
}
/**
* Safely searches for a value in a nested object by its key.
* If an error occurs during the search, returns `undefined` instead of throwing.
*
* @param obj - The object to search within.
* @param key - The key to look for.
* @returns The value associated with the key, or `undefined` if not found or an error occurs.
*/
safeFindValueByKey(obj, key) {
try {
if (key in obj) {
return obj[key];
}
for (const k in obj) {
if (typeof obj[k] === 'object' && obj[k] !== null) {
const result = this.safeFindValueByKey(obj[k], key);
if (result !== undefined) {
return result;
}
}
}
return undefined;
}
catch (error) {
console.error('Error during object search:', error);
return undefined;
}
}
/**
* Safely removes all `undefined` values from an object.
* If an error occurs during the cleaning process, returns the original object.
*
* @param obj - The object to clean.
* @returns A new object without `undefined` values, or the original object if an error occurs.
*/
removeUndefined(obj) {
try {
return JSON.parse(JSON.stringify(obj, (_, value) => value === undefined ? undefined : value));
}
catch (error) {
console.error('Error during object cleaning:', error);
return obj;
}
}
/**
* Safely converts a Map object to a plain JSON object.
* If an error occurs during the conversion, returns an empty object.
*
* @param map - The Map to convert.
* @returns A JSON object with the same key-value pairs as the Map, or an empty object if an error occurs.
*/
mapToJSON(map) {
try {
const obj = {};
map.forEach((value, key) => {
obj[String(key)] = value;
});
return obj;
}
catch (error) {
console.error('Error during Map to JSON conversion:', error);
return {};
}
}
/**
* Safely converts a plain JSON object to a Map object.
* If an error occurs during the conversion, returns an empty Map.
*
* @param json - The JSON object to convert.
* @returns A Map containing the key-value pairs from the JSON object, or an empty Map if an error occurs.
*/
jsonToMap(json) {
try {
if (json && typeof json === 'object' && !Array.isArray(json)) {
return new Map(Object.entries(json));
}
return new Map();
}
catch (error) {
console.error('Error during JSON to Map conversion:', error);
return new Map();
}
}
/**
* Safely merges multiple arrays of objects, keeping only unique objects based on a specified key.
* If invalid input is provided (non-array or non-object values), returns an empty array.
*
* @param arrays - An array of arrays of objects to merge.
* @param key - The key used to determine uniqueness (objects with the same key value are considered duplicates).
* @returns An array of unique objects based on the specified key, or an empty array if the input is invalid.
*/
mergeUniqueByKey(arrays, key) {
// Проверяем, что входные данные являются массивом и ключ - строкой
if (!Array.isArray(arrays) || typeof key !== 'string')
return [];
return arrays.flat().reduce((acc, obj) => {
// Проверяем, что каждый объект содержит заданный ключ
if (obj &&
obj.hasOwnProperty(key) &&
!acc.some((item) => item[key] === obj[key])) {
acc.push(obj);
}
return acc;
}, []);
}
/**
* Safely removes all keys from an object that have null or empty values.
* If the input is not an object, returns an empty object.
*
* @param obj - The object from which to remove empty values.
* @returns A new object with only non-empty values, or an empty object if the input is invalid.
*/
removeEmptyValues(obj) {
if (typeof obj !== 'object' || obj === null)
return {};
return Object.fromEntries(Object.entries(obj).filter(([_, v]) => v != null && v !== ''));
}
/**
* Safely retrieves the value from an object based on a specified path (array of keys).
* Returns undefined if the object or the path is invalid.
*
* @param obj - The object to retrieve the value from.
* @param path - An array of keys representing the path to the value.
* @returns The value at the specified path, or undefined if the object or path is invalid.
*/
getByPath(obj, path) {
if (typeof obj !== 'object' || obj === null || !Array.isArray(path))
return undefined;
return path.reduce((acc, key) => (acc && acc[key] !== undefined ? acc[key] : undefined), obj);
}
/**
* Safely extracts unique values from an array of objects based on a specific key.
* If the input is invalid (non-array or non-object values), returns an empty array.
*
* @param arr - An array of objects from which to extract unique values.
* @param key - The key used to extract unique values.
* @returns An array of unique values based on the specified key, or an empty array if the input is invalid.
*/
uniqueValuesByKey(arr, key) {
if (!Array.isArray(arr) || typeof key !== 'string')
return [];
return Array.from(new Set(arr.map((item) => (item && key in item ? item[key] : undefined))));
}
/**
* Safely flattens a nested object into a single-level object, using dot notation for nested keys.
* If the input is invalid (non-object or null), returns an empty object.
*
* @param obj - The object to flatten.
* @param prefix - The prefix to prepend to each key (used for nested objects).
* @returns A flattened object with dot notation keys, or an empty object if the input is invalid.
*/
flattenObject(obj, prefix = '') {
if (typeof obj !== 'object' || obj === null)
return {};
return Object.entries(obj).reduce((acc, [key, value]) => {
const newKey = prefix ? `${prefix}.${key}` : key;
if (typeof value === 'object' && value !== null) {
Object.assign(acc, this.flattenObject(value, newKey));
}
else {
acc[newKey] = value;
}
return acc;
}, {});
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.12", ngImport: i0, type: NgsJsonUtilsService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.12", ngImport: i0, type: NgsJsonUtilsService, providedIn: 'root' });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.12", ngImport: i0, type: NgsJsonUtilsService, decorators: [{
type: Injectable,
args: [{
providedIn: 'root',
}]
}] });
/*
* Public API Surface of ngs-json-utils
*/
/**
* Generated bundle index. Do not edit.
*/
export { NgsJsonUtilsService };
//# sourceMappingURL=ngs-json-utils.mjs.map