網站搜尋

javascript - 將具有 toJSON 方法的物件陣列序列化為 JSON 時出現奇怪的錯誤


我使用 NSwag 為 swagger API 端點生成 TypeScript 類型和類。生成的類中每個對象都包含一個 .toJSON() 方法,當使用 JSON.stringify() 將對象序列化為 JSON 時會被調用。

當序列化單個對象時一切正常,但當我嘗試序列化一個對象陣列時,它拋出了一個奇怪的錯誤:

angular.js:14199 TypeError: Cannot create property 'code' on string '0'
    at Dashboard.toJSON (App/models/api.js:785:34)
    at JSON.stringify (<anonymous>)

而觸發它的程式碼相當簡單:

console.log(JSON.stringify([
        Dashboard.fromJS({
            code: "1212312",
            name: "tresads",
            description: "some description"
        }),
        Dashboard.fromJS({
            code: "1212312",
            name: "tresads",
            description: "some description"
        })
    ]));

課程摘錄:

export class Dashboard implements IDashboard {
    code?: string | undefined;
    ...

    constructor(data?: IDashboard) {
        if (data) {
            for (var property in data) {
                if (data.hasOwnProperty(property))
                    (<any>this)[property] = (<any>data)[property];
            }
        }
    }

    init(data?: any) {
        if (data) {
            this.code = data["code"];
            ...
        }
    }

    static fromJS(data: any): Dashboard {
        let result = new Dashboard();
        result.init(data);
        return result;
    }

    toJSON(data?: any) {
        data = data ? data : {};
        data["code"] = this.code;
        ...
        return data; 
    }

    clone() {
        const json = this.toJSON();
        let result = new Dashboard();
        result.init(json);
        return result;
    }
}

有沒有想過為什麼 JSON.stringify() 會以 "0" 參數呼叫 toJSON() 方法?

答案#1

方法 toJSON 將會被呼叫並傳入一個參數,該參數是 this 被指派的屬性名稱。本質上,你關心的值不是那個參數,而是 this,它會綁定到你可以轉換的值。由於你用一個陣列呼叫 stringify,toJSON 將會對那個陣列的可列舉屬性被呼叫,也就是 0 和 1,而 this 將會是對應的 Dashboard 物件。

另外,我覺得你可以善用 Object.assign,它會將一個對象的屬性複製到另一個對象,這基本上就是你在建構函數的 for 迴圈中所做的事情。

所以這就是你可以做到的方法。我移除了 TypeScript 的裝飾器,改用純 JavaScript,但原理仍然相同:

class Dashboard {
    constructor(data) {
        // Object.assign does essentially what you want with the loop:
        Object.assign(this, data); 
    }

    init(data) {
        return Object.assign(this, data); 
    }

    static fromJS(data) {
        return new Dashboard(data);
    }

    toJSON(key) { 
        // `key` is the key/index of the property in the parent object.
        //    That probably is of no interest to you. You need `this`.
        // Extract properties into plain object, and return it for stringification
        return Object.assign({}, this);
    }

    clone() {
        return new Dashboard(this);
    }
}

console.log(JSON.stringify([
        Dashboard.fromJS({
            code: "1212312",
            name: "tresads",
            description: "some description"
        }),
        Dashboard.fromJS({
            code: "1212312",
            name: "tresads",
            description: "some description"
        })
    ]));

實際上,在給定的範例中,你根本不需要 toJSON,因為 Dashboard 實例的屬性是可列舉的,因此它們無論如何都會被轉換成字串。如果由於某些原因你確實需要它來進行某種轉換,那麼當然你仍然需要包括該轉換的邏輯,因為 Object.assign 只是簡單的複製。