Implementation:Apache Druid Object Change
| Knowledge Sources | |
|---|---|
| Domains | Web_Console, Immutable_Data_Structures |
| Last Updated | 2026-02-10 10:00 GMT |
Overview
Provides immutable deep object manipulation utilities including get, set, delete, move, and extend operations using dot-notation paths.
Description
The Object Change module implements a suite of pure functions that perform deep operations on nested objects and arrays without mutating the originals. It supports a dot-notation path syntax (e.g., `spec.dataSchema.granularitySpec`) with escaped segments using braces for keys containing dots (e.g., `{my.key}`). The `[append]` or `-1` path segment enables appending to arrays. Additional helpers include `deepExtend` for recursive merging, `allowKeys` for key whitelisting, and `deleteKeys` for key removal.
Usage
Used extensively in the web console's ingestion spec editor and form-based configuration UIs to create modified copies of deeply nested configuration objects in an immutable manner.
Code Reference
Source Location
- Repository: Apache Druid
- File: web-console/src/utils/object-change.ts
- Lines: 1-179
Signature
export function shallowCopy(v: any): any;
export function isEmpty(v: any): boolean;
export function parsePath(path: string): string[];
export function makePath(parts: string[]): string;
export function deepGet<T extends Record<string, any>>(value: T, path: string): any;
export function deepSet<T extends Record<string, any>>(value: T, path: string, x: any): T;
export function deepSetIfUnset<T extends Record<string, any>>(value: T, path: string, x: any): T;
export function deepSetMulti<T extends Record<string, any>>(value: T, changes: Record<string, any>): T;
export function deepDelete<T extends Record<string, any>>(value: T, path: string): T;
export function deepMove<T extends Record<string, any>>(value: T, fromPath: string, toPath: string): T;
export function deepExtend<T extends Record<string, any>>(target: T, diff: Record<string, any>): T;
export function allowKeys<T>(obj: T, keys: (keyof T)[]): T;
export function deleteKeys<T>(obj: T, keys: (keyof T)[]): T;
Import
import { deepGet, deepSet, deepDelete, deepExtend } from './utils/object-change';
I/O Contract
Inputs
| Name | Type | Required | Description |
|---|---|---|---|
| value / target | T extends Record<string, any> |
Yes | The source object or array to operate on (not mutated) |
| path | string |
Yes | A dot-notation path string, optionally with brace-escaped segments |
| x | any |
Yes | The value to set at the specified path |
| changes | Record<string, any> |
Yes | A map of path-to-value pairs for batch setting |
| diff | Record<string, any> |
Yes | A diff object for recursive merge; undefined values trigger deletion |
| keys | (keyof T)[] |
Yes | An array of keys to allow or delete |
Outputs
| Name | Type | Description |
|---|---|---|
| (return) | T |
A new object with the requested modification applied (original unchanged) |
| (return) | any |
The value found at the specified path (deepGet) |
| (return) | string[] |
Parsed path parts (parsePath) |
Usage Examples
Deep get and set
import { deepGet, deepSet } from './utils/object-change';
const spec = { dataSchema: { granularitySpec: { type: 'uniform' } } };
const type = deepGet(spec, 'dataSchema.granularitySpec.type'); // 'uniform'
const updated = deepSet(spec, 'dataSchema.granularitySpec.type', 'arbitrary');
// updated.dataSchema.granularitySpec.type === 'arbitrary'
// spec is unchanged
Append to an array
import { deepSet } from './utils/object-change';
const obj = { items: ['a', 'b'] };
const result = deepSet(obj, 'items.[append]', 'c');
// result.items === ['a', 'b', 'c']
Deep extend with deletion
import { deepExtend } from './utils/object-change';
const base = { a: 1, b: { c: 2, d: 3 } };
const result = deepExtend(base, { b: { d: undefined, e: 4 } });
// result === { a: 1, b: { c: 2, e: 4 } }