aboutsummaryrefslogtreecommitdiff
path: root/src/client/util/SerializationHelper.ts
blob: 7ded85e43f0cde647a9045a59655f055b7e6b44a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
import { PropSchema, serialize, deserialize, custom, setDefaultModelSchema, getDefaultModelSchema, primitive, SKIP } from "serializr";
import { Field } from "../../new_fields/Doc";

export namespace SerializationHelper {
    let serializing: number = 0;
    export function IsSerializing() {
        return serializing > 0;
    }

    export function Serialize(obj: Field): any {
        if (obj === undefined || obj === null) {
            return undefined;
        }

        if (typeof obj !== 'object') {
            return obj;
        }

        serializing += 1;
        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];
        serializing -= 1;
        return json;
    }

    export function Deserialize(obj: any): any {
        if (obj === undefined || obj === null) {
            return undefined;
        }

        if (typeof obj !== 'object') {
            return obj;
        }

        serializing += 1;
        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`);
        }

        const value = deserialize(serializationTypes[obj.__type], obj);
        serializing -= 1;
        return value;
    }
}

let serializationTypes: { [name: string]: any } = {};
let reverseMap: { [ctor: string]: string } = {};

export interface DeserializableOpts {
    (constructor: { new(...args: any[]): any }): void;
    withFields(fields: string[]): Function;
}

export function Deserializable(name: string): DeserializableOpts;
export function Deserializable(constructor: { new(...args: any[]): any }): void;
export function Deserializable(constructor: { new(...args: any[]): any } | string): DeserializableOpts | void {
    function addToMap(name: string, ctor: { new(...args: any[]): any }) {
        const schema = getDefaultModelSchema(ctor) as any;
        if (schema.targetClass !== ctor) {
            const newSchema = { ...schema, factory: () => new ctor() };
            setDefaultModelSchema(ctor, newSchema);
        }
        if (!(name in serializationTypes)) {
            serializationTypes[name] = ctor;
            reverseMap[ctor.name] = name;
        } else {
            throw new Error(`Name ${name} has already been registered as deserializable`);
        }
    }
    if (typeof constructor === "string") {
        return Object.assign((ctor: { new(...args: any[]): any }) => {
            addToMap(constructor, ctor);
        }, { withFields: Deserializable.withFields });
    }
    addToMap(constructor.name, constructor);
}

export namespace Deserializable {
    export function withFields(fields: string[]) {
        return function (constructor: { new(...fields: any[]): any }) {
            Deserializable(constructor);
            let schema = getDefaultModelSchema(constructor);
            if (schema) {
                schema.factory = context => {
                    const args = fields.map(key => context.json[key]);
                    return new constructor(...args);
                };
                // TODO A modified version of this would let us not reassign fields that we're passing into the constructor later on in deserializing
                // fields.forEach(field => {
                //     if (field in schema.props) {
                //         let propSchema = schema.props[field];
                //         if (propSchema === false) {
                //             return;
                //         } else if (propSchema === true) {
                //             propSchema = primitive();
                //         }
                //         schema.props[field] = custom(propSchema.serializer,
                //             () => {
                //                 return SKIP;
                //             });
                //     }
                // });
            } else {
                schema = {
                    props: {},
                    factory: context => {
                        const args = fields.map(key => context.json[key]);
                        return new constructor(...args);
                    }
                };
                setDefaultModelSchema(constructor, schema);
            }
        };
    }
}

export function autoObject(): PropSchema {
    return custom(
        (s) => SerializationHelper.Serialize(s),
        (s) => SerializationHelper.Deserialize(s)
    );
}