Преобразуйте класс Typescript в строку, используя getter и setter вместо закрытых переменных - PullRequest
0 голосов
/ 16 января 2019

У меня есть следующий код Typescript:

class Foo {
    private _id: number;
    private _desc: string;

    constructor(id: number, desc: string) {
        this._id = id;
        this._desc = desc;
    }

    public get id(): number {
        return this.id;
    } 

    public set id(value: number) {
        this._id = value;
    }

    public get desc():string {
        return this.desc;
    } 

    public set desc(value: string) {
        this._desc = value;
    }
}

let foo = new Foo(1, 'something');

Я хотел бы получить строку из класса Typescript, я имею в виду с getter и setter. Тогда я должен получить следующую строку:

{"id":1,"desc":"something"}

Согласно этому ответу Я могу достичь этого, добавив в класс следующий метод:

public toJSONString(): string {
    return JSON.stringify(this, Object.keys(this.constructor.prototype));
}

Работает.

Не работает, если класс Typescript содержит какой-либо другой подкласс.

Так что, если у меня есть следующий код:

class Foo {
    private _id: number;
    private _desc: string;
    private _user: Bar;

    constructor(id: number, desc: string, user: Bar) {
        this._id = id;
        this._desc = desc;
        this._user = user;
    }

    public get id(): number {
        return this._id;
    } 

    public set id(value: number) {
        this._id = value;
    }

    public get desc():string {
        return this._desc;
    } 

    public set desc(value: string) {
        this._desc = value;
    }

    public get user(): Bar {
        return this._user;
    } 

    public set user(value: Bar) {
        this._user = value;
    }

    public toJSONString(): string {
        return JSON.stringify(this, Object.keys(this.constructor.prototype));
    }
}

class Bar {
    private _name: string;
    private _surname: string;

    constructor(name: string, surname: string) {
        this._name = name;
        this._surname = surname;
    }

    public get name(): string {
        return this._name;
    } 

    public set name(value: string) {
        this._name = value;
    }

    public get surname():string {
        return this._surname;
    } 

    public set surname(value: string) {
        this._surname = value;
    }
}

let foo = new Foo(1, 'something', new Bar('foo', 'bar'));

Если я использую метод toJSONString, я получаю следующую строку:

{"id":1,"desc":"something","user":{}}

вместо этого:

{"id":1,"desc":"something","user":{ "name": "foo", "surname": "bar"}}

Итак, как я могу получить строку из класса Typescript, который имеет другие подклассы?

(Если вам нужна здесь игровая площадка для первого кода и вот игровая площадка для второго кода)

1 Ответ

0 голосов
/ 17 января 2019

Здесь нужно помнить 2 вещи:

  1. Когда вы определяете методы получения и установки, они не становятся методами экземпляра после переноса в Javascript, но они добавляются в прототип с помощью Object.defineProperty. Это означает, что вы не получите их, просто используя JSON.stringify

  2. Передача массива replacer в JSON.stringify и указание использовать только значения прототипа, но это не работает на вложенных объектах. По правде говоря, JSON.stringify будет анализировать только свойства с этим именем, независимо от того, где они находятся в структуре объекта.

Например

let a = {
    user: "Foo",
    data: {
        name: "Bar"
    }
};

JSON.stringify(a, ["user", "data"]);

Выводит {"user":"Foo","data":{}}, потому что, хотя ключ вложенного объекта равен data, сам объект не имеет свойств с именем user или data
Но

let a = {
    user: "Foo",
    data: {
        user: "Bar"
    }
};

JSON.stringify(a, ["user", "data"]);

Выводит {"user":"Foo","data":{"user":"Bar"}}, потому что вложенный объект имеет свойство, называемое user, так же как его родительский

Я считаю, что это поведение может сбивать с толку, но можно реализовать решение, создав метод, который получает все свойства всех интересующих вас объектов. В Typescript я не нашел способа проверить, реализует ли класс интерфейс (или расширяющий класс), поэтому мне пришлось немного поработать с тем, что, как я знаю, работает, хотя это не так уж «элегантно».

abstract class Stringifyiable {
    private isStringifyiable(value): boolean {
        return value != null && (typeof value === 'object' || typeof value === 'function') && value['getJsonKeys'] && typeof value['getJsonKeys'] === 'function';
    }

    public getJsonKeys(): string[] {
        let keys = Object.keys(this.constructor.prototype);
        keys.forEach(key => {
            if (this.isStringifyiable(this[key])) {
                keys = keys.concat(this[key].getJsonKeys());
            }
        });

        return keys;
    }

    public toJSONString(): string {
        return JSON.stringify(this, this.getJsonKeys());
    }
}



class Foo extends Stringifyiable {
    private _id: number;
    private _desc: string;
    private _user: Bar;

    constructor(id: number, desc: string, user: Bar) {
        super();
        this._id = id;
        this._desc = desc;
        this._user = user;
    }

    public get id(): number {
        return this._id;
    } 

    public set id(value: number) {
        this._id = value;
    }

    public get desc():string {
        return this._desc;
    } 

    public set desc(value: string) {
        this._desc = value;
    }

    public get user(): Bar {
        return this._user;
    } 

    public set user(value: Bar) {
        this._user = value;
    }
}



class Bar extends Stringifyiable {
    private _name: string;
    private _surname: string;

    constructor(name: string, surname: string) {
        super();
        this._name = name;
        this._surname = surname;
    }

    public get name(): string {
        return this._name;
    } 

    public set name(value: string) {
        this._name = value;
    }

    public get surname():string {
        return this._surname;
    } 

    public set surname(value: string) {
        this._surname = value;
    }
}

let foo = new Foo(1, 'something', new Bar('foo', 'bar'));
//this will output {"id":1,"desc":"something","user":{"name":"foo","surname":"bar"}}
foo.toJSONString();

Будьте осторожны с циклическими ссылками, потому что это войдет в бесконечный цикл (хотя я уверен, что это можно исправить).

...