diff --git a/README.md b/README.md index da786d4..7723bac 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,7 @@ import { commonUtil, formatUtil, searchQueryUtil, + typeUtil, deviceUtil, } from "kr-corekit"; @@ -73,6 +74,10 @@ const copied = await commonUtil.copyToClipboard("Hello, World!"); // true if suc // Search Query utilities const queryParams = searchQueryUtil.getAllQuery(); // { key: ["value1", "value2"], id: "123" } +// Type utilities +const isPlain = typeUtil.isPlainObject({}); // true +const isNotPlain = typeUtil.isPlainObject(new Date()); // false + // Device utilities const device = deviceUtil.getDevice(); // { isMobile: false, isTablet: false, isDesktop: true, isIOS: false, isAndroid: false } @@ -125,6 +130,10 @@ const formattedPhone = formatUtil.formatPhoneNumber("01012345678"); // "010-1234 - `getAllQuery(): Record` - Parses the current URL's query string and returns an object with key-value pairs. Values appear as arrays when the same key is used multiple times. +### TypeUtil + +- `isPlainObject(value: unknown): boolean` - Checks if a value is a plain object (created by Object literal or Object.create(null)), excluding arrays, dates, and other built-in objects. + ### DeviceUtil - `getDevice(): DeviceInfo` - Detects the user's device environment. Returns information about device type (mobile/tablet/desktop) and operating system (iOS/Android). Uses navigator.userAgent for detection and provides safe fallback for SSR environments. diff --git a/package/index.ts b/package/index.ts index 2b685eb..4a09500 100644 --- a/package/index.ts +++ b/package/index.ts @@ -1,3 +1,4 @@ +// 네임스페이스에 대한 익스포트 진행 export * as stringUtil from "./stringUtil"; export * as objectUtil from "./objectUtil"; export * as cookieUtil from "./cookieUtil"; @@ -5,3 +6,14 @@ export * as numberUtil from "./numberUtil"; export * as validationUtil from "./validationUtil"; export * as commonUtil from "./commonUtil"; export * as searchQueryUtil from "./searchQueryUtil"; +export * as typeUtil from "./typeUtil"; + +// 개별 함수에 대한 익스포트 진행 +export * from "./stringUtil"; +export * from "./objectUtil"; +export * from "./cookieUtil"; +export * from "./numberUtil"; +export * from "./validationUtil"; +export * from "./commonUtil"; +export * from "./searchQueryUtil"; +export * from "./typeUtil"; diff --git a/package/objectUtil/clearNullProperties/index.test.ts b/package/objectUtil/clearNullProperties/index.test.ts index f26510a..f78eeaf 100644 --- a/package/objectUtil/clearNullProperties/index.test.ts +++ b/package/objectUtil/clearNullProperties/index.test.ts @@ -6,3 +6,27 @@ test("만약 Null이 객체에 존재한다면, Null이 없는 객체가 반환 const result = clearNullProperties(obj); expect(result).toEqual({ a: 1, b: 2 }); }); + +test("만약 배열이 포함된 객체가 존재한다면, 그대로 반환된다.", () => { + const obj = { a: 1, b: [1, 2, 3], c: null }; + const result = clearNullProperties(obj); + expect(result).toEqual({ a: 1, b: [1, 2, 3] }); +}); + +test("만약 중첩된 객체가 존재한다면, 중첩된 객체의 Null도 제거된다.", () => { + const obj = { a: 1, b: { c: null, d: 4 }, e: null }; + const result = clearNullProperties(obj); + expect(result).toEqual({ a: 1, b: { d: 4 } }); +}); + +test("만약 모든 속성이 Null이라면, 빈 객체가 반환된다.", () => { + const obj = { a: null, b: null }; + const result = clearNullProperties(obj); + expect(result).toEqual({}); +}); + +test("만약 속성에 false 값이 존재한다면, false 값은 유지된다.", () => { + const obj = { a: false, b: null }; + const result = clearNullProperties(obj); + expect(result).toEqual({ a: false }); +}); diff --git a/package/objectUtil/clearNullProperties/index.ts b/package/objectUtil/clearNullProperties/index.ts index 8a8b2e8..46738ad 100644 --- a/package/objectUtil/clearNullProperties/index.ts +++ b/package/objectUtil/clearNullProperties/index.ts @@ -1,11 +1,18 @@ +import isPlainObject from "../../typeUtil/isPlainObject"; + export default function clearNullProperties(obj: T): T { - const result = { ...obj }; - Object.keys(result as Record).forEach((el) => { - if (!(result as Record)[el]) { - delete (result as Record)[el]; - } else if (typeof (result as Record)[el] === "object") { - (result as Record)[el] = clearNullProperties((result as Record)[el]); - } - }) - return result; + const result = { ...obj }; + Object.keys(result as Record).forEach((el) => { + if ( + (result as Record)[el] === null || + (result as Record)[el] === undefined + ) { + delete (result as Record)[el]; + } else if (isPlainObject((result as Record)[el])) { + (result as Record)[el] = clearNullProperties( + (result as Record)[el] + ); + } + }); + return result; } diff --git a/package/typeUtil/index.ts b/package/typeUtil/index.ts new file mode 100644 index 0000000..e3d4d93 --- /dev/null +++ b/package/typeUtil/index.ts @@ -0,0 +1 @@ +export { default as isPlainObject } from "./isPlainObject"; diff --git a/package/typeUtil/isPlainObject/index.test.ts b/package/typeUtil/isPlainObject/index.test.ts new file mode 100644 index 0000000..744a8b2 --- /dev/null +++ b/package/typeUtil/isPlainObject/index.test.ts @@ -0,0 +1,28 @@ +import { describe, expect, test } from "vitest"; +import isPlainObject from "."; + +describe("isPlainObject 유틸 함수 테스트", () => { + test("일반 객체는 true를 반환한다", () => { + const obj = { a: 1, b: 2 }; + expect(isPlainObject(obj)).toBe(true); + }); + + test("배열은 false를 반환한다", () => { + const arr = [1, 2, 3]; + expect(isPlainObject(arr)).toBe(false); + }); + + test("null은 false를 반환한다", () => { + expect(isPlainObject(null)).toBe(false); + }); + + test("날짜 객체는 false를 반환한다", () => { + const date = new Date(); + expect(isPlainObject(date)).toBe(false); + }); + + test("커스텀 프로토타입을 가진 객체는 false를 반환한다", () => { + const obj = Object.create({ a: 1 }); + expect(isPlainObject(obj)).toBe(false); + }); +}); diff --git a/package/typeUtil/isPlainObject/index.ts b/package/typeUtil/isPlainObject/index.ts new file mode 100644 index 0000000..ea5ce9d --- /dev/null +++ b/package/typeUtil/isPlainObject/index.ts @@ -0,0 +1,10 @@ +export default function isPlainObject(value: unknown): boolean { + if (typeof value !== "object" || value === null) { + return false; + } + + return ( + Object.getPrototypeOf(value) === Object.prototype || + Object.getPrototypeOf(value) === null + ); +}