Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .changeset/ready-rocks-tap.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
'@layerstack/svelte-actions': patch
'@layerstack/svelte-stores': patch
'@layerstack/svelte-state': patch
'@layerstack/svelte-table': patch
'@layerstack/tailwind': patch
'@layerstack/utils': patch
---

Add more get string syntax
64 changes: 64 additions & 0 deletions packages/utils/src/lib/get.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,4 +79,68 @@ describe('get', () => {
expect(get(obj, 'a.b.c.d.e.f')).toBe('deep');
expect(get(obj, ['a', 'b', 'c', 'd', 'e', 'f'])).toBe('deep');
});

it('returns value at number path', () => {
const arr = ['zero', 'one', 'two'];
expect(get(arr, 0)).toBe('zero');
expect(get(arr, 1)).toBe('one');
expect(get(arr, 2)).toBe('two');
});

it('returns defaultValue when number path does not exist', () => {
const arr = ['zero', 'one'];
expect(get(arr, 5, 'default')).toBe('default');
});

it('handles bracket notation with numeric indices', () => {
const obj = { a: [{ b: 1 }, { b: 2 }] };
expect(get(obj, 'a[0].b')).toBe(1);
expect(get(obj, 'a[1].b')).toBe(2);
});

it('handles bracket notation with double-quoted keys', () => {
const obj = { a: { 'special-key': 'value1', 'another.key': 'value2' } };
expect(get(obj, 'a["special-key"]')).toBe('value1');
expect(get(obj, 'a["another.key"]')).toBe('value2');
});

it('handles bracket notation with single-quoted keys', () => {
const obj = { a: { 'special-key': 'value1', 'another.key': 'value2' } };
expect(get(obj, "a['special-key']")).toBe('value1');
expect(get(obj, "a['another.key']")).toBe('value2');
});

it('handles mixed dot and bracket notation', () => {
const obj = { a: [{ b: { 'c-d': [1, 2, 3] } }] };
expect(get(obj, 'a[0].b["c-d"][2]')).toBe(3);
});

it('handles bracket notation at the start of path', () => {
const obj = { 0: 'zero', 'special-key': 'special' };
expect(get(obj, '[0]')).toBe('zero');
expect(get(obj, '["special-key"]')).toBe('special');
});

it('handles consecutive bracket notations', () => {
const obj = {
a: [
[1, 2],
[3, 4],
],
};
expect(get(obj, 'a[0][1]')).toBe(2);
expect(get(obj, 'a[1][0]')).toBe(3);
});

it('returns defaultValue for invalid bracket notation paths', () => {
const obj = { a: { b: 1 } };
expect(get(obj, 'a[0]', 'default')).toBe('default');
expect(get(obj, 'a["nonexistent"]', 'default')).toBe('default');
});

it('handles keys with special characters via bracket notation', () => {
const obj = { 'key.with.dots': 'dots', 'key[with]brackets': 'brackets' };
expect(get(obj, '["key.with.dots"]')).toBe('dots');
expect(get(obj, '["key[with]brackets"]')).toBe('brackets');
});
});
76 changes: 73 additions & 3 deletions packages/utils/src/lib/get.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,77 @@
/**
* Parse a path string (with optional bracket notation) into an array of path segments.
* Supports both dot notation (a.b.c) and bracket notation (a[0].b, a["key"])
*/
function parsePath(path: string): (string | number)[] {
if (path === '') {
return [''];
}

const segments: (string | number)[] = [];
let current = '';
let i = 0;

while (i < path.length) {
const char = path[i];

if (char === '.') {
if (current) {
segments.push(current);
current = '';
}
i++;
continue;
}

if (char === '[') {
if (current) {
segments.push(current);
current = '';
}
i++;

// Check for quoted key
if (path[i] === '"' || path[i] === "'") {
const quote = path[i];
i++;
let key = '';
while (i < path.length && path[i] !== quote) {
key += path[i];
i++;
}
segments.push(key);
i += 2; // skip closing quote and opening bracket
continue;
}

// Numeric index
let index = '';
while (i < path.length && path[i] !== ']') {
index += path[i];
i++;
}
segments.push(parseInt(index, 10));
i++; // skip closing bracket
continue;
}

current += char;
i++;
}

if (current) {
segments.push(current);
}

return segments;
}

/**
* See: https://github.com/angus-c/just/blob/d8c5dd18941062d8db7e9310ecc8f53fd607df54/packages/object-safe-get/index.mjs#L33C1-L61C2
*/
export function get<T = any>(
obj: any,
propsArg: string | symbol | (string | number | symbol)[],
propsArg: string | number | symbol | (string | number | symbol)[],
defaultValue?: T
): T {
if (!obj) {
Expand All @@ -15,11 +83,13 @@ export function get<T = any>(
if (Array.isArray(propsArg)) {
props = propsArg.slice(0);
} else if (typeof propsArg === 'string') {
props = propsArg.split('.');
props = parsePath(propsArg);
} else if (typeof propsArg === 'symbol') {
props = [propsArg];
} else if (typeof propsArg === 'number') {
props = [propsArg];
} else {
throw new Error('props arg must be an array, a string or a symbol');
throw new Error('props arg must be an array, a string, a number or a symbol');
}

let result: any = obj;
Expand Down
Loading