Skip to content

Commit

Permalink
Merge branch 'release/v0.18.4'
Browse files Browse the repository at this point in the history
  • Loading branch information
holtwick committed Mar 15, 2024
2 parents 0fb509f + 98f324b commit 171dffb
Show file tree
Hide file tree
Showing 6 changed files with 200 additions and 9 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "zeed",
"type": "module",
"version": "0.18.3",
"version": "0.18.4",
"description": "🌱 Simple foundation library",
"author": {
"name": "Dirk Holtwick",
Expand Down
2 changes: 1 addition & 1 deletion src/common/data/convert.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ export function renderMessages(
//

// Awesome trick from https://stackoverflow.com/a/5396742/140927
export function fixBrokenUth8String(brokenString: string): string {
export function fixBrokenUtf8String(brokenString: string): string {
try {
return decodeURIComponent(escape(brokenString))
}
Expand Down
61 changes: 60 additions & 1 deletion src/common/data/is.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { isEmpty, isNotEmpty, isNotNull, isNumber, isObject, isPrimitive, isRecord, isRecordPlain, isTruthy, isUint8Array, isValue } from './is'
import { isBinaryArray, isEmpty, isFunction, isNotEmpty, isNotNull, isNumber, isObject, isPrimitive, isRecord, isRecordPlain, isSymbol, isTruthy, isUint8Array, isValue } from './is'

describe('is', () => {
it('should identify Uint8Array', () => {
Expand Down Expand Up @@ -150,4 +150,63 @@ describe('is', () => {
expect(typeof nan === 'number').toBe(true)
expect(isNumber(nan)).toBe(false)
})

it('should identify function correctly', () => {
function fn() { return 'hello' }
const arrowFn = () => 'world'
const obj = { method() { return 'foo' } }
const classObj = new (class {
method() { return 'bar' }
})()

expect(isFunction(fn)).toBe(true)
expect(isFunction(arrowFn)).toBe(true)
// eslint-disable-next-line ts/unbound-method
expect(isFunction(obj.method)).toBe(true)
// eslint-disable-next-line ts/unbound-method
expect(isFunction(classObj.method)).toBe(true)

expect(isFunction({})).toBe(false)
expect(isFunction([])).toBe(false)
expect(isFunction(123)).toBe(false)
expect(isFunction('hello')).toBe(false)
expect(isFunction(true)).toBe(false)
expect(isFunction(null)).toBe(false)
expect(isFunction(undefined)).toBe(false)
})

it('should identify symbol correctly', () => {
const symbol = Symbol('test')
expect(isSymbol(symbol)).toBe(true)
expect(isSymbol('test')).toBe(false)
expect(isSymbol(123)).toBe(false)
expect(isSymbol({})).toBe(false)
expect(isSymbol([])).toBe(false)
expect(isSymbol(null)).toBe(false)
expect(isSymbol(undefined)).toBe(false)
})

it('should identify binary arrays correctly', () => {
expect(isBinaryArray(new Uint8Array([1, 2, 3]))).toBe(true)
expect(isBinaryArray(new Uint8ClampedArray([1, 2, 3]))).toBe(true)
expect(isBinaryArray(new Uint16Array([1, 2, 3]))).toBe(true)
expect(isBinaryArray(new Uint32Array([1, 2, 3]))).toBe(true)
expect(isBinaryArray(new Int8Array([1, 2, 3]))).toBe(true)
expect(isBinaryArray(new Int16Array([1, 2, 3]))).toBe(true)
expect(isBinaryArray(new Int32Array([1, 2, 3]))).toBe(true)
expect(isBinaryArray(new Float32Array([1, 2, 3]))).toBe(true)
expect(isBinaryArray(new Float64Array([1, 2, 3]))).toBe(true)
expect(isBinaryArray(new BigInt64Array([1n, 2n, 3n]))).toBe(true)
expect(isBinaryArray(new BigUint64Array([1n, 2n, 3n]))).toBe(true)

expect(isBinaryArray([])).toBe(false)
expect(isBinaryArray({})).toBe(false)
expect(isBinaryArray(123)).toBe(false)
expect(isBinaryArray('hello')).toBe(false)
expect(isBinaryArray(true)).toBe(false)
expect(isBinaryArray(null)).toBe(false)
expect(isBinaryArray(undefined)).toBe(false)
})

// ...
})
23 changes: 23 additions & 0 deletions src/common/data/is.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

import { size } from './utils'

// https://developer.mozilla.org/en-US/docs/Glossary/Primitive
export type Primitive =
| null
| undefined
Expand All @@ -18,6 +19,24 @@ export function isObject(obj: unknown): obj is object {
return obj != null && typeof obj === 'object'
}

export function isFunction(obj: unknown): obj is Function {
return obj != null && typeof obj === 'function'
}

export function isBinaryArray<T>(obj: unknown): obj is T {
return obj instanceof Uint8Array
|| obj instanceof Uint8ClampedArray
|| obj instanceof Uint16Array
|| obj instanceof Uint32Array
|| obj instanceof Int8Array
|| obj instanceof Int16Array
|| obj instanceof Int32Array
|| obj instanceof Float32Array
|| obj instanceof Float64Array
|| obj instanceof BigInt64Array
|| obj instanceof BigUint64Array
}

/** Something like number, string, boolean */
export function isPrimitive(obj: unknown): obj is Primitive {
return Object(obj) !== obj
Expand Down Expand Up @@ -58,6 +77,10 @@ export function isBoolean(obj: unknown): obj is boolean {
return typeof obj === 'boolean'
}

export function isSymbol(obj: unknown): obj is symbol {
return typeof obj === 'symbol'
}

/** @deprecated use `isNull` */
export function isNullOrUndefined(obj: unknown): obj is null | undefined {
return obj == null
Expand Down
84 changes: 83 additions & 1 deletion src/common/data/object.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ describe('objectPlain', () => {
},
})
})

it('should handle removeUndefined', () => {
const obj = {
a: 1,
Expand All @@ -169,4 +169,86 @@ describe('objectPlain', () => {
const result = objectPlain(obj, { filter: isNotNull })
expect(result).toEqual({ a: 1, c: [1] })
})

it('should also support crazy stuff', () => {
function fn(this: any) {
this.y = 1
}
fn.prototype.x = 2
fn.z = 6

const obj = {
a: 1,
b: {
x: Symbol('x'),
y: BigInt(123),
fn,
nan: Number.NaN,
inf: Number.POSITIVE_INFINITY,
err: new Error('err'),
set: new Set([1, { x: 1 }, 3]),
rx: /.*?.test/gim,
map: new Map<any, any>([
['a', 1],
['b', 2],
[true, 'bool'],
]),
weakMap: new WeakMap(),
uint8: new Uint8Array([1, 2, 3]),
uint16: new Uint16Array([1, 2, 3]),
c: 2,
d: [3, 4, 5],
},
}
const result = objectPlain(obj, {
keepAsIs: o => o instanceof Uint8Array,
errorTrace: false,
})

expect(result).toMatchInlineSnapshot(`
Object {
"a": 1,
"b": Object {
"c": 2,
"d": Array [
3,
4,
5,
],
"err": "Error: err",
"fn": Object {
"z": 6,
},
"inf": Infinity,
"map": Object {
"a": 1,
"b": 2,
"true": "bool",
},
"nan": NaN,
"rx": "/.*?.test/gim",
"set": Array [
1,
Object {
"x": 1,
},
3,
],
"uint16": Array [
1,
2,
3,
],
"uint8": Uint8Array [
1,
2,
3,
],
"weakMap": Object {},
"x": "Symbol(x)",
"y": 123n,
},
}
`)
})
})
37 changes: 32 additions & 5 deletions src/common/data/object.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { isArray, isObject, isPrimitive } from './is'
import { isArray, isBinaryArray, isFunction, isObject, isPrimitive, isSymbol } from './is'

/** Like `.map()` for object. Return new key and value or `undefined` to delete. */
export function objectMap<T = any>(
Expand Down Expand Up @@ -58,13 +58,17 @@ export function objectPlain(obj: any, opt?: {
maxDepth?: number
maxDepthValue?: any
circleValue?: any
errorTrace?: boolean
filter?: (value: any) => boolean
keepAsIs?: (value: any) => boolean
}): any {
const {
maxDepth = 99,
circleValue,
maxDepthValue,
errorTrace = true,
filter = () => true,
keepAsIs = () => false,
} = opt ?? {}

const cycle: any = []
Expand All @@ -73,6 +77,12 @@ export function objectPlain(obj: any, opt?: {
if (depth > maxDepth)
return maxDepthValue // '*** MAX DEPTH ***'

if (keepAsIs(obj))
return obj

if (isSymbol(obj))
return String(obj)

if (isPrimitive(obj))
return obj

Expand All @@ -81,16 +91,33 @@ export function objectPlain(obj: any, opt?: {

cycle.push(obj)

if (Array.isArray(obj))
return obj.map(o => handleObject(o, depth + 1)).filter(filter)
if (obj instanceof Date)
return obj.toISOString()

if (obj instanceof RegExp)
return obj.toString()

if (obj instanceof Map)
obj = Object.fromEntries(obj)

if (obj instanceof Set || isBinaryArray(obj))
obj = Array.from(obj as any)

if (isObject(obj)) {
if (obj instanceof Error)
return `${obj.name || 'Error'}: ${obj.message}${errorTrace ? `\n${obj.stack}` : ''}`

if (Array.isArray(obj)) {
return obj
.filter(filter)
.map(o => handleObject(o, depth + 1))
}

if (isObject(obj) || isFunction(obj)) {
const nobj: any = {}
for (const [key, value] of Object.entries(obj)) {
if (filter(value))
nobj[key] = handleObject(value, depth + 1)
}

return nobj
}

Expand Down

0 comments on commit 171dffb

Please sign in to comment.