diff --git a/src/AsyncData.ts b/src/AsyncData.ts index 0791feb..40a9039 100644 --- a/src/AsyncData.ts +++ b/src/AsyncData.ts @@ -1,6 +1,7 @@ import { keys, values } from "./Dict"; import { Option, Result } from "./OptionResult"; -import { LooseRecord } from "./types"; +import { BOXED_TYPE } from "./symbols"; +import { JsonAsyncData, LooseRecord } from "./types"; import { zip } from "./ZipUnzip"; class __AsyncData { @@ -92,6 +93,14 @@ class __AsyncData { // @ts-ignore value != null && value.__boxed_type__ === "AsyncData"; + static fromJSON = (value: JsonAsyncData) => { + return value.tag === "NotAsked" + ? AsyncData.NotAsked() + : value.tag === "Loading" + ? AsyncData.Loading() + : AsyncData.Done(value.value); + }; + map(this: AsyncData, func: (value: A) => B): AsyncData { if (this === NOT_ASKED || this === LOADING) { return this as unknown as AsyncData; @@ -266,6 +275,14 @@ class __AsyncData { isNotAsked(this: AsyncData): this is NotAsked { return this === NOT_ASKED; } + + toJSON(this: AsyncData): JsonAsyncData { + return this.match>({ + NotAsked: () => ({ [BOXED_TYPE]: "AsyncData", tag: "NotAsked" }), + Loading: () => ({ [BOXED_TYPE]: "AsyncData", tag: "Loading" }), + Done: (value) => ({ [BOXED_TYPE]: "AsyncData", tag: "Done", value }), + }); + } } // @ts-expect-error diff --git a/src/Boxed.ts b/src/Boxed.ts index 4e19b13..0139ed4 100644 --- a/src/Boxed.ts +++ b/src/Boxed.ts @@ -6,3 +6,4 @@ export { Future } from "./Future"; export { Lazy } from "./Lazy"; export { Option, Result } from "./OptionResult"; export * as Serializer from "./Serializer"; +export { JsonAsyncData, JsonOption, JsonResult } from "./types"; diff --git a/src/OptionResult.ts b/src/OptionResult.ts index 569f606..6d9e2ad 100644 --- a/src/OptionResult.ts +++ b/src/OptionResult.ts @@ -1,5 +1,6 @@ import { keys, values } from "./Dict"; -import { LooseRecord } from "./types"; +import { BOXED_TYPE } from "./symbols"; +import { JsonOption, JsonResult, LooseRecord } from "./types"; import { zip } from "./ZipUnzip"; class __Option { @@ -99,6 +100,10 @@ class __Option { : a.tag === b.tag; }; + static fromJSON = (value: JsonOption) => { + return value.tag === "None" ? Option.None() : Option.Some(value.value); + }; + /** * Returns the Option containing the value from the callback * @@ -222,6 +227,13 @@ class __Option { isNone(this: Option): this is None { return this === NONE; } + + toJSON(this: Option): JsonOption { + return this.match>({ + None: () => ({ [BOXED_TYPE]: "Option", tag: "None" }), + Some: (value) => ({ [BOXED_TYPE]: "Option", tag: "Some", value }), + }); + } } // @ts-expect-error @@ -387,6 +399,12 @@ class __Result { return false; }; + static fromJSON = (value: JsonResult) => { + return value.tag === "Ok" + ? Result.Ok(value.value) + : Result.Error(value.error); + }; + /** * Returns the Result containing the value from the callback * @@ -522,6 +540,13 @@ class __Result { isError(this: Result): this is Error { return this.tag === "Error"; } + + toJSON(this: Result): JsonResult { + return this.match>({ + Ok: (value) => ({ [BOXED_TYPE]: "Result", tag: "Ok", value }), + Error: (error) => ({ [BOXED_TYPE]: "Result", tag: "Error", error }), + }); + } } // @ts-expect-error diff --git a/src/Serializer.ts b/src/Serializer.ts index 742e63d..698f24d 100644 --- a/src/Serializer.ts +++ b/src/Serializer.ts @@ -1,34 +1,13 @@ import { AsyncData } from "./AsyncData"; import { Option, Result } from "./OptionResult"; +import { BOXED_TYPE } from "./symbols"; export const encode = (value: any, indent?: number | undefined) => { return JSON.stringify( value, - function (key, value) { - if (value == null) { - return; - } - if (value.__boxed_type__ === "Option") { - return { - __boxed_type__: "Option", - tag: value.tag, - value: value.value, - }; - } - if (value.__boxed_type__ === "Result") { - return { - __boxed_type__: "Result", - tag: value.tag, - value: value.value, - error: value.error, - }; - } - if (value.__boxed_type__ === "AsyncData") { - return { - __boxed_type__: "AsyncData", - tag: value.tag, - value: value.value, - }; + (key, value) => { + if (typeof value[BOXED_TYPE] === "string") { + return { ...value, __boxed_type__: value[BOXED_TYPE] }; } return value; }, @@ -42,19 +21,13 @@ export const decode = (value: string) => { return value; } if (value.__boxed_type__ === "Option") { - return value.tag === "Some" ? Option.Some(value.value) : Option.None(); + return Option.fromJSON(value); } if (value.__boxed_type__ === "Result") { - return value.tag === "Ok" - ? Result.Ok(value.value) - : Result.Error(value.error); + return Result.fromJSON(value); } if (value.__boxed_type__ === "AsyncData") { - return value.tag === "NotAsked" - ? AsyncData.NotAsked() - : value.tag === "Loading" - ? AsyncData.Loading() - : AsyncData.Done(value.value); + return AsyncData.fromJSON(value); } return value; }); diff --git a/src/symbols.ts b/src/symbols.ts new file mode 100644 index 0000000..fc9e6cf --- /dev/null +++ b/src/symbols.ts @@ -0,0 +1 @@ +export const BOXED_TYPE = Symbol.for("__boxed_type__"); diff --git a/src/types.ts b/src/types.ts index 163c8ab..a85d055 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1 +1,12 @@ export type LooseRecord = Record; + +export type JsonResult = + | { tag: "Ok"; value: A } + | { tag: "Error"; error: E }; + +export type JsonAsyncData = + | { tag: "NotAsked" } + | { tag: "Loading" } + | { tag: "Done"; value: A }; + +export type JsonOption = { tag: "None" } | { tag: "Some"; value: A };