【问题标题】:Typsescript object from json casting to type从 json 转换为类型的 Typescript 对象
【发布时间】:2018-07-14 22:47:36
【问题描述】:

在日常生活中,我需要从 ajax 读取一些 json 并将其转换为一些 Typed 对象(包括它的 METHODS)。在互联网上,我发现并使用以下代码进行类型转换:

export class Obj 
{
    public static cast<T>(obj, type: { new(...args): T} ): T 
    {
        obj.__proto__ = type.prototype;
        return obj;
    }
}

例如,它可以通过以下方式使用:

let objFromJson = { id: 666, name: "love" };
let building: Building = null; 
building = Obj.cast(objFromJson, Building);

// On this point constructor for Building is not call - this is
// correct because we not create object but only make type casting

building.test('xx');

// on this point on console we should get:
// > "building.test a:xx, name:love"

// so object 'building' indeed have methods of Building Class

哪里(例子来自'head')

export class Building {

    constructor(
        public id: number,
        public name: string,
    ) {
        console.log('building.constructor: id:' + id + ', name' + name);
    }

    public test(a) {
        console.log('building.test a:' + a + ', name:' + this.name);
    }

}

附加信息:我们可以只使用cast&lt;T&gt;(obj, type): T 而不是使用cast&lt;T&gt;(obj, type: { new(...args): T} ): T,但我读到第二个版本会导致箭头函数出现问题(https://stackoverflow.com/a/32186367/860099)-我不明白为什么-?

问题:我不太了解 Obj.cast 方法的工作原理(例如,我如何在调用它时使用 ...args) - 有人可以解释一下吗?有人知道替代函数,但不是用于强制转换,而是用于 CREATE 对象(所以调用构造函数)以类似方便的方式形成 json 数据(例如building = Obj.create(objFromJson, Building);

【问题讨论】:

    标签: json typescript casting


    【解决方案1】:

    cast 的工作原理

    Javascript 使用prototypical inheritance__proto__ 属性表示当前对象的原型。这决定了给定对象的类型

    对于使用对象字面量创建的对象,此值为 Object.prototype。对于使用数组字面量创建的对象,此值为 Array.prototype。对于函数,此值为 Function.prototype。对于使用 new fun 创建的对象,其中 fun 是 JavaScript 提供的内置构造函数之一(Array、Boolean、Date、Number、Object、String 等——包括随着 JavaScript 发展而添加的新构造函数),此值为总是很有趣的原型。 对于使用new fun 创建的对象,其中fun 是在脚本中定义的函数,该值就是fun.prototype 的值。

    因此,当您更改 __proto__ 时,您会更改对象的原型链,基本上会更改其类型。

    type: { new(...args): T} 它是您可以在 typescript 中表示构造函数的方式。正如上面的引用所说,对于由函数构造的对象(例如type),__ptoto__ 应该是函数的prototype

    所以在设置__proto__时,cast基本上模拟了对象是使用作为参数传递的type构造函数构造的事实。

    这种方法的问题

    问题在于构造函数实际上并没有被调用,您只是在模拟对象是使用给定构造函数创建的事实。因此,构造函数中发生的任何初始化都不会被执行。例如箭头函数需要捕获this,因此它们不是设置在构造函数的prototype上,而是在调用constructor期间设置在对象的实例上,所以如果构造函数没有被调用,箭头函数未初始化:

    export class Building {
        private otherField : string;
        constructor(
            public id: number,
            public name: string,
        ) {
            console.log('building.constructor: id:' + id + ', name' + name);
            this.otherField = name+id;
            // Ts adds in the code for initializing arrow functions in JS, but the idea is the same, this is where it would happen
        }
    
        public arrow = ()=> {};
        public test(a) {
            console.log('building.test a:' + a + ', name:' + this.name);
    
            // both fields below will be undefined if cast was used.
            console.log('building.otherField' + this.otherField + ', arrow:' + this.arrow);         }
    }
    

    替代cast

    另一种方法是创建类的新实例并使用Object.assign 分配json 对象的属性。乍一看这可能看起来很慢,但文档说更改 __ptoto__ 非常慢,不推荐

    根据现代 JavaScript 引擎优化属性访问的本质,在每个浏览器和 JavaScript 引擎中,更改对象的 [[Prototype]] 是一项非常缓慢的操作。

    export class Building {
        public id: number;
        public name: string;
        constructor(data: Partial<Building>){
            Object.assign(this, data)
            console.log('building.constructor: id:' + this.id + ', name' + this.name);
        }
    
        public test(a) {
            console.log('building.test a:' + a + ', name:' + this.name);
        }
    
    }
    let objFromJson = { id: 666, name: "love" };
    let building: Building = new Building(objFromJson);
    

    如果该类没有任何方法,也许您可​​以更改您的设计使其没有,那么我将只使用一个接口来键入 JSON 对象并继续使用原始 JSON 对象。

    【讨论】:

    • @Kamil Kiełczewski 添加了更多信息以考虑您在发布后添加的问题。如果某个地方的答案不清楚,请告诉我。
    • 感谢您的回答。 Alternative to cast - 不幸的是,你的提议并不方便 - 因为如果我使用它,我需要将 Object.assign(this, data) 添加到每个 model-class(如 Building、Floor 等)的构造函数(并更改其参数)。使用Obj.cast&lt;T&gt; 我不需要触摸任何“模型类”。
    • @KamilKiełczewski 您可以在构造函数之外使用Object.assign,并为参数传递 undefined(假设您不在构造函数中使用它们)。您仍然应该考虑构造函数版本。拥有这样一个构造函数是很常见的做法,new Building({ id: 10, name: "foo"})new Building(10, "foo") 更具可读性,尤其是在参数数量增加的情况下
    猜你喜欢
    • 1970-01-01
    • 2018-03-26
    • 1970-01-01
    • 2014-05-17
    • 2022-01-03
    相关资源
    最近更新 更多