diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/client/util/SerializationHelper.ts | 72 | ||||
-rw-r--r-- | src/debug/Test.tsx | 10 | ||||
-rw-r--r-- | src/fields/NewDoc.ts | 45 |
3 files changed, 107 insertions, 20 deletions
diff --git a/src/client/util/SerializationHelper.ts b/src/client/util/SerializationHelper.ts new file mode 100644 index 000000000..656101c95 --- /dev/null +++ b/src/client/util/SerializationHelper.ts @@ -0,0 +1,72 @@ +import { PropSchema, serialize, deserialize, custom } from "serializr"; +import { Field } from "../../fields/NewDoc"; + +export class SerializationHelper { + + public static Serialize(obj: Field): any { + if (!obj) { + return null; + } + + if (typeof obj !== 'object') { + return obj; + } + + if (!(obj.constructor.name in reverseMap)) { + throw Error(`type '${obj.constructor.name}' not registered. Make sure you register it using a @Deserializable decorator`); + } + + const json = serialize(obj); + json.__type = reverseMap[obj.constructor.name]; + return json; + } + + public static Deserialize(obj: any): any { + if (!obj) { + return null; + } + + if (typeof obj !== 'object') { + return obj; + } + + if (!obj.__type) { + throw Error("No property 'type' found in JSON."); + } + + if (!(obj.__type in serializationTypes)) { + throw Error(`type '${obj.__type}' not registered. Make sure you register it using a @Deserializable decorator`); + } + + return deserialize(serializationTypes[obj.__type], obj); + } +} + +let serializationTypes: { [name: string]: any } = {}; +let reverseMap: { [ctor: string]: string } = {}; + +export function Deserializable(name: string): Function; +export function Deserializable(constructor: Function): void; +export function Deserializable(constructor: Function | string): Function | void { + function addToMap(name: string, ctor: Function) { + if (!(name in serializationTypes)) { + serializationTypes[name] = constructor; + reverseMap[ctor.name] = name; + } else { + throw new Error(`Name ${name} has already been registered as deserializable`); + } + } + if (typeof constructor === "string") { + return (ctor: Function) => { + addToMap(constructor, ctor); + }; + } + addToMap(constructor.name, constructor); +} + +export function autoObject(): PropSchema { + return custom( + (s) => SerializationHelper.Serialize(s), + (s) => SerializationHelper.Deserialize(s) + ); +}
\ No newline at end of file diff --git a/src/debug/Test.tsx b/src/debug/Test.tsx index ca093e5b2..7e7b3a964 100644 --- a/src/debug/Test.tsx +++ b/src/debug/Test.tsx @@ -2,6 +2,7 @@ import * as React from 'react'; import * as ReactDOM from 'react-dom'; import { serialize, deserialize, map } from 'serializr'; import { URLField, Doc } from '../fields/NewDoc'; +import { SerializationHelper } from '../client/util/SerializationHelper'; class Test extends React.Component { onClick = () => { @@ -14,11 +15,10 @@ class Test extends React.Component { doc.url = url; doc.testDoc = doc2; - console.log(doc.hello); - console.log(doc.fields); - console.log(doc.test); - console.log(doc.url); - console.log(doc.testDoc); + console.log("doc", doc); + const cereal = Doc.Serialize(doc); + console.log("cereal", cereal); + console.log("doc again", SerializationHelper.Deserialize(cereal)); } render() { diff --git a/src/fields/NewDoc.ts b/src/fields/NewDoc.ts index d0e518306..05dd14bc3 100644 --- a/src/fields/NewDoc.ts +++ b/src/fields/NewDoc.ts @@ -1,16 +1,15 @@ import { observable, action } from "mobx"; import { Server } from "../client/Server"; import { UndoManager } from "../client/util/UndoManager"; -import { serialize, deserialize, serializable, primitive } from "serializr"; +import { serialize, deserialize, serializable, primitive, map, alias } from "serializr"; +import { autoObject, SerializationHelper, Deserializable } from "../client/util/SerializationHelper"; -const Type = Symbol("type"); - -const Id = Symbol("id"); export abstract class RefField { - readonly [Id]: string; + @serializable(alias("id", primitive())) + readonly __id: string; constructor(id: string) { - this[Id] = id; + this.__id = id; } } @@ -31,6 +30,8 @@ function url() { } }; } + +@Deserializable export class URLField extends ObjectField { @serializable(url()) url: URL; @@ -41,6 +42,7 @@ export class URLField extends ObjectField { } } +@Deserializable export class ProxyField<T extends RefField> extends ObjectField { constructor(); constructor(value: T); @@ -48,7 +50,7 @@ export class ProxyField<T extends RefField> extends ObjectField { super(); if (value) { this.cache = value; - this.fieldId = value[Id]; + this.fieldId = value.__id; } } @@ -94,11 +96,17 @@ export class ProxyField<T extends RefField> extends ObjectField { export type Field = number | string | boolean | ObjectField | RefField; const Self = Symbol("Self"); + +@Deserializable export class Doc extends RefField { private static setter(target: any, prop: string | symbol | number, value: any, receiver: any): boolean { + if (prop === "__id" || prop === "__fields") { + target[prop] = value; + return true; + } const curValue = target.__fields[prop]; - if (curValue === value || (curValue instanceof ProxyField && value instanceof RefField && curValue.fieldId === value[Id])) { + if (curValue === value || (curValue instanceof ProxyField && value instanceof RefField && curValue.fieldId === value.__id)) { // TODO This kind of checks correctly in the case that curValue is a ProxyField and value is a RefField, but technically // curValue should get filled in with value if it isn't already filled in, in case we fetched the referenced field some other way return true; @@ -130,16 +138,16 @@ export class Doc extends RefField { } private static getter(target: any, prop: string | symbol | number, receiver: any): any { - if (typeof prop !== "string") { - return undefined; + if (typeof prop === "symbol") { + return target[prop]; + } + if (prop === "__id" || prop === "__fields") { + return target[prop]; } return Doc.getField(target, prop, receiver); } - private static getField(target: any, prop: string, ignoreProto: boolean = false, callback?: (field: Field | undefined) => void): any { - if (typeof prop === "symbol") { - return target.__fields[prop]; - } + private static getField(target: any, prop: string | number, ignoreProto: boolean = false, callback?: (field: Field | undefined) => void): any { const field = target.__fields[prop]; if (field instanceof ProxyField) { return field.value(callback); @@ -162,6 +170,10 @@ export class Doc extends RefField { return new Promise(res => Doc.getField(self, key, ignoreProto, res)); } + static Serialize(doc: Doc) { + return SerializationHelper.Serialize(doc[Self]); + } + constructor(id: string) { super(id); const doc = new Proxy<this>(this, { @@ -175,6 +187,7 @@ export class Doc extends RefField { [key: string]: Field | null | undefined; + @serializable(alias("fields", map(autoObject()))) @observable private __fields: { [key: string]: Field | null | undefined } = {}; @@ -187,4 +200,6 @@ export class Doc extends RefField { export namespace Doc { export const Prototype = Symbol("Prototype"); -}
\ No newline at end of file +} + +export const GetAsync = Doc.GetAsync;
\ No newline at end of file |