-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
10 changed files
with
198 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
import { get } from './get'; | ||
|
||
describe('get function', () => { | ||
it('retrieves a simple property', () => { | ||
expect(get({ a: 42 }, 'a')).toBe(42); | ||
}); | ||
|
||
it('retrieves a nested property', () => { | ||
expect(get({ a: { b: { c: 42 } } }, 'a.b.c')).toBe(42); | ||
}); | ||
|
||
it('retrieves a nested property using array syntax', () => { | ||
expect(get({ a: { b: { c: 42 } } }, ['a', 'b', 'c'])).toBe(42); | ||
}); | ||
|
||
it('returns undefined for nonexistent properties', () => { | ||
expect(get({}, 'a')).toBeUndefined(); | ||
}); | ||
|
||
it('retrieves a property that is explicitly set to null', () => { | ||
expect(get({ a: null }, 'a')).toBeNull(); | ||
}); | ||
|
||
it('returns undefined for an incomplete path', () => { | ||
expect(get({ a: 42 }, 'a.b')).toBeUndefined(); | ||
}); | ||
|
||
it('returns undefined for a nonexistent nested property', () => { | ||
expect(get({ a: { b: 42 } }, 'a.b.c')).toBeUndefined(); | ||
}); | ||
|
||
it('returns undefined when object is a primitive', () => { | ||
expect(get(42, 'a')).toBeUndefined(); | ||
}); | ||
|
||
it('returns undefined when object is null', () => { | ||
expect(get(null, 'a')).toBeUndefined(); | ||
}); | ||
|
||
it('returns undefined when object is undefined', () => { | ||
expect(get(undefined, 'a')).toBeUndefined(); | ||
}); | ||
|
||
it('returns the default value if provided and property is not found', () => { | ||
expect(get({}, 'a', 'default')).toBe('default'); | ||
}); | ||
|
||
it('infers the return type from the default value', () => { | ||
const result: number | undefined = get({}, 'a', 42); | ||
expect(result).toBe(42); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
export function get<T>(obj: unknown, path: string | readonly string[]): T | undefined; | ||
export function get<T>(obj: unknown, path: string | readonly string[], defaultValue: T): T; | ||
export function get<T = unknown>( | ||
obj: unknown, | ||
path: string | readonly string[], | ||
defaultValue?: T, | ||
): T | undefined { | ||
if (obj == null) { | ||
return defaultValue; | ||
} | ||
|
||
const pathArray = typeof path === 'string' ? path.split('.') : path; | ||
let it: any = obj; | ||
|
||
for (const key of pathArray) { | ||
if (it[key] === undefined) { | ||
return defaultValue; | ||
} | ||
it = it[key]; | ||
} | ||
|
||
return it; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
import { isPrimitive } from './isPrimitive'; | ||
|
||
describe('isPrimitive function', () => { | ||
it('returns true for numbers', () => { | ||
expect(isPrimitive(42)).toBe(true); | ||
}); | ||
|
||
it('returns true for strings', () => { | ||
expect(isPrimitive('Hello')).toBe(true); | ||
}); | ||
|
||
it('returns true for booleans', () => { | ||
expect(isPrimitive(true)).toBe(true); | ||
expect(isPrimitive(false)).toBe(true); | ||
}); | ||
|
||
it('returns true for null', () => { | ||
expect(isPrimitive(null)).toBe(true); | ||
}); | ||
|
||
it('returns true for undefined', () => { | ||
expect(isPrimitive(void 0)).toBe(true); | ||
}); | ||
|
||
it('returns false for objects', () => { | ||
expect(isPrimitive({})).toBe(false); | ||
expect(isPrimitive({ key: 'value' })).toBe(false); | ||
}); | ||
|
||
it('returns false for arrays', () => { | ||
expect(isPrimitive([])).toBe(false); | ||
expect(isPrimitive([1, 2, 3])).toBe(false); | ||
}); | ||
|
||
it('returns false for functions', () => { | ||
expect(isPrimitive(() => {})).toBe(false); | ||
expect(isPrimitive(function () {})).toBe(false); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
export function isPrimitive(value: unknown): boolean { | ||
return ( | ||
typeof value === 'number' || | ||
typeof value === 'string' || | ||
typeof value === 'boolean' || | ||
value == null | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
import { set } from './set'; | ||
|
||
describe('set function', () => { | ||
it('should set a value for a simple path', () => { | ||
const obj: any = {}; | ||
set(obj, 'a', 42); | ||
expect(obj.a).toBe(42); | ||
}); | ||
|
||
it('should set a value for a nested path', () => { | ||
const obj: any = {}; | ||
set(obj, 'a.b.c', 42); | ||
expect(obj.a.b.c).toBe(42); | ||
}); | ||
|
||
it('should set a value for a nested path with array syntax', () => { | ||
const obj: any = {}; | ||
set(obj, ['a', 'b', 'c'], 42); | ||
expect(obj.a.b.c).toBe(42); | ||
}); | ||
|
||
it('should not set a value for unsafe keys', () => { | ||
const obj: any = {}; | ||
set(obj, '__proto__.unsafe', 42); | ||
expect(obj.unsafe).toBeUndefined(); | ||
}); | ||
|
||
it('should override existing properties', () => { | ||
const obj = { a: 1 }; | ||
set(obj, 'a', 42); | ||
expect(obj.a).toBe(42); | ||
}); | ||
|
||
it.each([[null], [undefined], [42], ['string'], [true]])( | ||
'should handle non-object targets gracefully: %j', | ||
(obj: unknown) => { | ||
expect(() => set(obj, 'a', 42)).not.toThrow(); | ||
}, | ||
); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
import { isPrimitive } from './isPrimitive'; | ||
|
||
export function set(obj: unknown, path: string | readonly string[], value: unknown): void { | ||
if (isPrimitive(obj)) { | ||
return; | ||
} | ||
|
||
const pathArray = typeof path === 'string' ? path.split('.') : path; | ||
let it: any = obj; | ||
|
||
for (let i = 0; i < pathArray.length; i++) { | ||
const key = pathArray[i]; | ||
if (isUnsafeKey(key)) { | ||
return; | ||
} | ||
|
||
if (i === pathArray.length - 1) { | ||
// If it's the last key in the path | ||
it[key] = value; | ||
} else { | ||
if (it[key] === undefined) { | ||
it[key] = {}; | ||
} | ||
it = it[key]; | ||
} | ||
} | ||
} | ||
|
||
function isUnsafeKey(key: string): boolean { | ||
return key === '__proto__' || key === 'constructor' || key === 'prototype'; | ||
} |