aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/client/util/SerializationHelper.ts72
-rw-r--r--src/debug/Test.tsx10
-rw-r--r--src/fields/NewDoc.ts45
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