From 532842c3538094b5418c43e5511cc22322233dab Mon Sep 17 00:00:00 2001 From: oliver-oloughlin Date: Mon, 27 Nov 2023 00:52:04 +0100 Subject: [PATCH] feat: added isObject utility + fixed stringify/parse --- json.ts | 88 +++++++++++++++++++++++++++++++++++++++++++++++++++---- object.ts | 55 ++++++++++++++++++++++++++++++++++ 2 files changed, 137 insertions(+), 6 deletions(-) diff --git a/json.ts b/json.ts index 0c31747..758417a 100644 --- a/json.ts +++ b/json.ts @@ -1,3 +1,5 @@ +import { isObject } from "./object.ts" + export type JSONError = { message: string name: string @@ -6,6 +8,7 @@ export type JSONError = { } export enum TypeKey { + Undefined = "__undefined__", BigInt = "__bigint__", KvU64 = "__kvu64__", Int8Array = "__int8array__", @@ -29,12 +32,47 @@ export enum TypeKey { NaN = "__nan__", } +/** + * Serialize a value to Uint8Array. + * + * @param value . Value to be serialized. + * @returns Serialized value. + */ +export function serialize(value: unknown) { + const str = stringify(value) + return new TextEncoder().encode(str) +} + +/** + * Deserialize a value encoded as Uint8Array. + * + * @param value - Value to be deserialize. + * @returns Deserialized value. + */ +export function deserialize(value: Uint8Array) { + const str = new TextDecoder().decode(value) + return parse(str) +} + +/** + * Convert a value to a JSON string. + * + * @param value - Value to be stringified. + * @param space + * @returns + */ export function stringify(value: unknown, space?: number | string) { - return JSON.stringify(value, replacer, space) + return JSON.stringify(_replacer(value), replacer, space) } +/** + * Parse a value from a JSON string. + * + * @param value - JSON string to be parsed. + * @returns + */ export function parse(value: string) { - return JSON.parse(value, reviver) as T + return postReviver(JSON.parse(value, reviver)) as T } /** @@ -44,7 +82,7 @@ export function parse(value: string) { * @param value * @returns */ -export function replacer(_key: string, value: unknown) { +function replacer(_key: string, value: unknown) { return _replacer(value) } @@ -55,7 +93,7 @@ export function replacer(_key: string, value: unknown) { * @param value * @returns */ -export function reviver(_key: string, value: unknown) { +function reviver(_key: string, value: unknown) { return _reviver(value) } @@ -69,7 +107,6 @@ function _replacer(value: unknown): unknown { // Return value if primitive, function or symbol if ( value === null || - value === undefined || typeof value === "string" || typeof value === "number" || typeof value === "function" || @@ -78,6 +115,13 @@ function _replacer(value: unknown): unknown { return value } + // Undefined + if (value === undefined) { + return { + [TypeKey.Undefined]: false, + } + } + // NaN if (Number.isNaN(value)) { return { @@ -238,7 +282,7 @@ function _replacer(value: unknown): unknown { } // Clone value to handle special cases - const clone = structuredClone(value) + const clone = structuredClone(value) as Record for (const [k, v] of Object.entries(value)) { if (v instanceof Date) { clone[k] = _replacer(v) @@ -385,6 +429,38 @@ function _reviver(value: unknown): unknown { return value } +/** + * Reviver post-parse. + * + * @param value + * @returns + */ +function postReviver(value: unknown): unknown { + if ( + value === undefined || + value === null || + typeof value !== "object" + ) { + return value + } + + if (TypeKey.Undefined in value) { + return undefined + } + + if (Array.isArray(value)) { + return value.map((v) => postReviver(v)) + } + + if (isObject(value)) { + return Object.fromEntries( + Object.entries(value).map(([k, v]) => [k, postReviver(v)]), + ) + } + + return value +} + /** * Map from special type entry to value. * diff --git a/object.ts b/object.ts index 22f9733..01e37f5 100644 --- a/object.ts +++ b/object.ts @@ -1,3 +1,11 @@ +/** + * Remove null and undefined entries from an object. + * + * If input value is null or undefined, the value is returned as is. + * + * @param value + * @returns + */ export function removeNullish(value: T): NonNullable { if (typeof value !== "object") { return value! @@ -15,3 +23,50 @@ export function removeNullish(value: T): NonNullable { .map(([key, val]) => [key, removeNullish(val)]), ) as NonNullable } + +/** + * Check if value is a basic JS object. + * + * @param value + * @returns + */ +export function isObject(value: unknown) { + // If value is null or undefined, return false + if (value === null || value === undefined) { + return false + } + + // If value is not an object, return false + if (typeof value !== "object") { + return false + } + + // If value is an instance of other KvValue objects, return false + if ( + value instanceof Deno.KvU64 || + value instanceof Array || + value instanceof Int8Array || + value instanceof Int16Array || + value instanceof Int32Array || + value instanceof BigInt64Array || + value instanceof Uint8Array || + value instanceof Uint16Array || + value instanceof Uint32Array || + value instanceof BigUint64Array || + value instanceof Uint8ClampedArray || + value instanceof Float32Array || + value instanceof Float64Array || + value instanceof ArrayBuffer || + value instanceof Date || + value instanceof Set || + value instanceof Map || + value instanceof RegExp || + value instanceof DataView || + value instanceof Error + ) { + return false + } + + // Return true after performing all checks + return true +}