Làm cách nào để tôi khởi tạo một đối tượng TypeScript bằng một đối tượng JSON


198

Tôi nhận được một đối tượng JSON từ một cuộc gọi AJAX đến máy chủ REST. Đối tượng này có tên thuộc tính khớp với lớp TypeScript của tôi (đây là phần tiếp theo của câu hỏi này ).

Cách tốt nhất để khởi tạo nó là gì? Tôi không nghĩ rằng điều này sẽ hoạt động vì lớp (& đối tượng JSON) có các thành viên là danh sách các đối tượng và thành viên là các lớp và các lớp đó có các thành viên là danh sách và / hoặc các lớp.

Nhưng tôi thích cách tiếp cận tìm kiếm tên thành viên và gán chúng, tạo danh sách và khởi tạo các lớp khi cần, vì vậy tôi không phải viết mã rõ ràng cho mọi thành viên trong mỗi lớp (có RẤT NHIỀU!)


1
Tại sao bạn lại hỏi điều này một lần nữa (vì câu trả lời tôi cung cấp trong câu hỏi khác nói rằng điều này sẽ không hoạt động và đó là về việc sao chép các thuộc tính vào một đối tượng hiện có)?
WiredPrairi


3
@WiredPrairi câu hỏi này là khác nhau, nó hỏi tôi có thể đi từng thuộc tính một và phân công chúng không. Các câu hỏi khác là hỏi tôi có thể bỏ nó không.
David Thielen

1
@WiredPrairi cont: Nếu bạn tiếp tục lặn vào các thuộc tính cho đến khi bạn chỉ nhận được các loại nguyên thủy, thì chúng có thể được chỉ định.
David Thielen

2
Nó vẫn sao chép tất cả các giá trị như tôi đề nghị bạn cần làm. Không có cách mới để làm điều này trong TypeScript vì đây là một thiết kế cơ bản của JavaScript. Đối với các đối tượng lớn, bạn có thể không muốn sao chép bất kỳ giá trị nào và chỉ "hành động" trên cấu trúc dữ liệu.
WiredPrairi

Câu trả lời:


188

Đây là một số bức ảnh nhanh ở đây để hiển thị một vài cách khác nhau. Chúng hoàn toàn không phải là "hoàn thành" và như một sự từ chối, tôi không nghĩ rằng đó là một ý tưởng tốt để làm điều đó như thế này. Ngoài ra, mã không quá sạch vì tôi chỉ cần gõ nó với nhau khá nhanh.

Cũng như một lưu ý: Tất nhiên các lớp khử lưu huỳnh cần phải có các hàm tạo mặc định như trường hợp của tất cả các ngôn ngữ khác, nơi tôi nhận thức được việc khử lưu huỳnh dưới bất kỳ hình thức nào. Tất nhiên, Javascript sẽ không phàn nàn nếu bạn gọi một hàm tạo không mặc định, không có đối số, nhưng lớp tốt hơn nên được chuẩn bị cho nó sau đó (cộng với, nó sẽ không thực sự là "cách đánh máy").

Tùy chọn # 1: Không có thông tin về thời gian chạy

Vấn đề với cách tiếp cận này chủ yếu là tên của bất kỳ thành viên nào phải phù hợp với lớp của nó. Điều này tự động giới hạn bạn với một thành viên cùng loại trên mỗi lớp và phá vỡ một số quy tắc thực hành tốt. Tôi thực sự khuyên bạn nên chống lại điều này, nhưng chỉ liệt kê nó ở đây vì đó là "bản nháp" đầu tiên khi tôi viết câu trả lời này (đó cũng là lý do tại sao các tên là "Foo", v.v.).

module Environment {
    export class Sub {
        id: number;
    }

    export class Foo {
        baz: number;
        Sub: Sub;
    }
}

function deserialize(json, environment, clazz) {
    var instance = new clazz();
    for(var prop in json) {
        if(!json.hasOwnProperty(prop)) {
            continue;
        }

        if(typeof json[prop] === 'object') {
            instance[prop] = deserialize(json[prop], environment, environment[prop]);
        } else {
            instance[prop] = json[prop];
        }
    }

    return instance;
}

var json = {
    baz: 42,
    Sub: {
        id: 1337
    }
};

var instance = deserialize(json, Environment, Environment.Foo);
console.log(instance);

Tùy chọn # 2: Thuộc tính tên

Để loại bỏ vấn đề trong tùy chọn # 1, chúng ta cần có một số loại thông tin về loại nút trong đối tượng JSON. Vấn đề là trong Bản mô tả, những thứ này là các cấu trúc thời gian biên dịch và chúng ta cần chúng trong thời gian chạy - nhưng các đối tượng thời gian chạy đơn giản là không có nhận thức về các thuộc tính của chúng cho đến khi chúng được đặt.

Một cách để làm điều đó là bằng cách làm cho các lớp nhận biết tên của họ. Bạn cũng cần thuộc tính này trong JSON. Trên thực tế, bạn chỉ cần nó trong json:

module Environment {
    export class Member {
        private __name__ = "Member";
        id: number;
    }

    export class ExampleClass {
        private __name__ = "ExampleClass";

        mainId: number;
        firstMember: Member;
        secondMember: Member;
    }
}

function deserialize(json, environment) {
    var instance = new environment[json.__name__]();
    for(var prop in json) {
        if(!json.hasOwnProperty(prop)) {
            continue;
        }

        if(typeof json[prop] === 'object') {
            instance[prop] = deserialize(json[prop], environment);
        } else {
            instance[prop] = json[prop];
        }
    }

    return instance;
}

var json = {
    __name__: "ExampleClass",
    mainId: 42,
    firstMember: {
        __name__: "Member",
        id: 1337
    },
    secondMember: {
        __name__: "Member",
        id: -1
    }
};

var instance = deserialize(json, Environment);
console.log(instance);

Tùy chọn # 3: Rõ ràng nêu rõ các loại thành viên

Như đã nêu ở trên, thông tin loại của các thành viên lớp không có sẵn trong thời gian chạy - đó là trừ khi chúng tôi cung cấp thông tin đó. Chúng tôi chỉ cần làm điều này cho các thành viên không nguyên thủy và chúng tôi rất tốt để đi:

interface Deserializable {
    getTypes(): Object;
}

class Member implements Deserializable {
    id: number;

    getTypes() {
        // since the only member, id, is primitive, we don't need to
        // return anything here
        return {};
    }
}

class ExampleClass implements Deserializable {
    mainId: number;
    firstMember: Member;
    secondMember: Member;

    getTypes() {
        return {
            // this is the duplication so that we have
            // run-time type information :/
            firstMember: Member,
            secondMember: Member
        };
    }
}

function deserialize(json, clazz) {
    var instance = new clazz(),
        types = instance.getTypes();

    for(var prop in json) {
        if(!json.hasOwnProperty(prop)) {
            continue;
        }

        if(typeof json[prop] === 'object') {
            instance[prop] = deserialize(json[prop], types[prop]);
        } else {
            instance[prop] = json[prop];
        }
    }

    return instance;
}

var json = {
    mainId: 42,
    firstMember: {
        id: 1337
    },
    secondMember: {
        id: -1
    }
};

var instance = deserialize(json, ExampleClass);
console.log(instance);

Tùy chọn # 4: Cách dài dòng, nhưng gọn gàng

Cập nhật 01/03/2016: Như @GameAlchemist đã chỉ ra trong các ý kiến ​​( ý tưởng , triển khai ), kể từ Bản mô tả 1.7, giải pháp được mô tả dưới đây có thể được viết theo cách tốt hơn bằng cách sử dụng trang trí lớp / thuộc tính.

Tuần tự hóa luôn là một vấn đề và theo tôi, cách tốt nhất là cách không ngắn nhất. Trong số tất cả các tùy chọn, đây là điều tôi thích vì tác giả của lớp có toàn quyền kiểm soát trạng thái của các đối tượng được khử lưu huỳnh. Nếu tôi phải đoán, tôi sẽ nói rằng tất cả các tùy chọn khác, sớm hay muộn, sẽ khiến bạn gặp rắc rối (trừ khi Javascript đưa ra một cách riêng để xử lý vấn đề này).

Thực sự, ví dụ sau đây không làm công lý linh hoạt. Nó thực sự chỉ sao chép cấu trúc của lớp. Tuy nhiên, điểm khác biệt bạn phải ghi nhớ ở đây là lớp có toàn quyền kiểm soát để sử dụng bất kỳ loại JSON nào mà nó muốn kiểm soát trạng thái của toàn bộ lớp (bạn có thể tính toán mọi thứ, v.v.).

interface Serializable<T> {
    deserialize(input: Object): T;
}

class Member implements Serializable<Member> {
    id: number;

    deserialize(input) {
        this.id = input.id;
        return this;
    }
}

class ExampleClass implements Serializable<ExampleClass> {
    mainId: number;
    firstMember: Member;
    secondMember: Member;

    deserialize(input) {
        this.mainId = input.mainId;

        this.firstMember = new Member().deserialize(input.firstMember);
        this.secondMember = new Member().deserialize(input.secondMember);

        return this;
    }
}

var json = {
    mainId: 42,
    firstMember: {
        id: 1337
    },
    secondMember: {
        id: -1
    }
};

var instance = new ExampleClass().deserialize(json);
console.log(instance);

12
Lựa chọn số 4 là cách tôi muốn gọi một cách hợp lý. Bạn vẫn cần phải viết mã khử lưu huỳnh, nhưng nó nằm trong cùng một lớp và hoàn toàn có thể kiểm soát được. Nếu bạn đến từ Java, thì điều này có thể so sánh với việc phải viết equalshoặc toStringphương thức (chỉ có điều bạn thường tự động tạo chúng). Không quá khó để viết một trình tạo deserializenếu bạn muốn, nhưng nó không thể tự động hóa thời gian chạy.
Ingo Bürk

2
@ IngoBürk, tôi biết tôi sẽ hỏi câu hỏi này 2 năm sau nhưng nó sẽ hoạt động như thế nào trên mảng các đối tượng? Mã mẫu ở trên hoạt động tốt cho đối tượng JSON. Làm thế nào nó có thể được sử dụng cho mảng các đối tượng?
Pratik Gaikwad

2
Một nhận xét bên lề: kể từ 1.7, (thừa nhận gần đây hơn câu trả lời của bạn), bản thảo cung cấp các trình trang trí lớp / thuộc tính cho phép viết giải pháp thứ 4 theo cách gọn gàng hơn.
Nhà hóa học trò chơi

1
Tài liệu tốt nhất tôi tìm thấy là câu trả lời StackOverflow: stackoverflow.com/a/29837695/856501 . Tôi đã sử dụng trang trí trong một dự án của tôi, và mặc dù tôi muốn một vài tính năng khác tôi phải nói rằng chúng hoạt động như một bùa mê.
GameAlchemist 23/2/2016

2
Tôi sẽ không nhảy vào trang trí cho một dự án sản xuất - hãy nhớ rằng chúng vẫn là một tính năng thử nghiệm. Tôi sẽ không dựa vào mã trong thế giới thực về "thử nghiệm" bởi vì theo như chúng tôi lo ngại thì chúng có thể biến mất trong phiên bản tiếp theo và bạn sẽ phải viết lại một loạt mã hoặc bị kẹt mãi mãi trên phiên bản TS cũ. Chỉ cần $ 0,02 của tôi
RVP

35

bạn có thể sử dụng Object.assignTôi không biết khi nào nó được thêm vào, tôi hiện đang sử dụng Bản mô tả 2.0.2 và đây có vẻ là một tính năng ES6.

client.fetch( '' ).then( response => {
        return response.json();
    } ).then( json => {
        let hal : HalJson = Object.assign( new HalJson(), json );
        log.debug( "json", hal );

đây HalJson

export class HalJson {
    _links: HalLinks;
}

export class HalLinks implements Links {
}

export interface Links {
    readonly [text: string]: Link;
}

export interface Link {
    readonly href: URL;
}

Đây là những gì chrome nói

HalJson {_links: Object}
_links
:
Object
public
:
Object
href
:
"http://localhost:9000/v0/public

vì vậy bạn có thể thấy nó không thực hiện chuyển nhượng đệ quy


2
vì vậy, về cơ bản, đây là : Object.assign. Tại sao chúng ta có hai câu trả lời giống như từ vựng ở trên câu này?
phil294

18
@Blauhim Bởi vì Object.assignsẽ không hoạt động đệ quy và không khởi tạo các loại đối tượng chính xác, để lại các giá trị làm Objectví dụ. Mặc dù nó là tốt cho các nhiệm vụ tầm thường, việc tuần tự hóa loại phức tạp là không thể với nó. Ví dụ: nếu thuộc tính lớp thuộc loại lớp tùy chỉnh, JSON.parse+ Object.assignsẽ khởi tạo thuộc tính đó thành Object. Tác dụng phụ bao gồm các phương pháp và phụ kiện bị thiếu.
John Weisz

@Johnweisz lớp phân công đối tượng cấp cao nhất có loại chính xác và tôi đã đề cập đến điều đệ quy trong điều này ... có nghĩa là, YMMV, và những thứ đó có thể là bộ ngắt thỏa thuận.
xenoterracide

Trích dẫn trực tiếp từ câu hỏi: "lớp có thành viên là danh sách các đối tượng và thành viên là lớp và những lớp đó có thành viên là danh sách và / hoặc lớp [...] Tôi thích cách tiếp cận tìm kiếm thành viên đặt tên và gán chúng cho nhau, tạo danh sách và khởi tạo các lớp khi cần thiết, vì vậy tôi không phải viết mã rõ ràng cho mọi thành viên trong mỗi lớp " - đó không phải là trường hợp Object.assign, khi nó vẫn chuyển sang viết tức thời lồng nhau tay. Cách tiếp cận này tốt cho các đối tượng mức độ hướng dẫn rất đơn giản, nhưng không sử dụng thực sự.
John Weisz

@Johnweisz chắc chắn, chủ yếu trả lời với điều này bởi vì nó không có trong bất kỳ câu trả lời nào và có vẻ đơn giản đối với một số trường hợp sử dụng. Tôi chắc chắn rằng nó cũng có thể được sử dụng kết hợp với các câu trả lời khác như phản xạ, để làm những gì bạn đang tìm kiếm. Tôi cũng đã viết nó một phần để tôi sẽ nhớ nó sau. Nhìn vào những câu trả lời này và đã sử dụng và viết các thư viện mạnh mẽ hơn nhiều, dường như không có bất cứ thứ gì có sẵn cho "sử dụng thực sự".
xenoterracide

34

TLDR: TypedJSON (bằng chứng làm việc của khái niệm)


Nguồn gốc của sự phức tạp của vấn đề này là chúng ta cần giải tuần tự hóa JSON trong thời gian chạy bằng cách sử dụng thông tin kiểu chỉ tồn tại ở thời gian biên dịch . Điều này đòi hỏi thông tin loại là bằng cách nào đó có sẵn trong thời gian chạy.

May mắn thay, điều này có thể được giải quyết một cách rất thanh lịch và mạnh mẽ với các nhà trang tríReflectDecorators :

  1. Sử dụng các trình trang trí thuộc tính trên các thuộc tính có thể tuần tự hóa, để ghi thông tin siêu dữ liệu và lưu trữ thông tin đó ở đâu đó, ví dụ như trên nguyên mẫu lớp
  2. Cung cấp thông tin siêu dữ liệu này cho trình khởi tạo đệ quy (trình giải nén)

 

Ghi thông tin loại

Với sự kết hợp của ReflectDecorators và trang trí thuộc tính, thông tin loại có thể dễ dàng được ghi lại về một tài sản. Một triển khai thô sơ của phương pháp này sẽ là:

function JsonMember(target: any, propertyKey: string) {
    var metadataFieldKey = "__propertyTypes__";

    // Get the already recorded type-information from target, or create
    // empty object if this is the first property.
    var propertyTypes = target[metadataFieldKey] || (target[metadataFieldKey] = {});

    // Get the constructor reference of the current property.
    // This is provided by TypeScript, built-in (make sure to enable emit
    // decorator metadata).
    propertyTypes[propertyKey] = Reflect.getMetadata("design:type", target, propertyKey);
}

Đối với bất kỳ thuộc tính đã cho nào, đoạn mã trên sẽ thêm tham chiếu hàm xây dựng của thuộc tính vào thuộc tính ẩn __propertyTypes__trên nguyên mẫu lớp. Ví dụ:

class Language {
    @JsonMember // String
    name: string;

    @JsonMember// Number
    level: number;
}

class Person {
    @JsonMember // String
    name: string;

    @JsonMember// Language
    language: Language;
}

Và đó là nó, chúng ta có loại thông tin cần thiết trong thời gian chạy, hiện có thể được xử lý.

 

Xử lý thông tin loại

Trước tiên chúng ta cần có được một Objectcá thể bằng cách sử dụng JSON.parse- sau đó, chúng ta có thể lặp lại các mục nhập trong __propertyTypes__(được thu thập ở trên) và khởi tạo các thuộc tính cần thiết cho phù hợp. Loại của đối tượng gốc phải được chỉ định, để trình giải nén có điểm bắt đầu.

Một lần nữa, một cách thực hiện đơn giản đã chết của phương pháp này sẽ là:

function deserialize<T>(jsonObject: any, Constructor: { new (): T }): T {
    if (!Constructor || !Constructor.prototype.__propertyTypes__ || !jsonObject || typeof jsonObject !== "object") {
        // No root-type with usable type-information is available.
        return jsonObject;
    }

    // Create an instance of root-type.
    var instance: any = new Constructor();

    // For each property marked with @JsonMember, do...
    Object.keys(Constructor.prototype.__propertyTypes__).forEach(propertyKey => {
        var PropertyType = Constructor.prototype.__propertyTypes__[propertyKey];

        // Deserialize recursively, treat property type as root-type.
        instance[propertyKey] = deserialize(jsonObject[propertyKey], PropertyType);
    });

    return instance;
}
var json = '{ "name": "John Doe", "language": { "name": "en", "level": 5 } }';
var person: Person = deserialize(JSON.parse(json), Person);

Ý tưởng trên có một lợi thế lớn là giải tuần tự hóa bằng các loại dự kiến (đối với các giá trị phức tạp / đối tượng), thay vì những gì có trong JSON. Nếu a Personđược mong đợi, thì đó là một Personthể hiện được tạo ra. Với một số biện pháp bảo mật bổ sung được áp dụng cho các kiểu và mảng nguyên thủy, cách tiếp cận này có thể được bảo mật, chống lại bất kỳ JSON độc hại nào .

 

Vỏ cạnh

Tuy nhiên, nếu bây giờ bạn hài lòng rằng giải pháp đơn giản như vậy , tôi có một số tin xấu: có một số lượng lớn các trường hợp cạnh cần được quan tâm. Chỉ một số trong đó là:

  • Mảng và các phần tử mảng (đặc biệt là trong các mảng lồng nhau)
  • Đa hình
  • Các lớp và giao diện trừu tượng
  • ...

Nếu bạn không muốn loay hoay với tất cả những điều này (tôi cá là bạn không), tôi rất vui khi giới thiệu phiên bản thử nghiệm hoạt động của một khái niệm bằng chứng sử dụng phương pháp này, TypedJSON - mà tôi đã tạo để giải quyết vấn đề chính xác này, một vấn đề tôi phải đối mặt hàng ngày.

Do cách các nhà trang trí vẫn đang được coi là thử nghiệm, tôi không khuyên bạn nên sử dụng nó cho sử dụng sản xuất, nhưng cho đến nay nó vẫn phục vụ tốt cho tôi.


TypedJSON đã làm việc tuyệt vời; cảm ơn rất nhiều vì đã tham khảo
Neil

Công việc tuyệt vời, bạn đã đưa ra một giải pháp rất thanh lịch cho một vấn đề đã gây phiền toái cho tôi trong một thời gian. Tôi sẽ theo dõi dự án của bạn rất chặt chẽ!
John Strickler

12

Tôi đã sử dụng anh chàng này để thực hiện công việc: https://github.com/weichx/cerialize

Nó rất đơn giản nhưng mạnh mẽ. Nó hỗ trợ:

  • Tuần tự hóa và giải tuần tự hóa toàn bộ một cây các đối tượng.
  • Thuộc tính bền bỉ & thoáng qua trên cùng một đối tượng.
  • Móc để tùy chỉnh logic tuần tự hóa (de).
  • Nó có thể (de) tuần tự hóa thành một thể hiện hiện có (tuyệt vời cho Angular) hoặc tạo các thể hiện mới.
  • Vân vân.

Thí dụ:

class Tree {
  @deserialize public species : string; 
  @deserializeAs(Leaf) public leafs : Array<Leaf>;  //arrays do not need extra specifications, just a type.
  @deserializeAs(Bark, 'barkType') public bark : Bark;  //using custom type and custom key name
  @deserializeIndexable(Leaf) public leafMap : {[idx : string] : Leaf}; //use an object as a map
}

class Leaf {
  @deserialize public color : string;
  @deserialize public blooming : boolean;
  @deserializeAs(Date) public bloomedAt : Date;
}

class Bark {
  @deserialize roughness : number;
}

var json = {
  species: 'Oak',
  barkType: { roughness: 1 },
  leafs: [ {color: 'red', blooming: false, bloomedAt: 'Mon Dec 07 2015 11:48:20 GMT-0500 (EST)' } ],
  leafMap: { type1: { some leaf data }, type2: { some leaf data } }
}
var tree: Tree = Deserialize(json, Tree);

6

Tôi đã tạo một công cụ tạo giao diện TypeScript và "kiểu bản đồ" thời gian chạy để thực hiện đánh máy thời gian chạy so với kết quả của JSON.parse: ts.quicktype.io

Ví dụ, đưa ra JSON này:

{
  "name": "David",
  "pets": [
    {
      "name": "Smoochie",
      "species": "rhino"
    }
  ]
}

quicktype tạo ra giao diện TypeScript và bản đồ loại sau:

export interface Person {
    name: string;
    pets: Pet[];
}

export interface Pet {
    name:    string;
    species: string;
}

const typeMap: any = {
    Person: {
        name: "string",
        pets: array(object("Pet")),
    },
    Pet: {
        name: "string",
        species: "string",
    },
};

Sau đó, chúng tôi kiểm tra kết quả của JSON.parsebản đồ loại:

export function fromJson(json: string): Person {
    return cast(JSON.parse(json), object("Person"));
}

Tôi đã bỏ qua một số mã, nhưng bạn có thể thử quicktype để biết chi tiết.


1
Sau khi thực hiện nhiều giờ nghiên cứu và thử sức mình với một vài kỹ thuật phân tích cú pháp, tôi có thể nói đây là một giải pháp tuyệt vời - chủ yếu là vì các nhà trang trí vẫn đang thử nghiệm. * Liên kết ban đầu bị hỏng đối với tôi; nhưng ts.quicktype.io hoạt động. * Chuyển đổi Lược đồ JSON sang JSON là bước đầu tiên tốt.
LexieHankins

3

Tùy chọn # 5: Sử dụng các hàm tạo typecript và jQuery.extend

Đây có vẻ là phương thức dễ bảo trì nhất: thêm một hàm tạo lấy tham số cấu trúc json và mở rộng đối tượng json. Bằng cách đó bạn có thể phân tích cấu trúc json thành toàn bộ mô hình ứng dụng.

Không cần tạo giao diện hoặc liệt kê các thuộc tính trong hàm tạo.

export class Company
{
    Employees : Employee[];

    constructor( jsonData: any )
    {
        jQuery.extend( this, jsonData);

        // apply the same principle to linked objects:
        if ( jsonData.Employees )
            this.Employees = jQuery.map( jsonData.Employees , (emp) => {
                return new Employee ( emp );  });
    }

    calculateSalaries() : void { .... }
}

export class Employee
{
    name: string;
    salary: number;
    city: string;

    constructor( jsonData: any )
    {
        jQuery.extend( this, jsonData);

        // case where your object's property does not match the json's:
        this.city = jsonData.town;
    }
}

Trong cuộc gọi lại ajax của bạn, nơi bạn nhận được một công ty để tính lương:

onReceiveCompany( jsonCompany : any ) 
{
   let newCompany = new Company( jsonCompany );

   // call the methods on your newCompany object ...
   newCompany.calculateSalaries()
}

từ đâu $.extendđến
whale_steward

@whale_steward Tôi cho rằng tác giả đang đề cập đến thư viện jQuery. Trong thế giới JavaScript, '$' rất thường xuyên có người sử dụng jQuery.
Nick Roth

Làm thế nào để nhập khẩu? Chỉ cần đưa nó vào đầu html là đủ?
whale_steward

có, tôi cập nhật câu trả lời để thay thế $ bằng jQuery. nhập jQuery.js vào phần đầu html và cài đặt và thêm @ type / jquery trong phần pack.json, devDependencies của bạn.
Anthony Brenelière

1
Lưu ý rằng trong Javascript, bạn nên làm Object.assign, loại bỏ sự phụ thuộc này vào jQuery.
Léon Pelletier

2

Đối với các đối tượng đơn giản, tôi thích phương pháp này:

class Person {
  constructor(
    public id: String, 
    public name: String, 
    public title: String) {};

  static deserialize(input:any): Person {
    return new Person(input.id, input.name, input.title);
  }
}

var person = Person.deserialize({id: 'P123', name: 'Bob', title: 'Mr'});

Tận dụng khả năng xác định các thuộc tính trong hàm tạo cho phép nó ngắn gọn.

Điều này giúp bạn có một đối tượng được nhập (so với tất cả các câu trả lời sử dụng Object.assign hoặc một số biến thể, cung cấp cho bạn một Đối tượng) và không yêu cầu các thư viện hoặc trang trí bên ngoài.


1

Tùy chọn thứ 4 được mô tả ở trên là một cách đơn giản và hay để thực hiện, nó phải được kết hợp với tùy chọn thứ 2 trong trường hợp bạn phải xử lý một hệ thống phân cấp lớp như ví dụ một danh sách thành viên là bất kỳ sự xuất hiện nào của các lớp con của một siêu thành viên, ví dụ: Giám đốc mở rộng Thành viên hoặc Sinh viên mở rộng Thành viên. Trong trường hợp đó, bạn phải cung cấp loại lớp con ở định dạng json


1

JQuery .extend thực hiện điều này cho bạn:

var mytsobject = new mytsobject();

var newObj = {a:1,b:2};

$.extend(mytsobject, newObj); //mytsobject will now contain a & b

0

Có thể không thực tế, nhưng giải pháp đơn giản:

interface Bar{
x:number;
y?:string; 
}

var baz:Bar = JSON.parse(jsonString);
alert(baz.y);

làm việc cho những người phụ thuộc khó khăn quá !!!


9
Cách tiếp cận này không thực sự hoạt động như mong đợi. Nếu bạn kiểm tra kết quả thời gian chạy, bazsẽ thuộc loại Objectchứ không phải loại Bar.Nó hoạt động trong trường hợp đơn giản này vì Barkhông có phương thức (chỉ thuộc tính nguyên thủy). Nếu Barcó một phương thức như thế isEnabled(), cách tiếp cận này sẽ thất bại vì phương thức đó sẽ không nằm trong chuỗi JSON được tuần tự hóa.
Todd

0

Một lựa chọn khác sử dụng các nhà máy

export class A {

    id: number;

    date: Date;

    bId: number;
    readonly b: B;
}

export class B {

    id: number;
}

export class AFactory {

    constructor(
        private readonly createB: BFactory
    ) { }

    create(data: any): A {

        const createB = this.createB.create;

        return Object.assign(new A(),
            data,
            {
                get b(): B {

                    return createB({ id: data.bId });
                },
                date: new Date(data.date)
            });
    }
}

export class BFactory {

    create(data: any): B {

        return Object.assign(new B(), data);
    }
}

https://github.com/MrAntix/ts-deserialize

sử dụng như thế này

import { A, B, AFactory, BFactory } from "./deserialize";

// create a factory, simplified by DI
const aFactory = new AFactory(new BFactory());

// get an anon js object like you'd get from the http call
const data = { bId: 1, date: '2017-1-1' };

// create a real model from the anon js object
const a = aFactory.create(data);

// confirm instances e.g. dates are Dates 
console.log('a.date is instanceof Date', a.date instanceof Date);
console.log('a.b is instanceof B', a.b instanceof B);
  1. giữ cho lớp học của bạn đơn giản
  2. tiêm có sẵn cho các nhà máy cho linh hoạt

0

tốt nhất tôi tìm thấy cho mục đích này là biến áp lớp. github.com/typestack/ class-transformer

Đó là cách bạn sử dụng nó:

Một số lớp học:

export class Foo {

    name: string;

    @Type(() => Bar)
    bar: Bar;

    public someFunction = (test: string): boolean => {
        ...
    }
}


import { plainToClass } from 'class-transformer';

export class SomeService {

  anyFunction() {
u = plainToClass(Foo, JSONobj);
 }

Nếu bạn sử dụng các thuộc tính lồng nhau @Type decorator cũng sẽ được tạo.


0

Cá nhân tôi thích tùy chọn số 3 của @Ingo Bürk. Và tôi đã cải thiện mã của anh ấy để hỗ trợ một mảng dữ liệu phức tạp và Mảng dữ liệu nguyên thủy.

interface IDeserializable {
  getTypes(): Object;
}

class Utility {
  static deserializeJson<T>(jsonObj: object, classType: any): T {
    let instanceObj = new classType();
    let types: IDeserializable;
    if (instanceObj && instanceObj.getTypes) {
      types = instanceObj.getTypes();
    }

    for (var prop in jsonObj) {
      if (!(prop in instanceObj)) {
        continue;
      }

      let jsonProp = jsonObj[prop];
      if (this.isObject(jsonProp)) {
        instanceObj[prop] =
          types && types[prop]
            ? this.deserializeJson(jsonProp, types[prop])
            : jsonProp;
      } else if (this.isArray(jsonProp)) {
        instanceObj[prop] = [];
        for (let index = 0; index < jsonProp.length; index++) {
          const elem = jsonProp[index];
          if (this.isObject(elem) && types && types[prop]) {
            instanceObj[prop].push(this.deserializeJson(elem, types[prop]));
          } else {
            instanceObj[prop].push(elem);
          }
        }
      } else {
        instanceObj[prop] = jsonProp;
      }
    }

    return instanceObj;
  }

  //#region ### get types ###
  /**
   * check type of value be string
   * @param {*} value
   */
  static isString(value: any) {
    return typeof value === "string" || value instanceof String;
  }

  /**
   * check type of value be array
   * @param {*} value
   */
  static isNumber(value: any) {
    return typeof value === "number" && isFinite(value);
  }

  /**
   * check type of value be array
   * @param {*} value
   */
  static isArray(value: any) {
    return value && typeof value === "object" && value.constructor === Array;
  }

  /**
   * check type of value be object
   * @param {*} value
   */
  static isObject(value: any) {
    return value && typeof value === "object" && value.constructor === Object;
  }

  /**
   * check type of value be boolean
   * @param {*} value
   */
  static isBoolean(value: any) {
    return typeof value === "boolean";
  }
  //#endregion
}

// #region ### Models ###
class Hotel implements IDeserializable {
  id: number = 0;
  name: string = "";
  address: string = "";
  city: City = new City(); // complex data
  roomTypes: Array<RoomType> = []; // array of complex data
  facilities: Array<string> = []; // array of primitive data

  // getter example
  get nameAndAddress() {
    return `${this.name} ${this.address}`;
  }

  // function example
  checkRoom() {
    return true;
  }

  // this function will be use for getting run-time type information
  getTypes() {
    return {
      city: City,
      roomTypes: RoomType
    };
  }
}

class RoomType implements IDeserializable {
  id: number = 0;
  name: string = "";
  roomPrices: Array<RoomPrice> = [];

  // getter example
  get totalPrice() {
    return this.roomPrices.map(x => x.price).reduce((a, b) => a + b, 0);
  }

  getTypes() {
    return {
      roomPrices: RoomPrice
    };
  }
}

class RoomPrice {
  price: number = 0;
  date: string = "";
}

class City {
  id: number = 0;
  name: string = "";
}
// #endregion

// #region ### test code ###
var jsonObj = {
  id: 1,
  name: "hotel1",
  address: "address1",
  city: {
    id: 1,
    name: "city1"
  },
  roomTypes: [
    {
      id: 1,
      name: "single",
      roomPrices: [
        {
          price: 1000,
          date: "2020-02-20"
        },
        {
          price: 1500,
          date: "2020-02-21"
        }
      ]
    },
    {
      id: 2,
      name: "double",
      roomPrices: [
        {
          price: 2000,
          date: "2020-02-20"
        },
        {
          price: 2500,
          date: "2020-02-21"
        }
      ]
    }
  ],
  facilities: ["facility1", "facility2"]
};

var hotelInstance = Utility.deserializeJson<Hotel>(jsonObj, Hotel);

console.log(hotelInstance.city.name);
console.log(hotelInstance.nameAndAddress); // getter
console.log(hotelInstance.checkRoom()); // function
console.log(hotelInstance.roomTypes[0].totalPrice); // getter
// #endregion

-1

bạn có thể làm như dưới đây

export interface Instance {
  id?:string;
  name?:string;
  type:string;
}

var instance: Instance = <Instance>({
      id: null,
      name: '',
      type: ''
    });

Điều này sẽ không thực sự tạo ra một thể hiện thời gian chạy của loại đối tượng dự kiến ​​của bạn. Nó sẽ xuất hiện để hoạt động khi kiểu của bạn chỉ có các thuộc tính nguyên thủy, nhưng sẽ thất bại khi một kiểu có các phương thức. Định nghĩa giao diện cũng không có sẵn trong thời gian chạy (chỉ xây dựng thời gian).
Todd

-1
**model.ts**
export class Item {
    private key: JSON;
    constructor(jsonItem: any) {
        this.key = jsonItem;
    }
}

**service.ts**
import { Item } from '../model/items';

export class ItemService {
    items: Item;
    constructor() {
        this.items = new Item({
            'logo': 'Logo',
            'home': 'Home',
            'about': 'About',
            'contact': 'Contact',
        });
    }
    getItems(): Item {
        return this.items;
    }
}

gọi nội dung như ví dụ dưới đây:
user8390810

<a class="navbar-brand" href="#"> {{keyItems.key.logo}} </a>
user8390810

Điều này dường như không "[khởi tạo] các lớp khi cần thiết".
LexieHankins
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.