Website Structure

This commit is contained in:
supalerk-ar66 2026-01-13 10:46:40 +07:00
parent 62812f2090
commit 71f0676a62
22365 changed files with 4265753 additions and 791 deletions

260
Frontend-Learner/node_modules/dot-prop/index.d.ts generated vendored Normal file
View file

@ -0,0 +1,260 @@
import {type Get} from 'type-fest';
/**
Get the value of the property at the given path.
@param object - Object or array to get the `path` value.
@param path - Path of the property in the object, using `.` to separate each nested key. Use `\\.` if you have a `.` in the key. Array indices can be accessed using bracket notation (`'users[0].name'`) or dot notation (`'users.0.name'`). Both syntaxes are equivalent and will create arrays when setting values. Numeric strings in dot notation (like `'users.0.name'`) are automatically coerced to numbers. When using array paths, numeric strings are normalized to numbers for simplicity (treating `['users', '0']` the same as `['users', 0]`).
@param defaultValue - Default value.
@example
```
import {getProperty} from 'dot-prop';
getProperty({foo: {bar: 'unicorn'}}, 'foo.bar');
//=> 'unicorn'
getProperty({foo: {bar: 'a'}}, 'foo.notDefined.deep');
//=> undefined
getProperty({foo: {bar: 'a'}}, 'foo.notDefined.deep', 'default value');
//=> 'default value'
getProperty({foo: {'dot.dot': 'unicorn'}}, 'foo.dot\\.dot');
//=> 'unicorn'
getProperty({foo: [{bar: 'unicorn'}]}, 'foo[0].bar');
//=> 'unicorn'
getProperty({foo: [{bar: 'unicorn'}]}, 'foo.0.bar');
//=> 'unicorn'
```
*/
export function getProperty<ObjectType, PathType extends string, DefaultValue = undefined>(
object: ObjectType,
path: PathType | ReadonlyArray<string | number>,
defaultValue?: DefaultValue
): ObjectType extends Record<string, unknown> | unknown[]
? (unknown extends Get<ObjectType, PathType> ? DefaultValue : Get<ObjectType, PathType>)
: DefaultValue extends undefined ? unknown : DefaultValue;
/**
Set the property at the given path to the given value.
@param object - Object or array to set the `path` value.
@param path - Path of the property in the object, using `.` to separate each nested key. Use `\\.` if you have a `.` in the key. Array indices can be accessed using bracket notation (`'users[0].name'`) or dot notation (`'users.0.name'`). Both syntaxes are equivalent and will create arrays when setting values. Numeric strings in dot notation (like `'users.0.name'`) are automatically coerced to numbers. When using array paths, numeric strings are normalized to numbers for simplicity (treating `['users', '0']` the same as `['users', 0]`).
@param value - Value to set at `path`.
@returns The object.
@example
```
import {setProperty} from 'dot-prop';
const object = {foo: {bar: 'a'}};
setProperty(object, 'foo.bar', 'b');
console.log(object);
//=> {foo: {bar: 'b'}}
const foo = setProperty({}, 'foo.bar', 'c');
console.log(foo);
//=> {foo: {bar: 'c'}}
setProperty(object, 'foo.baz', 'x');
console.log(object);
//=> {foo: {bar: 'b', baz: 'x'}}
setProperty(object, 'foo.biz[0]', 'a');
console.log(object);
//=> {foo: {bar: 'b', baz: 'x', biz: ['a']}}
setProperty(object, 'foo.items.0', 'first');
console.log(object);
//=> {foo: {bar: 'b', baz: 'x', biz: ['a'], items: ['first']}}
```
*/
export function setProperty<ObjectType extends (Record<string, any> | unknown[])>(
object: ObjectType,
path: string | ReadonlyArray<string | number>,
value: unknown
): ObjectType;
/**
Check whether the property at the given path exists.
@param object - Object or array to test the `path` value.
@param path - Path of the property in the object, using `.` to separate each nested key. Use `\\.` if you have a `.` in the key. Array indices can be accessed using bracket notation (`'users[0].name'`) or dot notation (`'users.0.name'`). Both syntaxes are equivalent and will create arrays when setting values. Numeric strings in dot notation (like `'users.0.name'`) are automatically coerced to numbers. When using array paths, numeric strings are normalized to numbers for simplicity (treating `['users', '0']` the same as `['users', 0]`).
@example
```
import {hasProperty} from 'dot-prop';
hasProperty({foo: {bar: 'unicorn'}}, 'foo.bar');
//=> true
```
*/
export function hasProperty(object: Record<string, any> | unknown[] | undefined, path: string | ReadonlyArray<string | number>): boolean;
/**
Delete the property at the given path.
@param object - Object or array to delete the `path` value.
@param path - Path of the property in the object, using `.` to separate each nested key. Use `\\.` if you have a `.` in the key. Array indices can be accessed using bracket notation (`'users[0].name'`) or dot notation (`'users.0.name'`). Both syntaxes are equivalent and will create arrays when setting values. Numeric strings in dot notation (like `'users.0.name'`) are automatically coerced to numbers. When using array paths, numeric strings are normalized to numbers for simplicity (treating `['users', '0']` the same as `['users', 0]`).
@returns A boolean of whether the property existed before being deleted.
@example
```
import {deleteProperty} from 'dot-prop';
const object = {foo: {bar: 'a'}};
deleteProperty(object, 'foo.bar');
console.log(object);
//=> {foo: {}}
object.foo.bar = {x: 'y', y: 'x'};
deleteProperty(object, 'foo.bar.x');
console.log(object);
//=> {foo: {bar: {y: 'x'}}}
```
*/
export function deleteProperty(object: Record<string, any> | unknown[], path: string | ReadonlyArray<string | number>): boolean;
/**
Escape special characters in a path. Useful for sanitizing user input.
@param path - The dot path to sanitize.
@example
```
import {getProperty, escapePath} from 'dot-prop';
const object = {
foo: {
bar: '👸🏻 You found me Mario!',
},
'foo.bar' : '🍄 The princess is in another castle!',
};
const escapedPath = escapePath('foo.bar');
console.log(getProperty(object, escapedPath));
//=> '🍄 The princess is in another castle!'
```
*/
export function escapePath(path: string): string;
/**
Parse a dot path into an array of path segments.
@param path - Path to parse. Use `\\.` if you have a `.` in the key. Array indices can be accessed using bracket notation (`'users[0].name'`) or dot notation (`'users.0.name'`). Both syntaxes are equivalent. Numeric strings in dot notation (like `'users.0.name'`) are automatically coerced to numbers.
@returns An array of path segments where numbers represent array indices and strings represent object keys.
@example
```
import {parsePath} from 'dot-prop';
parsePath('foo.bar');
//=> ['foo', 'bar']
parsePath('foo[0].bar');
//=> ['foo', 0, 'bar']
parsePath('foo.0.bar');
//=> ['foo', 0, 'bar']
parsePath('foo\\.bar');
//=> ['foo.bar']
// Use case: Iterate over path segments to build up a nested object
const path = 'users[0].profile.settings.theme';
const segments = parsePath(path);
//=> ['users', 0, 'profile', 'settings', 'theme']
```
*/
export function parsePath(path: string): Array<string | number>;
/**
Convert an array of path segments back into a path string.
@param pathSegments - Array of path segments where numbers represent array indices and strings represent object keys.
@param options - Options for stringifying the path.
@example
```
import {stringifyPath} from 'dot-prop';
stringifyPath(['foo', 'bar']);
//=> 'foo.bar'
stringifyPath(['foo', 0, 'bar']);
//=> 'foo[0].bar'
stringifyPath(['foo', '0', 'bar']);
//=> 'foo[0].bar'
// With preferDotForIndices option
stringifyPath(['foo', 0, 'bar'], {preferDotForIndices: true});
//=> 'foo.0.bar'
```
*/
export function stringifyPath(pathSegments: ReadonlyArray<string | number>, options?: {preferDotForIndices?: boolean}): string;
/**
Returns an array of every path. Non-empty plain objects and arrays are deeply recursed and are not themselves included.
This can be useful to help flatten an object for an API that only accepts key-value pairs or for a tagged template literal.
@param object - The object to iterate through.
@example
```
import {getProperty, deepKeys} from 'dot-prop';
const user = {
name: {
first: 'Richie',
last: 'Bendall',
},
activeTasks: [],
currentProject: null
};
for (const property of deepKeys(user)) {
console.log(`${property}: ${getProperty(user, property)}`);
//=> name.first: Richie
//=> name.last: Bendall
//=> activeTasks: []
//=> currentProject: null
}
```
*/
export function deepKeys(object: unknown): string[];
/**
Convert an object with dot paths into a nested object.
Uses the same path rules and escaping as the rest of the API.
@param object - A plain object mapping paths to values.
@returns A new nested object.
@example
```
import {unflatten} from 'dot-prop';
const flat = {
'unicorn.name': 'Rainbow Dash',
'unicorn.color': '🦄',
'unicorn.treasures[0]': 'sparkles',
'unicorn.treasures[1]': 'glitter',
};
unflatten(flat);
//=> {
//=> unicorn: {
//=> name: 'Rainbow Dash',
//=> color: '🦄',
//=> treasures: ['sparkles', 'glitter']
//=> }
//=> }
```
*/
export function unflatten(object: Record<string, unknown>): Record<string, unknown>;

498
Frontend-Learner/node_modules/dot-prop/index.js generated vendored Normal file
View file

@ -0,0 +1,498 @@
const isObject = value => {
const type = typeof value;
return value !== null && (type === 'object' || type === 'function');
};
// Optimized empty check without creating an array.
const isEmptyObject = value => {
if (!isObject(value)) {
return false;
}
for (const key in value) {
if (Object.hasOwn(value, key)) {
return false;
}
}
return true;
};
const disallowedKeys = new Set([
'__proto__',
'prototype',
'constructor',
]);
// Maximum allowed array index to prevent DoS via memory exhaustion.
const MAX_ARRAY_INDEX = 1_000_000;
// Optimized digit check without Set overhead.
const isDigit = character => character >= '0' && character <= '9';
// Check if a segment should be coerced to a number.
function shouldCoerceToNumber(segment) {
// Only coerce valid non-negative integers without leading zeros.
if (segment === '0') {
return true;
}
if (/^[1-9]\d*$/.test(segment)) {
const parsedNumber = Number.parseInt(segment, 10);
// Check within safe integer range and under MAX_ARRAY_INDEX to prevent DoS.
return parsedNumber <= Number.MAX_SAFE_INTEGER && parsedNumber <= MAX_ARRAY_INDEX;
}
return false;
}
// Helper to process a path segment (eliminates duplication).
function processSegment(segment, parts) {
if (disallowedKeys.has(segment)) {
return false; // Signal to return empty array.
}
if (segment && shouldCoerceToNumber(segment)) {
parts.push(Number.parseInt(segment, 10));
} else {
parts.push(segment);
}
return true;
}
export function parsePath(path) { // eslint-disable-line complexity
if (typeof path !== 'string') {
throw new TypeError(`Expected a string, got ${typeof path}`);
}
const parts = [];
let currentSegment = '';
let currentPart = 'start';
let isEscaping = false;
let position = 0;
for (const character of path) {
position++;
// Handle escaping.
if (isEscaping) {
currentSegment += character;
isEscaping = false;
continue;
}
// Handle escape character.
if (character === '\\') {
if (currentPart === 'index') {
throw new Error(`Invalid character '${character}' in an index at position ${position}`);
}
if (currentPart === 'indexEnd') {
throw new Error(`Invalid character '${character}' after an index at position ${position}`);
}
isEscaping = true;
currentPart = currentPart === 'start' ? 'property' : currentPart;
continue;
}
switch (character) {
case '.': {
if (currentPart === 'index') {
throw new Error(`Invalid character '${character}' in an index at position ${position}`);
}
if (currentPart === 'indexEnd') {
currentPart = 'property';
break;
}
if (!processSegment(currentSegment, parts)) {
return [];
}
currentSegment = '';
currentPart = 'property';
break;
}
case '[': {
if (currentPart === 'index') {
throw new Error(`Invalid character '${character}' in an index at position ${position}`);
}
if (currentPart === 'indexEnd') {
currentPart = 'index';
break;
}
if (currentPart === 'property' || currentPart === 'start') {
// Only push if we have content OR if we're in 'property' mode (not 'start')
if ((currentSegment || currentPart === 'property') && !processSegment(currentSegment, parts)) {
return [];
}
currentSegment = '';
}
currentPart = 'index';
break;
}
case ']': {
if (currentPart === 'index') {
if (currentSegment === '') {
// Empty brackets - backtrack and treat as literal
const lastSegment = parts.pop() || '';
currentSegment = lastSegment + '[]';
currentPart = 'property';
} else {
// Index must be digits only (enforced by default case)
const parsedNumber = Number.parseInt(currentSegment, 10);
const isValidInteger = !Number.isNaN(parsedNumber)
&& Number.isFinite(parsedNumber)
&& parsedNumber >= 0
&& parsedNumber <= Number.MAX_SAFE_INTEGER
&& parsedNumber <= MAX_ARRAY_INDEX
&& currentSegment === String(parsedNumber);
if (isValidInteger) {
parts.push(parsedNumber);
} else {
// Keep as string if not a valid integer representation or exceeds MAX_ARRAY_INDEX
parts.push(currentSegment);
}
currentSegment = '';
currentPart = 'indexEnd';
}
break;
}
if (currentPart === 'indexEnd') {
throw new Error(`Invalid character '${character}' after an index at position ${position}`);
}
// In property context, treat ] as literal character
currentSegment += character;
break;
}
default: {
if (currentPart === 'index' && !isDigit(character)) {
throw new Error(`Invalid character '${character}' in an index at position ${position}`);
}
if (currentPart === 'indexEnd') {
throw new Error(`Invalid character '${character}' after an index at position ${position}`);
}
if (currentPart === 'start') {
currentPart = 'property';
}
currentSegment += character;
}
}
}
// Handle unfinished escaping (trailing backslash)
if (isEscaping) {
currentSegment += '\\';
}
// Handle end of path
switch (currentPart) {
case 'property': {
if (!processSegment(currentSegment, parts)) {
return [];
}
break;
}
case 'index': {
throw new Error('Index was not closed');
}
case 'start': {
parts.push('');
break;
}
// No default
}
return parts;
}
function normalizePath(path) {
if (typeof path === 'string') {
return parsePath(path);
}
if (Array.isArray(path)) {
const normalized = [];
for (const [index, segment] of path.entries()) {
// Type validation.
if (typeof segment !== 'string' && typeof segment !== 'number') {
throw new TypeError(`Expected a string or number for path segment at index ${index}, got ${typeof segment}`);
}
// Validate numbers are finite (reject NaN, Infinity, -Infinity).
if (typeof segment === 'number' && !Number.isFinite(segment)) {
throw new TypeError(`Path segment at index ${index} must be a finite number, got ${segment}`);
}
// Check for disallowed keys.
if (disallowedKeys.has(segment)) {
return [];
}
// Normalize numeric strings to numbers for simplicity.
// This treats ['items', '0'] the same as ['items', 0].
if (typeof segment === 'string' && shouldCoerceToNumber(segment)) {
normalized.push(Number.parseInt(segment, 10));
} else {
normalized.push(segment);
}
}
return normalized;
}
return [];
}
export function getProperty(object, path, value) {
if (!isObject(object) || (typeof path !== 'string' && !Array.isArray(path))) {
return value === undefined ? object : value;
}
const pathArray = normalizePath(path);
if (pathArray.length === 0) {
return value;
}
for (let index = 0; index < pathArray.length; index++) {
const key = pathArray[index];
object = object[key];
if (object === undefined || object === null) {
// Return default value if we hit undefined/null before the end of the path.
// This ensures get({foo: null}, 'foo.bar') returns the default value, not null.
if (index !== pathArray.length - 1) {
return value;
}
break;
}
}
return object === undefined ? value : object;
}
export function setProperty(object, path, value) {
if (!isObject(object) || (typeof path !== 'string' && !Array.isArray(path))) {
return object;
}
const root = object;
const pathArray = normalizePath(path);
if (pathArray.length === 0) {
return object;
}
for (let index = 0; index < pathArray.length; index++) {
const key = pathArray[index];
if (index === pathArray.length - 1) {
object[key] = value;
} else if (!isObject(object[key])) {
const nextKey = pathArray[index + 1];
// Create arrays for numeric indices, objects for string keys
const shouldCreateArray = typeof nextKey === 'number';
object[key] = shouldCreateArray ? [] : {};
}
object = object[key];
}
return root;
}
export function deleteProperty(object, path) {
if (!isObject(object) || (typeof path !== 'string' && !Array.isArray(path))) {
return false;
}
const pathArray = normalizePath(path);
if (pathArray.length === 0) {
return false;
}
for (let index = 0; index < pathArray.length; index++) {
const key = pathArray[index];
if (index === pathArray.length - 1) {
const existed = Object.hasOwn(object, key);
if (!existed) {
return false;
}
delete object[key];
return true;
}
object = object[key];
if (!isObject(object)) {
return false;
}
}
}
export function hasProperty(object, path) {
if (!isObject(object) || (typeof path !== 'string' && !Array.isArray(path))) {
return false;
}
const pathArray = normalizePath(path);
if (pathArray.length === 0) {
return false;
}
for (const key of pathArray) {
if (!isObject(object) || !(key in object)) {
return false;
}
object = object[key];
}
return true;
}
export function escapePath(path) {
if (typeof path !== 'string') {
throw new TypeError(`Expected a string, got ${typeof path}`);
}
// Escape special characters in one pass
return path.replaceAll(/[\\.[]/g, String.raw`\$&`);
}
function normalizeEntries(value) {
const entries = Object.entries(value);
if (Array.isArray(value)) {
return entries.map(([key, entryValue]) => {
// Use shouldCoerceToNumber for consistency with parsePath
const normalizedKey = shouldCoerceToNumber(key)
? Number.parseInt(key, 10)
: key;
return [normalizedKey, entryValue];
});
}
return entries;
}
export function stringifyPath(pathSegments, options = {}) {
if (!Array.isArray(pathSegments)) {
throw new TypeError(`Expected an array, got ${typeof pathSegments}`);
}
const {preferDotForIndices = false} = options;
const parts = [];
for (const [index, segment] of pathSegments.entries()) {
// Validate segment types at runtime
if (typeof segment !== 'string' && typeof segment !== 'number') {
throw new TypeError(`Expected a string or number for path segment at index ${index}, got ${typeof segment}`);
}
if (typeof segment === 'number') {
// Handle numeric indices
if (!Number.isInteger(segment) || segment < 0) {
// Non-integer or negative numbers are treated as string keys
const escaped = escapePath(String(segment));
parts.push(index === 0 ? escaped : `.${escaped}`);
} else if (preferDotForIndices && index > 0) {
parts.push(`.${segment}`);
} else {
parts.push(`[${segment}]`);
}
} else if (typeof segment === 'string') {
if (segment === '') {
// Empty string handling
if (index === 0) {
// Start with empty string, no prefix needed
} else {
parts.push('.');
}
} else if (shouldCoerceToNumber(segment)) {
// Numeric strings are normalized to numbers
const numericValue = Number.parseInt(segment, 10);
if (preferDotForIndices && index > 0) {
parts.push(`.${numericValue}`);
} else {
parts.push(`[${numericValue}]`);
}
} else {
// Regular strings use dot notation
const escaped = escapePath(segment);
parts.push(index === 0 ? escaped : `.${escaped}`);
}
}
}
return parts.join('');
}
function * deepKeysIterator(object, currentPath = [], ancestors = new Set()) {
if (!isObject(object) || isEmptyObject(object)) {
if (currentPath.length > 0) {
yield stringifyPath(currentPath);
}
return;
}
// Check if this object is already in the current path (circular reference)
if (ancestors.has(object)) {
return;
}
// Add to ancestors, recurse, then remove (backtrack)
ancestors.add(object);
// Reuse currentPath array by push/pop instead of creating new arrays
for (const [key, value] of normalizeEntries(object)) {
currentPath.push(key);
yield * deepKeysIterator(value, currentPath, ancestors);
currentPath.pop();
}
ancestors.delete(object);
}
export function deepKeys(object) {
return [...deepKeysIterator(object)];
}
export function unflatten(object) {
const result = {};
if (!isObject(object)) {
return result;
}
for (const [path, value] of Object.entries(object)) {
setProperty(result, path, value);
}
return result;
}

9
Frontend-Learner/node_modules/dot-prop/license generated vendored Normal file
View file

@ -0,0 +1,9 @@
MIT License
Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (https://sindresorhus.com)
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

58
Frontend-Learner/node_modules/dot-prop/package.json generated vendored Normal file
View file

@ -0,0 +1,58 @@
{
"name": "dot-prop",
"version": "10.1.0",
"description": "Get, set, or delete a property from a nested object using a dot path",
"license": "MIT",
"repository": "sindresorhus/dot-prop",
"funding": "https://github.com/sponsors/sindresorhus",
"author": {
"name": "Sindre Sorhus",
"email": "sindresorhus@gmail.com",
"url": "https://sindresorhus.com"
},
"type": "module",
"exports": {
"types": "./index.d.ts",
"default": "./index.js"
},
"sideEffects": false,
"engines": {
"node": ">=20"
},
"scripts": {
"test": "xo && ava && tsc",
"bench": "node benchmark.js",
"coverage": "c8 ava"
},
"files": [
"index.js",
"index.d.ts"
],
"keywords": [
"object",
"prop",
"property",
"dot",
"path",
"get",
"set",
"delete",
"access",
"notation",
"dotty",
"dottie",
"unflatten",
"expand"
],
"dependencies": {
"type-fest": "^5.0.0"
},
"devDependencies": {
"ava": "^6.4.1",
"benchmark": "^2.1.4",
"c8": "^10.1.3",
"expect-type": "^1.2.2",
"typescript": "^5.9.2",
"xo": "^1.2.2"
}
}

304
Frontend-Learner/node_modules/dot-prop/readme.md generated vendored Normal file
View file

@ -0,0 +1,304 @@
# dot-prop
> Get, set, or delete a property from a nested object using a dot path
## Install
```sh
npm install dot-prop
```
## Usage
```js
import {getProperty, setProperty, hasProperty, deleteProperty} from 'dot-prop';
// Getter
getProperty({foo: {bar: 'unicorn'}}, 'foo.bar');
//=> 'unicorn'
getProperty({foo: {bar: 'a'}}, 'foo.notDefined.deep');
//=> undefined
getProperty({foo: {bar: 'a'}}, 'foo.notDefined.deep', 'default value');
//=> 'default value'
getProperty({foo: {'dot.dot': 'unicorn'}}, 'foo.dot\\.dot');
//=> 'unicorn'
getProperty({foo: [{bar: 'unicorn'}]}, 'foo[0].bar');
//=> 'unicorn'
getProperty({foo: [{bar: 'unicorn'}]}, 'foo.0.bar');
//=> 'unicorn'
// Setter
const object = {foo: {bar: 'a'}};
setProperty(object, 'foo.bar', 'b');
console.log(object);
//=> {foo: {bar: 'b'}}
const foo = setProperty({}, 'foo.bar', 'c');
console.log(foo);
//=> {foo: {bar: 'c'}}
setProperty(object, 'foo.baz', 'x');
console.log(object);
//=> {foo: {bar: 'b', baz: 'x'}}
setProperty(object, 'foo.biz[0]', 'a');
console.log(object);
//=> {foo: {bar: 'b', baz: 'x', biz: ['a']}}
setProperty(object, 'foo.items.0', 'first');
console.log(object);
//=> {foo: {bar: 'b', baz: 'x', biz: ['a'], items: ['first']}}
// Has
hasProperty({foo: {bar: 'unicorn'}}, 'foo.bar');
//=> true
// Deleter
const object = {foo: {bar: 'a'}};
deleteProperty(object, 'foo.bar');
console.log(object);
//=> {foo: {}}
object.foo.bar = {x: 'y', y: 'x'};
deleteProperty(object, 'foo.bar.x');
console.log(object);
//=> {foo: {bar: {y: 'x'}}}
```
### Array paths
For improved performance and interoperability with other libraries, you can also pass paths as arrays instead of strings. This avoids the overhead of parsing string paths.
```js
import {getProperty, setProperty} from 'dot-prop';
const object = {
users: [
{name: 'Alice', role: 'admin'},
{name: 'Bob', role: 'user'}
]
};
// Using array paths - no parsing overhead
getProperty(object, ['users', 0, 'name']);
//=> 'Alice'
setProperty(object, ['users', 1, 'role'], 'moderator');
console.log(object.users[1].role);
//=> 'moderator'
// Useful for interoperability with libraries that return paths as arrays
const pathFromOtherLib = ['users', 0, 'profile', 'settings'];
setProperty(object, pathFromOtherLib, {theme: 'dark'});
```
Array paths:
- Avoid the parse/stringify cycle when you already have path segments
- Work with all functions: `getProperty`, `setProperty`, `hasProperty`, `deleteProperty`
- Numeric strings are automatically normalized to numbers for simplicity
## API
### getProperty(object, path, defaultValue?)
Get the value of the property at the given path.
Returns the value if any.
### setProperty(object, path, value)
Set the property at the given path to the given value.
Returns the object.
### hasProperty(object, path)
Check whether the property at the given path exists.
Returns a boolean.
### deleteProperty(object, path)
Delete the property at the given path.
Returns a boolean of whether the property existed before being deleted.
### escapePath(path)
Escape special characters in a path. Useful for sanitizing user input.
```js
import {getProperty, escapePath} from 'dot-prop';
const object = {
foo: {
bar: '👸🏻 You found me Mario!',
},
'foo.bar' : '🍄 The princess is in another castle!',
};
const escapedPath = escapePath('foo.bar');
console.log(getProperty(object, escapedPath));
//=> '🍄 The princess is in another castle!'
```
### parsePath(path)
Parse a dot path into an array of path segments.
Returns an array of path segments where numbers represent array indices and strings represent object keys.
```js
import {parsePath} from 'dot-prop';
parsePath('foo.bar');
//=> ['foo', 'bar']
parsePath('foo[0].bar');
//=> ['foo', 0, 'bar']
parsePath('foo.0.bar');
//=> ['foo', 0, 'bar']
parsePath('foo\\.bar');
//=> ['foo.bar']
// Use case: Iterate over path segments to build up a nested object
const path = 'users[0].profile.settings.theme';
const segments = parsePath(path);
//=> ['users', 0, 'profile', 'settings', 'theme']
```
### stringifyPath(pathSegments, options?)
Convert an array of path segments back into a path string.
Returns a string path that can be used with other dot-prop functions.
```js
import {stringifyPath} from 'dot-prop';
stringifyPath(['foo', 'bar']);
//=> 'foo.bar'
stringifyPath(['foo', 0, 'bar']);
//=> 'foo[0].bar'
stringifyPath(['foo', '0', 'bar']);
//=> 'foo[0].bar'
// With preferDotForIndices option
stringifyPath(['foo', 0, 'bar'], {preferDotForIndices: true});
//=> 'foo.0.bar'
```
#### pathSegments
Type: `Array<string | number>`
Array of path segments where numbers represent array indices and strings represent object keys.
#### options
Type: `object`
##### preferDotForIndices
Type: `boolean`\
Default: `false`
When `true`, numeric indices will use dot notation instead of bracket notation when not the first segment.
### deepKeys(object)
Returns an array of every path. Non-empty plain objects and arrays are deeply recursed and are not themselves included.
This can be useful to help flatten an object for an API that only accepts key-value pairs or for a tagged template literal.
```js
import {getProperty, deepKeys} from 'dot-prop';
const user = {
name: {
first: 'Richie',
last: 'Bendall',
},
activeTasks: [],
currentProject: null
};
for (const property of deepKeys(user)) {
console.log(`${property}: ${getProperty(user, property)}`);
//=> name.first: Richie
//=> name.last: Bendall
//=> activeTasks: []
//=> currentProject: null
}
```
Sparse arrays are supported. In general, [avoid using sparse arrays](https://github.com/sindresorhus/dot-prop/issues/109#issuecomment-1614819869).
#### object
Type: `object | array`
Object or array to get, set, or delete the `path` value.
You are allowed to pass in `undefined` as the object to the `get` and `has` functions.
#### path
Type: `string | Array<string | number>`
Path of the property in the object.
**String paths**: Use `.` to separate each nested key. Use `\\.` if you have a `.` in the key. Array indices can be accessed using bracket notation (like `'users[0].name'`) or dot notation (like `'users.0.name'`). Both syntaxes are equivalent and will create arrays when setting values. Numeric strings in dot notation (like `'users.0.name'`) are automatically coerced to numbers.
**Array paths**: Pass an array of path segments for better performance and interoperability. Numbers create arrays (like `['users', 0, 'name']`). Numeric strings are normalized to numbers for simplicity. No parsing overhead.
The following path components are invalid and results in `undefined` being returned: `__proto__`, `prototype`, `constructor`.
#### value
Type: `unknown`
Value to set at `path`.
#### defaultValue
Type: `unknown`
Default value.
### unflatten(object)
Convert an object with dot paths into a nested object.
Uses the same path rules and escaping as the rest of the API.
```js
import {unflatten} from 'dot-prop';
const flat = {
'unicorn.name': 'Rainbow Dash',
'unicorn.color': '🦄',
'unicorn.treasures[0]': 'sparkles',
'unicorn.treasures[1]': 'glitter',
};
unflatten(flat);
/*
{
unicorn: {
name: 'Rainbow Dash',
color: '🦄',
treasures: ['sparkles', 'glitter']
}
}
*/
```