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
11 changes: 0 additions & 11 deletions docs/array.md
Original file line number Diff line number Diff line change
Expand Up @@ -290,17 +290,6 @@ utils.maxBy(people, 'age') // { name: 'Foo', age: 42 }
```
---

## merge<T,>(a: T[], b: T[]): T[]

Merge two arrays.

```ts
utils.merge([1, 2, 3, 4], [4, 5, 6]) // [1, 2, 3, 4, 5, 6]
utils.merge([1, 2, 3], [4, 5, 6]) // [1, 2, 3, 4, 5, 6]
```

---

## minBy<T extends Record<string, unknown>, K extends keyof T>(arr: T[], key: K): T

Find the minimum item of an array by given key.
Expand Down
91 changes: 89 additions & 2 deletions docs/object.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,49 @@
# Object Functions

## const createObject(): object
## clone<T>(target: T): T

Recursively clones an object. An empty object is returned for uncloneable values such as error WeakMaps.

```ts
const map = new Map();
map.set('key', 'value');

const set = new Set();
set.add('value1');
set.add('value2');

const target = {
field1: 1,
field2: undefined,
field3: {
child: 'child'
},
field4: [2, 3, 4, { foo: { a: 'a', b: { c: 'c' } } }],
field5: true,
empty: null,
map,
set,
bool: new Boolean(false),
num: new Number(5),
str: new String('str'),
symbol: Object(Symbol(6)),
date: new Date('2022-04-18T00:00:00.000Z'),
reg: /\d+/,
error: new Error('Clone function error'),
greet: (): string => {
return 'hello friend!'
},
sum: function (a: number, b: number): number {
return a + b
}
}

const clonedObject = utils.clone(target)
```

---

## createObject(): object

Create an empty map that does not have properties.

Expand Down Expand Up @@ -37,12 +80,18 @@ const obj = {
b: {
c: 42
}
}
},
foo: [
2,
{ bar: 'baz' }
]
}

utils.getValue(obj, 'a.b.c') // 42
utils.getValue(obj, 'a.b.c.d') // undefined
utils.getValue(obj, 'a.b.c.d', 'default') // 'default'
utils.getValue(obj, 'foo[1]["bar"]') // 'baz'
utils.getValue(obj, 'foo[0]') // 2
```

---
Expand All @@ -63,6 +112,18 @@ utils.invert(obj) // { bar: 'foo', fuzz: 'baz', lorem: 'ergo' }

---

## merge<T,>(a: T[], b: T[]): T[]

Recursively merges own and inherited enumerable string keyed properties of source objects into the destination object.

```ts
utils.merge([1, 2, 3, 4], [4, 5, 6]) // [4, 5, 6, 4])
utils.merge([1, 2, 3], [4, 5, 6]) // [4, 5, 6])
utils.merge({ a: [{ b: 2 }, { d: 4 }] }, { a: [{ c: 3 }, { e: 5 }] }) // { a: [{ b: 2, c: 3 }, { d: 4, e: 5 }] })
```

---

## omit<T, K extends keyof T>(object: T, keys: K[]): Omit<T, K>

Omit a subset of properties from an object
Expand Down Expand Up @@ -144,6 +205,32 @@ utils.renameKeys(obj, { foo: 'bar', baz: 'fuz' }) // { bar: 'bar', fuz: 42 }

---

## setValue<T>(target: T, path: string, value: unknown): unknown

Sets the value to the path of the object. If a part of the route doesn't exist, it is created. Arrays are created for missing index properties, while objects are created for all other missing properties. This function does not modify the original object, returning a new object with the changes applied.

```ts
const target = {
a: {
b: {
c: 42
}
},
foo: [
2,
{ bar: 'baz' }
]
}

utils.setValue(target, 'a.b.c', 'foo') // { a: { b: { c: 'foo' } }, foo: [2, { bar: 'baz' }] })
utils.setValue(target, 'a.b.c.d', 'bar') // { a: { b: { c: { d: 'bar' } } }, foo: [2, { bar: 'baz' }] })
utils.setValue(target, 'foo[1]["bar"]', 'bazbar') // { a: { b: { c: 42 } }, foo: [2, { bar: 'bazbar' }] })
utils.setValue(target, 'foo[0]', 'bar') // { a: { b: { c: 42 } }, foo: ['bar', { bar: 'baz' }] })
utils.setValue(target, 'foo[]', 'bar') // { a: { b: { c: 42 } }, foo: [2, { bar: 'baz' }, 'bar'] })
```

---

## sortKeys<T extends object>(object: T): object

Sort an object by its properties,
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@devnetic/utils",
"version": "2.0.0",
"version": "2.1.0",
"description": "Common utils for every single day tasks",
"main": "lib/index.js",
"types": "lib/types/index.d.ts",
Expand Down
4 changes: 0 additions & 4 deletions src/array.ts
Original file line number Diff line number Diff line change
Expand Up @@ -134,10 +134,6 @@ export const maxBy = <T extends Record<string, unknown>, K extends keyof T>(arr:
})
}

export const merge = <T,>(a: T[], b: T[]): T[] => {
return [...new Set([...a, ...b])]
}

export const minBy = <T extends Record<string, unknown>, K extends keyof T>(arr: T[], key: K): T => {
return arr.reduce((prev, curr) => {
return curr[key] < prev[key] ? curr : prev
Expand Down
14 changes: 14 additions & 0 deletions src/misc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,20 @@ export const getQueryStringValue = (url: string, key: string): string | undefine
}

export const getType = (value: unknown = undefined): string => {
const type = typeof value

if (type !== 'object') {
if (type === 'function') {
return 'Function'
}

if (type === 'bigint') {
return 'BigInt'
}

return type
}

const typeRegex = /\[object (.*)\]/

return (Object.prototype.toString.call(value).match(typeRegex) as string[])[1]
Expand Down
138 changes: 127 additions & 11 deletions src/object.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,76 @@
import { getType } from './misc'
import { isNil, isObject } from './validator'

export type IsNullable<T, K> = T extends null | undefined ? K : never
export type NullableKeys<T> = { [K in keyof T]-?: IsNullable<T[K], K> }[keyof T]

export const clone = <T>(target: T): T => {
if (isNil(target) || !isObject(target)) {
return target
}

const cloned: any = {}

for (const attr in target) {
const value: any = target[attr]
const type: string = getType(value)

switch (type) {
case 'Array':
cloned[attr] = Object.values(value).map(clone)
break

case 'Function':
cloned[attr] = value.valueOf()
break

case 'Error':
cloned[attr] = Object.getOwnPropertyNames(value).reduce((acc: any, prop) => {
acc[prop] = value[prop]

return acc
}, new Error())

break

case 'Object':
cloned[attr] = Object.keys(value).reduce((cloned: any, key: string) => {
cloned[key] = clone(value[key])

return cloned
}, {})

break

case 'Symbol':
cloned[attr] = Object(value.valueOf())

break

case 'WeakMap':
cloned[attr] = {}

break

case 'Date':
case 'Boolean':
case 'Map':
case 'Number':
case 'RegExp':
case 'Set':
case 'String':
cloned[attr] = new value.constructor(value.valueOf())

break

default:
cloned[attr] = value
}
}

return cloned
}

export const createObject = (): object => {
return Object.create(null)
}
Expand All @@ -19,17 +89,13 @@ export const fromEntries = <T>(entries: Iterable<T> | ArrayLike<T>): object => {
}, {})
}

export const getValue = (object: object, path: string, defaultValue?: unknown): unknown => {
const keys = path.split('.')
let result: any = object

for (let index = 0; index < keys.length; index++) {
const key = keys[index]

result = result[key]
}

return result ?? defaultValue
export const getValue = (target: object, path: string, defaultValue?: unknown): unknown => {
return path.replace(/(?:\["?(\w+)"?])/g, '.$1')
.replace(/^\./, '')
.split('.')
.reduce((result: any, property: string) => {
return result[property] ?? defaultValue
}, target)
}

export const invert = (object: object): object => {
Expand All @@ -38,6 +104,23 @@ export const invert = (object: object): object => {
}, {})
}

export const merge = <T>(...sources: T[]): T => {
return sources.reduce((prev: any, obj: any) => {
(Object.keys(obj) as Array<keyof typeof obj>).forEach((key) => {
const pVal = prev[key]
const oVal = obj[key]

if ((Array.isArray(pVal) && Array.isArray(oVal)) || (isObject(pVal) && isObject(oVal))) {
prev[key] = merge(pVal, oVal)
} else {
prev[key] = oVal
}
})

return prev
}, Array.isArray(sources[0]) ? [] : {})
}

export const omit = <T, K extends keyof T>(object: T, keys: K[]): Omit<T, K> => {
return Object.entries(object).reduce((result: object, [key, value]: [any, any]) => {
if (keys.includes(key)) {
Expand Down Expand Up @@ -80,6 +163,39 @@ export const renameKeys = <T extends object, K extends keyof T>(object: T, map:
}, {})
}

export const setValue = <T, >(target: T, path: string, value: unknown): unknown => {
const segments: string[] = path.replace(/(?:\[["']?(\w*)["']?])/g, '.$1')
.replace(/^\./, '')
.replace(/\.$/, '')
.split('.')

const limit = segments.length - 1
let clonedTarget: any = clone(target)

// Store the reference to the result object
const resultReference = clonedTarget

for (let i = 0; i < limit; ++i) {
const key = segments[i]

if (typeof clonedTarget[key] !== 'object') {
clonedTarget[key] = {}
}

clonedTarget = clonedTarget[key]
}

const key = segments[limit]

if (Array.isArray(clonedTarget[key])) {
clonedTarget[key].push(value)
} else {
clonedTarget[key] = value
}

return resultReference
}

export const sortKeys = <T extends object>(object: T): object => {
return Object.entries(object).sort(([keyA, valueA], [keyB, valueB]): number => {
return keyA.localeCompare(keyB)
Expand Down
2 changes: 1 addition & 1 deletion src/validator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,7 @@ export const isRegExp = <T>(value: T): boolean => {
}

export const isString = <T>(value: T): boolean => {
return getType(value) === 'String'
return typeof value === 'string' || value instanceof String
}

export const isSubsetOf = <T>(set: T[], subset: T[]): boolean => {
Expand Down
5 changes: 0 additions & 5 deletions test/array.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -185,11 +185,6 @@ test('should returns the the minimun item of and array by key', t => {
t.is(utils.minBy(people, 'age'), people[1])
})

test('should merge two arrays', t => {
t.deepEqual(utils.merge([1, 2, 3, 4], [4, 5, 6]), [1, 2, 3, 4, 5, 6])
t.deepEqual(utils.merge([1, 2, 3], [4, 5, 6]), [1, 2, 3, 4, 5, 6])
})

test('should returns the partion of an array based on a predicate', t => {
const people = [
{ name: 'Bar', age: 24 },
Expand Down
Loading