Skip to content

Commit 37583c0

Browse files
add pick from object function
1 parent c555d20 commit 37583c0

File tree

4 files changed

+146
-18
lines changed

4 files changed

+146
-18
lines changed

bun.lock

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -7,35 +7,35 @@
77
"@sindresorhus/slugify": "^2.2.1",
88
},
99
"devDependencies": {
10-
"@biomejs/biome": "^1.9.4",
11-
"@types/bun": "^1.2.15",
10+
"@biomejs/biome": "^2.1.1",
11+
"@types/bun": "^1.2.18",
1212
"rimraf": "^6.0.1",
1313
"typescript": "^5.8.3",
14-
"vite": "^6.3.5",
14+
"vite": "^7.0.4",
1515
},
1616
"peerDependencies": {
1717
"@sindresorhus/slugify": "^2.2.1",
1818
},
1919
},
2020
},
2121
"packages": {
22-
"@biomejs/biome": ["@biomejs/biome@1.9.4", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "1.9.4", "@biomejs/cli-darwin-x64": "1.9.4", "@biomejs/cli-linux-arm64": "1.9.4", "@biomejs/cli-linux-arm64-musl": "1.9.4", "@biomejs/cli-linux-x64": "1.9.4", "@biomejs/cli-linux-x64-musl": "1.9.4", "@biomejs/cli-win32-arm64": "1.9.4", "@biomejs/cli-win32-x64": "1.9.4" }, "bin": { "biome": "bin/biome" } }, "sha512-1rkd7G70+o9KkTn5KLmDYXihGoTaIGO9PIIN2ZB7UJxFrWw04CZHPYiMRjYsaDvVV7hP1dYNRLxSANLaBFGpog=="],
22+
"@biomejs/biome": ["@biomejs/biome@2.1.1", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "2.1.1", "@biomejs/cli-darwin-x64": "2.1.1", "@biomejs/cli-linux-arm64": "2.1.1", "@biomejs/cli-linux-arm64-musl": "2.1.1", "@biomejs/cli-linux-x64": "2.1.1", "@biomejs/cli-linux-x64-musl": "2.1.1", "@biomejs/cli-win32-arm64": "2.1.1", "@biomejs/cli-win32-x64": "2.1.1" }, "bin": { "biome": "bin/biome" } }, "sha512-HFGYkxG714KzG+8tvtXCJ1t1qXQMzgWzfvQaUjxN6UeKv+KvMEuliInnbZLJm6DXFXwqVi6446EGI0sGBLIYng=="],
2323

24-
"@biomejs/cli-darwin-arm64": ["@biomejs/cli-darwin-arm64@1.9.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-bFBsPWrNvkdKrNCYeAp+xo2HecOGPAy9WyNyB/jKnnedgzl4W4Hb9ZMzYNbf8dMCGmUdSavlYHiR01QaYR58cw=="],
24+
"@biomejs/cli-darwin-arm64": ["@biomejs/cli-darwin-arm64@2.1.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-2Muinu5ok4tWxq4nu5l19el48cwCY/vzvI7Vjbkf3CYIQkjxZLyj0Ad37Jv2OtlXYaLvv+Sfu1hFeXt/JwRRXQ=="],
2525

26-
"@biomejs/cli-darwin-x64": ["@biomejs/cli-darwin-x64@1.9.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-ngYBh/+bEedqkSevPVhLP4QfVPCpb+4BBe2p7Xs32dBgs7rh9nY2AIYUL6BgLw1JVXV8GlpKmb/hNiuIxfPfZg=="],
26+
"@biomejs/cli-darwin-x64": ["@biomejs/cli-darwin-x64@2.1.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-cC8HM5lrgKQXLAK+6Iz2FrYW5A62pAAX6KAnRlEyLb+Q3+Kr6ur/sSuoIacqlp1yvmjHJqjYfZjPvHWnqxoEIA=="],
2727

28-
"@biomejs/cli-linux-arm64": ["@biomejs/cli-linux-arm64@1.9.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-fJIW0+LYujdjUgJJuwesP4EjIBl/N/TcOX3IvIHJQNsAqvV2CHIogsmA94BPG6jZATS4Hi+xv4SkBBQSt1N4/g=="],
28+
"@biomejs/cli-linux-arm64": ["@biomejs/cli-linux-arm64@2.1.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-tw4BEbhAUkWPe4WBr6IX04DJo+2jz5qpPzpW/SWvqMjb9QuHY8+J0M23V8EPY/zWU4IG8Ui0XESapR1CB49Q7g=="],
2929

30-
"@biomejs/cli-linux-arm64-musl": ["@biomejs/cli-linux-arm64-musl@1.9.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-v665Ct9WCRjGa8+kTr0CzApU0+XXtRgwmzIf1SeKSGAv+2scAlW6JR5PMFo6FzqqZ64Po79cKODKf3/AAmECqA=="],
30+
"@biomejs/cli-linux-arm64-musl": ["@biomejs/cli-linux-arm64-musl@2.1.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-/7FBLnTswu4jgV9ttI3AMIdDGqVEPIZd8I5u2D4tfCoj8rl9dnjrEQbAIDlWhUXdyWlFSz8JypH3swU9h9P+2A=="],
3131

32-
"@biomejs/cli-linux-x64": ["@biomejs/cli-linux-x64@1.9.4", "", { "os": "linux", "cpu": "x64" }, "sha512-lRCJv/Vi3Vlwmbd6K+oQ0KhLHMAysN8lXoCI7XeHlxaajk06u7G+UsFSO01NAs5iYuWKmVZjmiOzJ0OJmGsMwg=="],
32+
"@biomejs/cli-linux-x64": ["@biomejs/cli-linux-x64@2.1.1", "", { "os": "linux", "cpu": "x64" }, "sha512-3WJ1GKjU7NzZb6RTbwLB59v9cTIlzjbiFLDB0z4376TkDqoNYilJaC37IomCr/aXwuU8QKkrYoHrgpSq5ffJ4Q=="],
3333

34-
"@biomejs/cli-linux-x64-musl": ["@biomejs/cli-linux-x64-musl@1.9.4", "", { "os": "linux", "cpu": "x64" }, "sha512-gEhi/jSBhZ2m6wjV530Yy8+fNqG8PAinM3oV7CyO+6c3CEh16Eizm21uHVsyVBEB6RIM8JHIl6AGYCv6Q6Q9Tg=="],
34+
"@biomejs/cli-linux-x64-musl": ["@biomejs/cli-linux-x64-musl@2.1.1", "", { "os": "linux", "cpu": "x64" }, "sha512-kUu+loNI3OCD2c12cUt7M5yaaSjDnGIksZwKnueubX6c/HWUyi/0mPbTBHR49Me3F0KKjWiKM+ZOjsmC+lUt9g=="],
3535

36-
"@biomejs/cli-win32-arm64": ["@biomejs/cli-win32-arm64@1.9.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-tlbhLk+WXZmgwoIKwHIHEBZUwxml7bRJgk0X2sPyNR3S93cdRq6XulAZRQJ17FYGGzWne0fgrXBKpl7l4M87Hg=="],
36+
"@biomejs/cli-win32-arm64": ["@biomejs/cli-win32-arm64@2.1.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-vEHK0v0oW+E6RUWLoxb2isI3rZo57OX9ZNyyGH701fZPj6Il0Rn1f5DMNyCmyflMwTnIQstEbs7n2BxYSqQx4Q=="],
3737

38-
"@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@1.9.4", "", { "os": "win32", "cpu": "x64" }, "sha512-8Y5wMhVIPaWe6jw2H+KlEm4wP/f7EW3810ZLmDlrEEy5KvBsb9ECEfu/kMWD484ijfQ8+nIi0giMgu9g1UAuuA=="],
38+
"@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@2.1.1", "", { "os": "win32", "cpu": "x64" }, "sha512-i2PKdn70kY++KEF/zkQFvQfX1e8SkA8hq4BgC+yE9dZqyLzB/XStY2MvwI3qswlRgnGpgncgqe0QYKVS1blksg=="],
3939

4040
"@esbuild/aix-ppc64": ["@esbuild/[email protected]", "", { "os": "aix", "cpu": "ppc64" }, "sha512-1VCICWypeQKhVbE9oW/sJaAmjLxhVqacdkvPLEjwlttjfwENRSClS8EjBz0KzRyFSCPDIkuXW34Je/vk7zdB7Q=="],
4141

@@ -167,7 +167,7 @@
167167

168168
"escape-string-regexp": ["[email protected]", "", {}, "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="],
169169

170-
"fdir": ["[email protected].4", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg=="],
170+
"fdir": ["[email protected].6", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w=="],
171171

172172
"foreground-child": ["[email protected]", "", { "dependencies": { "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" } }, "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw=="],
173173

@@ -199,7 +199,7 @@
199199

200200
"picomatch": ["[email protected]", "", {}, "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg=="],
201201

202-
"postcss": ["[email protected].3", "", { "dependencies": { "nanoid": "^3.3.8", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A=="],
202+
"postcss": ["[email protected].6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="],
203203

204204
"rimraf": ["[email protected]", "", { "dependencies": { "glob": "^11.0.0", "package-json-from-dist": "^1.0.0" }, "bin": { "rimraf": "dist/esm/bin.mjs" } }, "sha512-9dkvaxAsk/xNXSJzMgFqqMCuFgt2+KsOFek3TMLfo8NCPfWpBmqwyNn5Y+NX56QUYfCtsyhF3ayiboEoUmJk/A=="],
205205

@@ -221,13 +221,13 @@
221221

222222
"strip-ansi-cjs": ["[email protected]", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
223223

224-
"tinyglobby": ["[email protected].13", "", { "dependencies": { "fdir": "^6.4.4", "picomatch": "^4.0.2" } }, "sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw=="],
224+
"tinyglobby": ["[email protected].14", "", { "dependencies": { "fdir": "^6.4.4", "picomatch": "^4.0.2" } }, "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ=="],
225225

226226
"typescript": ["[email protected]", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="],
227227

228228
"undici-types": ["[email protected]", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="],
229229

230-
"vite": ["vite@6.3.5", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", "picomatch": "^4.0.2", "postcss": "^8.5.3", "rollup": "^4.34.9", "tinyglobby": "^0.2.13" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "jiti": ">=1.21.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ=="],
230+
"vite": ["vite@7.0.5", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.6", "picomatch": "^4.0.2", "postcss": "^8.5.6", "rollup": "^4.40.0", "tinyglobby": "^0.2.14" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-1mncVwJxy2C9ThLwz0+2GKZyEXuC3MyWtAAlNftlZZXZDP3AJt5FmwcMit/IGGaNZ8ZOB2BNO/HFUB+CpN0NQw=="],
231231

232232
"which": ["[email protected]", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
233233

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "@primoui/utils",
33
"description": "A lightweight set of utilities",
4-
"version": "1.2.0",
4+
"version": "1.2.1",
55
"license": "MIT",
66
"type": "module",
77
"author": {

src/objects/objects.test.ts

Lines changed: 103 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { describe, expect, it } from "bun:test"
22

3-
import { isEmptyObject, sortObject, sortObjectKeys } from "./objects"
3+
import { isEmptyObject, pickFromObject, sortObject, sortObjectKeys } from "./objects"
44

55
describe("isEmptyObject", () => {
66
it("returns true for an empty object", () => {
@@ -72,3 +72,105 @@ describe("sortObject", () => {
7272
expect(sortedObj).toEqual({ c: 3, b: 2, a: 1 })
7373
})
7474
})
75+
76+
describe("pickFromObject", () => {
77+
it("picks specified properties from an object", () => {
78+
const user = { id: 1, name: "John", email: "[email protected]", password: "secret" }
79+
const result = pickFromObject(user, ["id", "name", "email"])
80+
81+
expect(result).toEqual({ id: 1, name: "John", email: "[email protected]" })
82+
})
83+
84+
it("returns an empty object when given an empty keys array", () => {
85+
const user = { id: 1, name: "John", email: "[email protected]" }
86+
const result = pickFromObject(user, [])
87+
88+
expect(result).toEqual({})
89+
})
90+
91+
it("handles non-existing keys gracefully", () => {
92+
const user = { id: 1, name: "John" }
93+
const result = pickFromObject(user, ["id", "age" as keyof typeof user])
94+
95+
expect(result).toEqual({ id: 1 })
96+
})
97+
98+
it("picks properties with different data types", () => {
99+
const data = {
100+
str: "hello",
101+
num: 42,
102+
bool: true,
103+
arr: [1, 2, 3],
104+
obj: { nested: "value" },
105+
nil: null,
106+
undef: undefined,
107+
}
108+
const result = pickFromObject(data, ["str", "num", "bool", "arr", "obj"])
109+
110+
expect(result).toEqual({
111+
str: "hello",
112+
num: 42,
113+
bool: true,
114+
arr: [1, 2, 3],
115+
obj: { nested: "value" },
116+
})
117+
})
118+
119+
it("handles an empty source object", () => {
120+
const emptyObj = {}
121+
const result = pickFromObject(emptyObj, ["nonExistent" as keyof typeof emptyObj])
122+
123+
expect(result).toEqual({})
124+
})
125+
126+
it("picks a single property", () => {
127+
const user = { id: 1, name: "John", email: "[email protected]" }
128+
const result = pickFromObject(user, ["name"])
129+
130+
expect(result).toEqual({ name: "John" })
131+
})
132+
133+
it("handles objects with symbol keys", () => {
134+
const sym = Symbol("test")
135+
const obj = { [sym]: "symbol value", regular: "regular value" }
136+
const result = pickFromObject(obj, ["regular"])
137+
138+
expect(result).toEqual({ regular: "regular value" })
139+
})
140+
141+
it("preserves property order", () => {
142+
const obj = { c: 3, a: 1, b: 2 }
143+
const result = pickFromObject(obj, ["a", "b", "c"])
144+
145+
// While object property order isn't guaranteed in all cases,
146+
// modern JS engines preserve insertion order for string keys
147+
expect(Object.keys(result)).toEqual(["a", "b", "c"])
148+
expect(result).toEqual({ a: 1, b: 2, c: 3 })
149+
})
150+
151+
it("works with readonly keys array", () => {
152+
const user = { id: 1, name: "John", email: "[email protected]" }
153+
const keys = ["id", "name"] as const
154+
const result = pickFromObject(user, keys)
155+
156+
expect(result).toEqual({ id: 1, name: "John" })
157+
})
158+
159+
it("handles falsy values correctly", () => {
160+
const data = {
161+
zero: 0,
162+
emptyString: "",
163+
false: false,
164+
nullValue: null,
165+
undefinedValue: undefined,
166+
}
167+
const result = pickFromObject(data, ["zero", "emptyString", "false", "nullValue"])
168+
169+
expect(result).toEqual({
170+
zero: 0,
171+
emptyString: "",
172+
false: false,
173+
nullValue: null,
174+
})
175+
})
176+
})

src/objects/objects.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,3 +47,29 @@ export const sortObject = <T extends Record<K, unknown>, K extends keyof T>(
4747
return { ...result, [key as K]: obj[key as K] }
4848
}, {} as T)
4949
}
50+
51+
/**
52+
* Creates a new object with only the specified properties from the source object.
53+
* Provides full type safety and intellisense for the picked properties.
54+
*
55+
* @param obj - The source object to pick properties from
56+
* @param keys - Array of property keys to pick from the source object
57+
* @returns A new object containing only the specified properties
58+
*
59+
* @example
60+
* const user = { id: 1, name: 'John', email: 'john@example.com', password: 'secret' }
61+
* const publicUser = pick(user, ['id', 'name', 'email'])
62+
* // Result: { id: 1, name: 'John', email: 'john@example.com' }
63+
*/
64+
export function pickFromObject<T extends object, K extends keyof T>(
65+
obj: T,
66+
keys: readonly K[],
67+
): Pick<T, K> {
68+
const result = {} as Pick<T, K>
69+
for (const key of keys) {
70+
if (key in obj) {
71+
result[key] = obj[key]
72+
}
73+
}
74+
return result
75+
}

0 commit comments

Comments
 (0)