@plandek-utils/plain-object
Advanced tools
| import { parseDayjsOrError } from "@plandek-utils/ts-parse-dayjs"; | ||
| import { describe, expect, it } from "vitest"; | ||
| import { isPlainObject, isPlainObjectValue, isValidArray, isValidPrimitive } from "../index.ts"; | ||
| describe("isPlainObjectValue", () => { | ||
| it("should return true for nil values", () => { | ||
| expect(isPlainObjectValue(null)).toBe(true); | ||
| expect(isPlainObjectValue(undefined)).toBe(true); | ||
| }); | ||
| it("should return true for boolean values", () => { | ||
| expect(isPlainObjectValue(true)).toBe(true); | ||
| expect(isPlainObjectValue(false)).toBe(true); | ||
| }); | ||
| it("should return true for number values", () => { | ||
| expect(isPlainObjectValue(42)).toBe(true); | ||
| }); | ||
| it("should return true for string values", () => { | ||
| expect(isPlainObjectValue("hello")).toBe(true); | ||
| expect(isPlainObjectValue("42")).toBe(true); | ||
| }); | ||
| it("should return true for Dayjs objects", () => { | ||
| const dayjsObj = parseDayjsOrError("2023-01-01"); | ||
| expect(isPlainObjectValue(dayjsObj)).toBe(true); | ||
| }); | ||
| it("should return false for non-primitive values", () => { | ||
| expect(isPlainObjectValue(() => "whatever")).toBe(false); | ||
| }); | ||
| it("should return false for Infinity, -Infinity and NaN", () => { | ||
| expect(isPlainObjectValue(Number.POSITIVE_INFINITY)).toBe(false); | ||
| expect(isPlainObjectValue(Number.NEGATIVE_INFINITY)).toBe(false); | ||
| expect(isPlainObjectValue(Number.NaN)).toBe(false); | ||
| }); | ||
| it("should return false for symbol", () => { | ||
| expect(isPlainObjectValue(Symbol("foo"))).toBe(false); | ||
| }); | ||
| it("should recursively check arrays", () => { | ||
| const arrayWithPrimitives = [42, "hello", true]; | ||
| expect(arrayWithPrimitives.every(isPlainObjectValue)).toBe(true); | ||
| expect(isPlainObjectValue(arrayWithPrimitives)).toBe(true); | ||
| }); | ||
| it("should recursively check arrays fail nested", () => { | ||
| const arrayWithPrimitives = [42, "hello", true, () => "whatever"]; | ||
| expect(isPlainObjectValue(arrayWithPrimitives)).toBe(false); | ||
| }); | ||
| it("should recursively check nested objects", () => { | ||
| const nestedObj = { a: 42, b: "hello", c: { d: true } }; | ||
| expect(Object.values(nestedObj).every(isPlainObjectValue)).toBe(true); | ||
| expect(isPlainObjectValue(nestedObj)).toBe(true); | ||
| expect(isPlainObjectValue({ a: 42, b: "hello", c: { d: () => "oh no" } })).toBe(false); | ||
| }); | ||
| }); | ||
| describe("isPlainObject", () => { | ||
| it("should return true for plain objects", () => { | ||
| const obj = { a: 42, b: "hello", c: true }; | ||
| expect(isPlainObject(obj)).toBe(true); | ||
| }); | ||
| it("should return true for empty plain objects", () => { | ||
| expect(isPlainObject({})).toBe(true); | ||
| }); | ||
| it("should return false for arrays", () => { | ||
| expect(isPlainObject([])).toBe(false); | ||
| expect(isPlainObject([1, 2, 3])).toBe(false); | ||
| }); | ||
| it("should return false for null and undefined", () => { | ||
| expect(isPlainObject(null)).toBe(false); | ||
| expect(isPlainObject(undefined)).toBe(false); | ||
| }); | ||
| }); | ||
| describe("isValidPrimitive", () => { | ||
| it("should return true for valid primitives", () => { | ||
| expect(isValidPrimitive(null)).toBe(true); | ||
| expect(isValidPrimitive(undefined)).toBe(true); | ||
| expect(isValidPrimitive(true)).toBe(true); | ||
| expect(isValidPrimitive(false)).toBe(true); | ||
| expect(isValidPrimitive(42)).toBe(true); | ||
| expect(isValidPrimitive("hello")).toBe(true); | ||
| expect(isValidPrimitive(parseDayjsOrError("2023-01-01"))).toBe(true); | ||
| }); | ||
| it("should return false for invalid primitives", () => { | ||
| expect(isValidPrimitive(() => "whatever")).toBe(false); | ||
| expect(isValidPrimitive(Number.POSITIVE_INFINITY)).toBe(false); | ||
| expect(isValidPrimitive(Number.NEGATIVE_INFINITY)).toBe(false); | ||
| expect(isValidPrimitive(Number.NaN)).toBe(false); | ||
| expect(isValidPrimitive(Symbol("foo"))).toBe(false); | ||
| }); | ||
| }); | ||
| describe("isValidArray", () => { | ||
| it("should return true for an array of PlainObjectValues", () => { | ||
| const arrayWithPrimitives = [42, "hello", true]; | ||
| expect(isValidArray(arrayWithPrimitives)).toBe(true); | ||
| }); | ||
| it("should return false for an array with non-PlainObjectValues", () => { | ||
| const arrayWithPrimitives = [42, "hello", true, () => "whatever"]; | ||
| expect(isValidArray(arrayWithPrimitives)).toBe(false); | ||
| }); | ||
| }); |
+118
| import { dayjsSchema } from "@plandek-utils/ts-parse-dayjs"; | ||
| import { z } from "zod"; | ||
| export const plainObjectValuePrimitiveSchema = z.union([ | ||
| z.undefined(), | ||
| z.null(), | ||
| z.boolean(), | ||
| z.number().finite(), | ||
| z.string(), | ||
| z.instanceof(Date), | ||
| dayjsSchema, | ||
| ]); | ||
| /** | ||
| * Union of all possible primitive values (non-array, non-nested-object) of a Plain Object field. | ||
| * | ||
| * That means: | ||
| * - It can be `undefined` or `null`. | ||
| * - It can be a boolean, number, or string. | ||
| * - It can be a Date object. | ||
| * - It can be a Dayjs object. | ||
| */ | ||
| export type PlainObjectValuePrimitive = z.infer<typeof plainObjectValuePrimitiveSchema>; | ||
| /** | ||
| * Union of all possible values of a Plain Object field. | ||
| * | ||
| * That means: | ||
| * - It can be `undefined` or `null`. | ||
| * - It can be a boolean, number, or string. | ||
| * - It can be a Dayjs object. | ||
| * - It can be an array of Plain Object values. | ||
| * - It can be a Plain Object where all values are Plain Object values. | ||
| * | ||
| * No other types are allowed, including functions. | ||
| */ | ||
| export type PlainObjectValue = | ||
| | PlainObjectValuePrimitive | ||
| | PlainObjectValue[] | ||
| | readonly PlainObjectValue[] | ||
| | { [prop: string]: PlainObjectValue }; | ||
| export const plainObjectValueSchema: z.ZodType<PlainObjectValue> = z.lazy(() => | ||
| z.union([ | ||
| plainObjectValuePrimitiveSchema, | ||
| z.array(plainObjectValueSchema), | ||
| z.array(plainObjectValueSchema).readonly(), | ||
| z.record(plainObjectValueSchema), | ||
| ]), | ||
| ); | ||
| /** | ||
| * Check if the given value is either a Plain Object or a valid value of a Plain Object field. | ||
| * | ||
| * That means: | ||
| * - It can be `undefined` or `null`. | ||
| * - It can be a boolean, number, or string. | ||
| * - It can be a Dayjs object. | ||
| * - It can be an array of Plain Object values. | ||
| * - It can be a Plain Object where all values are Plain Object values. | ||
| * | ||
| * No other types are allowed, including functions. | ||
| */ | ||
| export function isPlainObjectValue(x: unknown): x is PlainObjectValue { | ||
| return plainObjectValueSchema.safeParse(x).success; | ||
| } | ||
| export const plainObjectSchema = z.record(plainObjectValueSchema); | ||
| /** | ||
| * Object where all values are Plain Object values. | ||
| */ | ||
| export type PlainObject = z.infer<typeof plainObjectSchema>; | ||
| /** | ||
| * Union of Plain Object and an array of Plain Objects. | ||
| */ | ||
| export type PlainObjectOrArray = PlainObject | PlainObject[]; | ||
| /** | ||
| * Checks if the given PlainObjectValue is a PlainObject. | ||
| * | ||
| * Since the given value is a PlainObjectValue, we just need to discard the primitive values and arrays. | ||
| * | ||
| * @param o | ||
| * @returns | ||
| */ | ||
| export function isPlainObject(o: PlainObjectValue): o is Record<string, unknown> & PlainObject { | ||
| return plainObjectSchema.safeParse(o).success; | ||
| } | ||
| /** | ||
| * Extension of PlainObjectValue that allows for a generic type to be added as a valid value. | ||
| */ | ||
| export type PlainObjectValueExtended<T> = | ||
| | PlainObjectValuePrimitive | ||
| | T | ||
| | PlainObjectValueExtended<T>[] | ||
| | readonly PlainObjectValueExtended<T>[] | ||
| | { [prop: string]: PlainObjectValueExtended<T> }; | ||
| /** | ||
| * Extension of PlainObject that uses PlainObjectValueExtended to add extra possible values. | ||
| */ | ||
| export type PlainObjectExtended<T> = { | ||
| [prop: string]: PlainObjectValueExtended<T>; | ||
| }; | ||
| /** | ||
| * Returns true if the given value is a valid primitive: null, undefined, boolean, string, Dayjs, or number. | ||
| */ | ||
| export function isValidPrimitive(x: unknown): x is PlainObjectValuePrimitive { | ||
| return plainObjectValuePrimitiveSchema.safeParse(x).success; | ||
| } | ||
| /** | ||
| * Returns true if the given value is a valid array: array where all elements are PlainObjectValues. | ||
| */ | ||
| export function isValidArray(x: unknown): x is PlainObjectValue[] { | ||
| return Array.isArray(x) && x.every(isPlainObjectValue); | ||
| } |
+9
-8
| { | ||
| "name": "@plandek-utils/plain-object", | ||
| "version": "2.0.0", | ||
| "version": "2.0.1", | ||
| "author": "Eduardo TuriƱo <eturino@plandek.com>", | ||
@@ -10,3 +10,4 @@ "description": "TypeScript types and predicate `isPlainObject` and `isPlainObjectValue`, which are serializable POJOs.", | ||
| "files": [ | ||
| "dist" | ||
| "dist", | ||
| "src" | ||
| ], | ||
@@ -44,14 +45,14 @@ "scripts": { | ||
| "@commitlint/cz-commitlint": "^19.6.1", | ||
| "@types/node": "^22.10.7", | ||
| "@vitest/coverage-v8": "^3.0.3", | ||
| "@types/node": "^22.12.0", | ||
| "@vitest/coverage-v8": "^3.0.4", | ||
| "commitizen": "^4.3.1", | ||
| "husky": "^9.1.7", | ||
| "inquirer": "^9.3.7", | ||
| "tsup": "^8.3.5", | ||
| "tsup": "^8.3.6", | ||
| "typescript": "^5.7.3", | ||
| "vitest": "^3.0.3" | ||
| "vitest": "^3.0.4" | ||
| }, | ||
| "peerDependencies": { | ||
| "@plandek-utils/ts-parse-dayjs": "6.3.2", | ||
| "zod": "3.24.1" | ||
| "@plandek-utils/ts-parse-dayjs": "^6.4.0", | ||
| "zod": "^3.24.1" | ||
| }, | ||
@@ -58,0 +59,0 @@ "config": { |
+0
-9
@@ -62,13 +62,4 @@ # @plandek-utils/plain-object | ||
| This package is developed with deno 2. The production code is in `src/mod.ts` and its test in | ||
| `src/__tests__/mod.spec.ts` | ||
| - `deno fmt`: format files | ||
| - `deno lint`: lint files | ||
| - `deno dev`: run tests on each change in mod.ts | ||
| - `deno run test && deno run lcov && deno run html`: run the tests with coverage, then convert to lcov and prepare in | ||
| `html_cov` an HTML export of the coverage info. | ||
| TypeScript types and predicate `isPlainObject` and `isPlainObjectValue`. PlainObject = POJO where all values are | ||
| PlainObjectValue. PlainObjectValue = serializable value (Dayjs, nil, number, string, boolean, PlainObjectValue[], | ||
| PlainObject) |
29890
33.12%10
25%397
104.64%65
-12.16%