diff --git a/json.test.ts b/json.test.ts index b097dca..19a6d8c 100644 --- a/json.test.ts +++ b/json.test.ts @@ -31,12 +31,29 @@ Deno.test({ }); Deno.test({ - name: "toValue - Array", + name: "toValue - legacy array", fn() { assertEquals(toValue({ type: "Array", value: [1, 2, 3] }), [1, 2, 3]); }, }); +Deno.test({ + name: "toValue - Array", + fn() { + assertEquals( + toValue({ + type: "json_array", + value: [ + { type: "number", value: 1 }, + { type: "number", value: 2 }, + { type: "number", value: 3 }, + ], + }), + [1, 2, 3], + ); + }, +}); + Deno.test({ name: "toValue - bigint", fn() { @@ -181,7 +198,7 @@ Deno.test({ }); Deno.test({ - name: "toValue - Map", + name: "toValue - legacy Map", fn() { const actual = toValue({ type: "Map", @@ -193,6 +210,25 @@ Deno.test({ }, }); +Deno.test({ + name: "toValue - Map", + fn() { + const actual = toValue({ + type: "json_map", + value: [[ + { type: "string", value: "key" }, + { type: "string", value: "value" }, + ], [ + { type: "string", value: "key2" }, + { type: "string", value: "value2" }, + ]], + }); + assert(actual instanceof Map); + assertEquals(actual.size, 2); + assertEquals(actual.get("key"), "value"); + }, +}); + Deno.test({ name: "toValue - null", fn() { @@ -208,7 +244,7 @@ Deno.test({ }); Deno.test({ - name: "toValue - object", + name: "toValue - legacy object", fn() { assertEquals(toValue({ type: "object", value: { foo: "bar" } }), { foo: "bar", @@ -216,6 +252,21 @@ Deno.test({ }, }); +Deno.test({ + name: "toValue - object", + fn() { + assertEquals( + toValue({ + type: "json_object", + value: { foo: { type: "string", value: "bar" } }, + }), + { + foo: "bar", + }, + ); + }, +}); + Deno.test({ name: "toValue - RegExp", fn() { @@ -227,7 +278,7 @@ Deno.test({ }); Deno.test({ - name: "toValue - Set", + name: "toValue - legacy Set", fn() { const actual = toValue({ type: "Set", value: [1, 2, 3] }); assert(actual instanceof Set); @@ -236,6 +287,23 @@ Deno.test({ }, }); +Deno.test({ + name: "toValue - Set", + fn() { + const actual = toValue({ + type: "json_set", + value: [ + { type: "number", value: 1 }, + { type: "number", value: 2 }, + { type: "number", value: 3 }, + ], + }); + assert(actual instanceof Set); + assertEquals(actual.size, 3); + assert(actual.has(1)); + }, +}); + Deno.test({ name: "toValue - string", fn() { @@ -375,8 +443,12 @@ Deno.test({ fn() { const actual = valueToJSON([1, 2, 3]); assertEquals(actual, { - type: "Array", - value: [1, 2, 3], + type: "json_array", + value: [ + { type: "number", value: 1 }, + { type: "number", value: 2 }, + { type: "number", value: 3 }, + ], }); }, }); @@ -528,8 +600,10 @@ Deno.test({ fn() { const actual = valueToJSON(new Map([["key", "value"]])); assertEquals(actual, { - type: "Map", - value: [["key", "value"]], + type: "json_map", + value: [ + [{ type: "string", value: "key" }, { type: "string", value: "value" }], + ], }); }, }); @@ -583,8 +657,12 @@ Deno.test({ fn() { const actual = valueToJSON(new Set([1, 2, 3])); assertEquals(actual, { - type: "Set", - value: [1, 2, 3], + type: "json_set", + value: [ + { type: "number", value: 1 }, + { type: "number", value: 2 }, + { type: "number", value: 3 }, + ], }); }, }); @@ -736,8 +814,8 @@ Deno.test({ fn() { const actual = valueToJSON({ key: "value" }); assertEquals(actual, { - type: "object", - value: { key: "value" }, + type: "json_object", + value: { key: { type: "string", value: "value" } }, }); }, }); @@ -987,8 +1065,12 @@ Deno.test({ assertEquals(actual, { key: [{ type: "string", value: "a" }], value: { - type: "Array", - value: [1, 2, 3], + type: "json_array", + value: [ + { type: "number", value: 1 }, + { type: "number", value: 2 }, + { type: "number", value: 3 }, + ], }, versionstamp: "00000000", }); @@ -1006,8 +1088,12 @@ Deno.test({ assertEquals(actual, { key: [{ type: "string", value: "a" }], value: { - type: "Array", - value: [1, 2, 3], + type: "json_array", + value: [ + { type: "number", value: 1 }, + { type: "number", value: 2 }, + { type: "number", value: 3 }, + ], }, versionstamp: "00000000", }); diff --git a/json.ts b/json.ts index 54c6ba3..189914d 100644 --- a/json.ts +++ b/json.ts @@ -147,12 +147,24 @@ export interface KvArrayBufferJSON { /** * A representation of an {@linkcode Array} Deno KV value. The value is the * JSON serialized version of the elements of the array. + * + * @deprecated This is a legacy representation and is only retained for + * compatibility with older versions of the library. */ -export interface KvArrayJSON { +export interface KvLegacyArrayJSON { type: "Array"; value: T[]; } +/** + * A representation of an {@linkcode Array} Deno KV value. The value is the + * JSON serialized version of the elements of the array. + */ +export interface KvArrayJSON { + type: "json_array"; + value: KvValueJSON[]; +} + /** * A representation of an {@linkcode DataView} Deno KV value. The value is * the bytes of the buffer encoded as a URL safe base64 string, for example a @@ -229,12 +241,25 @@ export interface KvKvU64JSON { * A representation of a {@linkcode Map} Deno KV value. The value is an array * of map entries where is map entry is a tuple of a JSON serialized key and * value. + * + * @deprecated This is a legacy representation and is only retained for + * compatibility with older versions of the library. */ -export interface KvMapJSON { +export interface KvLegacyMapJSON { type: "Map"; value: [key: K, value: V][]; } +/** + * A representation of a {@linkcode Map} Deno KV value. The value is an array + * of map entries where is map entry is a tuple of a JSON serialized key and + * value. + */ +export interface KvMapJSON { + type: "json_map"; + value: [key: KvValueJSON, value: KvValueJSON][]; +} + /** * A representation of a {@linkcode null} Deno KV value. The value is `null`. */ @@ -246,12 +271,24 @@ export interface KvNullJSON { /** * A representation of a {@linkcode object} Deno KV value. The value is a JSON * serialized version of the value. + * + * @deprecated This is a legacy representation and is only retained for + * compatibility with older versions of the library. */ -export interface KvObjectJSON { +export interface KvLegacyObjectJSON { type: "object"; value: T; } +/** + * A representation of a {@linkcode object} Deno KV value. The value is a JSON + * serialized version of the value. + */ +export interface KvObjectJSON { + type: "json_object"; + value: { [key: string]: KvValueJSON }; +} + /** * A representation of a {@linkcode RegExp} Deno KV value. The value is a string * representation of the regular expression value. @@ -264,12 +301,20 @@ export interface KvRegExpJSON { /** * A representation of a {@linkcode Set} Deno KV value. The value is an array * of JSON serialized entries. + * + * @deprecated This is a legacy representation and is only retained for + * compatibility with older versions of the library. */ -export interface KvSetJSON { +export interface KvLegacySetJSON { type: "Set"; value: T[]; } +export interface KvSetJSON { + type: "json_set"; + value: KvValueJSON[]; +} + /** Used internally to identify a typed array. */ type TypedArray = | Int8Array @@ -351,7 +396,11 @@ export type KvValueJSON = | KvSetJSON | KvStringJSON | KvTypedArrayJSON - | KvUndefinedJSON; + | KvUndefinedJSON + | KvLegacyArrayJSON + | KvLegacyMapJSON + | KvLegacyObjectJSON + | KvLegacySetJSON; // Deno KV Entry types @@ -447,6 +496,24 @@ function typedArrayToJSON(typedArray: ArrayBufferView): KvTypedArrayJSON { throw TypeError("Unexpected typed array type, could not serialize."); } +/** Internal function to encode an object. */ +function encodeObject(object: object): { [key: string]: KvValueJSON } { + const result: { [key: string]: KvValueJSON } = {}; + for (const [key, value] of Object.entries(object)) { + result[key] = valueToJSON(value); + } + return result; +} + +/** Internal function to decode an object. */ +function decodeObject(json: { [key: string]: KvValueJSON }): object { + const result: { [key: string]: unknown } = {}; + for (const [key, value] of Object.entries(json)) { + result[key] = toValue(value); + } + return result; +} + /** Serialize a {@linkcode Deno.KvKeyPart} to JSON. */ export function keyPartToJSON(value: Deno.KvKeyPart): KvKeyPartJSON { switch (typeof value) { @@ -480,7 +547,7 @@ export function keyToJSON(value: Deno.KvKey): KvKeyJSON { } /** Serialize an array value to JSON. */ -export function valueToJSON(value: T[]): KvArrayJSON; +export function valueToJSON(value: unknown[]): KvArrayJSON; /** Serialize a bigint value to JSON. */ export function valueToJSON(value: bigint): KvBigIntJSON; /** Serialize a boolean value to JSON. */ @@ -494,7 +561,7 @@ export function valueToJSON( /** Serialize a {@linkcode Deno.KvU64} value to JSON. */ export function valueToJSON(value: Deno.KvU64): KvKvU64JSON; /** Serialize a {@linkcode Map} value to JSON. */ -export function valueToJSON(value: Map): KvMapJSON; +export function valueToJSON(value: Map): KvMapJSON; /** Serialize a `null` value to JSON. */ export function valueToJSON(value: null): KvNullJSON; /** Serialize a number value to JSON. */ @@ -502,7 +569,7 @@ export function valueToJSON(value: number): KvNumberJSON; /** Serialize a {@linkcode RegExp} value to JSON. */ export function valueToJSON(value: RegExp): KvRegExpJSON; /** Serialize a {@linkcode Set} value to JSON. */ -export function valueToJSON(value: Set): KvSetJSON; +export function valueToJSON(value: Set): KvSetJSON; /** Serialize a string value to JSON. */ export function valueToJSON(value: string): KvStringJSON; /** Serialize a typed array value to JSON. */ @@ -516,7 +583,7 @@ export function valueToJSON(value: DataView): KvDataViewJSON; /** Serialize a `undefined` value to JSON. */ export function valueToJSON(value: undefined): KvUndefinedJSON; /** Serialize a object value to JSON. */ -export function valueToJSON(value: T): KvObjectJSON; +export function valueToJSON(value: object): KvObjectJSON; /** Serialize a value to JSON. */ export function valueToJSON(value: unknown): KvValueJSON; export function valueToJSON(value: unknown): KvValueJSON { @@ -530,7 +597,7 @@ export function valueToJSON(value: unknown): KvValueJSON { return { type: "undefined" }; case "object": if (Array.isArray(value)) { - return { type: "Array", value: [...value] }; + return { type: "json_array", value: value.map(valueToJSON) }; } if (value instanceof DataView) { return { type: "DataView", value: encodeBase64Url(value.buffer) }; @@ -551,7 +618,14 @@ export function valueToJSON(value: unknown): KvValueJSON { return errorToJSON(value); } if (value instanceof Map) { - return { type: "Map", value: [...value.entries()] }; + return { + type: "json_map", + value: [ + ...value.entries().map(( + [key, value], + ) => [valueToJSON(key), valueToJSON(value)]), + ] as [KvValueJSON, KvValueJSON][], + }; } if (value === null) { return { type: "null", value }; @@ -560,9 +634,9 @@ export function valueToJSON(value: unknown): KvValueJSON { return { type: "RegExp", value: String(value) }; } if (value instanceof Set) { - return { type: "Set", value: [...value] }; + return { type: "json_set", value: [...value].map(valueToJSON) }; } - return { type: "object", value: structuredClone(value) }; + return { type: "json_object", value: encodeObject(value) }; default: throw new TypeError("Unexpected value type, unable to serialize."); } @@ -714,7 +788,15 @@ export function toValue(json: KvArrayBufferJSON): ArrayBuffer; * Deserialize {@linkcode KvArrayJSON} to an array which can be stored in a Deno * KV store. */ -export function toValue(json: KvArrayJSON): T[]; +export function toValue(json: KvArrayJSON): unknown[]; +/** + * Deserialize {@linkcode KvLegacyArrayJSON} to an array which can be stored in a Deno + * KV store. + * + * @deprecated This is a legacy representation and is only retained for + * compatibility with older versions of the library. + */ +export function toValue(json: KvLegacyArrayJSON): T[]; /** * Deserialize {@linkcode KvBigIntJSON} to a bigint which can be stored in a * Deno KV store. @@ -751,7 +833,15 @@ export function toValue(json: KvKvU64JSON): Deno.KvU64; * Deserialize {@linkcode KvMapJSON} to a {@linkcode Map} which can be stored in * a Deno KV store. */ -export function toValue(json: KvMapJSON): Map; +export function toValue(json: KvMapJSON): Map; +/** + * Deserialize {@linkcode KvLegacyMapJSON} to a {@linkcode Map} which can be stored in + * a Deno KV store. + * + * @deprecated This is a legacy representation and is only retained for + * compatibility with older versions of the library. + */ +export function toValue(json: KvLegacyMapJSON): Map; /** * Deserialize {@linkcode KvNullJSON} to a `null` which can be stored in a Deno * KV store. @@ -766,7 +856,15 @@ export function toValue(json: KvNumberJSON): number; * Deserialize {@linkcode KvObjectJSON} to a value which can be stored in a Deno * KV store. */ -export function toValue(json: KvObjectJSON): T; +export function toValue(json: KvObjectJSON): Record; +/** + * Deserialize {@linkcode KvLegacyObjectJSON} to a value which can be stored in + * a Deno KV store. + * + * @deprecated This is a legacy representation and is only retained for + * compatibility with older versions of the library. + */ +export function toValue(json: KvLegacyObjectJSON): T; /** * Deserialize {@linkcode KvRegExpJSON} to a {@linkcode RegExp} which can be * stored in a Deno KV store. @@ -776,7 +874,15 @@ export function toValue(json: KvRegExpJSON): RegExp; * Deserialize {@linkcode KvSetJSON} to a {@linkcode Set} which can be stored in * a Deno KV store. */ -export function toValue(json: KvSetJSON): Set; +export function toValue(json: KvSetJSON): Set; +/** + * Deserialize {@linkcode KvLegacySetJSON} to a {@linkcode Set} which can be + * stored in a Deno KV store. + * + * @deprecated This is a legacy representation and is only retained for + * compatibility with older versions of the library. + */ +export function toValue(json: KvLegacySetJSON): Set; /** * Deserialize {@linkcode KvStringJSON} to a string which can be stored in a * Deno KV store. @@ -801,6 +907,16 @@ export function toValue(json: KvUndefinedJSON): undefined; export function toValue(json: KvValueJSON): unknown; export function toValue(json: KvValueJSON): unknown { switch (json.type) { + case "json_array": + return json.value.map(toValue); + case "json_map": + return new Map(json.value.map(( + [key, value]: [KvValueJSON, KvValueJSON], + ) => [toValue(key), toValue(value)]) as [unknown, unknown][]); + case "json_object": + return decodeObject(json.value); + case "json_set": + return new Set(json.value.map(toValue)); case "Array": case "null": case "object":