ค้นหาเว็บไซต์

ฉันจะแปลงวัตถุ JSON เป็นคลาส TypeScript ได้อย่างไร?


ฉันอ่านวัตถุ JSON จากเซิร์ฟเวอร์ REST ระยะไกล วัตถุ JSON นี้มีคุณสมบัติทั้งหมดของคลาส TypeScript (ตามการออกแบบ) ฉันจะแปลงวัตถุ JSON ที่ได้รับนี้ให้เป็นตัวแปรประเภทใดได้อย่างไร?

ฉันไม่อยากจะใส่ค่าลงในตัวแปร TypeScript (เช่น มี constructor ที่รับวัตถุ JSON นี้) มันใหญ่และการคัดลอกทุกอย่างทีละวัตถุย่อยและทีละสมบัติจะใช้เวลามาก

อัปเดต: อย่างไรก็ตาม คุณสามารถแปลงมันเป็นอินเทอร์เฟซของไทพ์สคริปต์ได้!

คำตอบ #1

คุณไม่สามารถเพียงแค่แปลงผลลัพธ์ของ JavaScript ปกติจากคำขอ Ajax ให้เป็นอินสแตนซ์ของคลาส JavaScript/TypeScript แบบต้นแบบได้ มีหลายเทคนิคสำหรับการทำเช่นนั้น และโดยทั่วไปเกี่ยวข้องกับการคัดลอกข้อมูล เว้นแต่ว่าคุณจะสร้างอินสแตนซ์ของคลาส มันจะไม่มีเมธอดหรือคุณสมบัติใด ๆ มันจะยังคงเป็นวัตถุ JavaScript ธรรมดาอยู่

ในขณะเดียวกัน ถ้าคุณจัดการแค่กับข้อมูล คุณสามารถทำการแปลงเป็นอินเทอร์เฟซได้ (เพราะมันเป็นโครงสร้างในเวลาคอมไพล์เท่านั้น) แต่สิ่งนี้จะต้องให้คุณใช้คลาส TypeScript ซึ่งใช้ตัวอย่างข้อมูลและทำการดำเนินการกับข้อมูลนั้น

ตัวอย่างบางส่วนของการคัดลอกข้อมูล:

  1. คัดลอกวัตถุ JSON ของ AJAX ลงในวัตถุที่มีอยู่
  2. แยกสตริง JSON เป็นโปรโตไทป์ของวัตถุเฉพาะใน JavaScript

โดยหลักการแล้ว คุณก็แค่ :

var d = new MyRichObject();
d.copyInto(jsonResult);

คำตอบ #2

ฉันมีปัญหาเดียวกันและฉันพบไลบรารีที่ทำงานนี้ได้: https://github.com/pleerock/class-transformer.

มันทำงานแบบนี้:

let jsonObject = response.json() as Object;
let fooInstance = plainToClass(Models.Foo, jsonObject);
return fooInstance;

มันรองรับลูกที่ซ้อนกันได้ แต่คุณต้องตกแต่งสมาชิกของคลาสของคุณ

คำตอบ #3

ใน TypeScript คุณสามารถทำการยืนยันประเภทโดยใช้ interface และ generics ดังนี้:

var json = Utilities.JSONLoader.loadFromFile("../docs/location_map.json");
var locations: Array<ILocationMap> = JSON.parse(json).location;

ที่ ILocationMap อธิบายรูปแบบของข้อมูลของคุณ ข้อดีของวิธีนี้คือ JSON ของคุณสามารถมีคุณสมบัติมากขึ้นได้ แต่รูปแบบนั้นยังคงเป็นไปตามเงื่อนไขของอินเทอร์เฟซ

อย่างไรก็ตาม สิ่งนี้ไม่ได้เพิ่มเมธอดของอินสแตนซ์ของคลาส

คำตอบ #4

ถ้าคุณกำลังใช้ ES6 ลองแบบนี้:

class Client{
  name: string

  displayName(){
    console.log(this.name)
  }
}

service.getClientFromAPI().then(clientData => {
  
  // Here the client data from API only have the "name" field
  // If we want to use the Client class methods on this data object we need to:
  let clientWithType = Object.assign(new Client(), clientData)

  clientWithType.displayName()
})

แต่วิธีนี้ จะใช้กับอ็อบเจ็กต์ที่ซ้อนกันไม่ได้ น่าเสียดาย

คำตอบ #5

ยังไม่มีอะไรที่จะตรวจสอบโดยอัตโนมัติว่าค่าที่คุณได้รับจากเซิร์ฟเวอร์ในรูปแบบ JSON มีคุณสมบัติตามอินเทอร์เฟซของ TypeScript ที่คาดหวัง (หรือเป็นไปตาม) แต่คุณสามารถใช้ User-Defined Type Guards ได้

พิจารณาอินเทอร์เฟซดังต่อไปนี้และวัตถุ JSON โง่ ๆ (มันอาจจะเป็นประเภทใดก็ได้):

interface MyInterface {
    key: string;
 }

const json: object = { "key": "value" }

สามวิธีที่เป็นไปได้:

A. การยืนยันชนิดข้อมูลหรือการแปลงแบบคงที่ง่าย ๆ ที่วางหลังตัวแปร

const myObject: MyInterface = json as MyInterface;

B. การแคสต์แบบสแตติกง่าย ๆ วางไว้หน้าตัวแปรและระหว่างเครื่องหมายเพชร

const myObject: MyInterface = <MyInterface>json;

C. การแปลงชนิดแบบไดนามิกขั้นสูง คุณตรวจสอบโครงสร้างของอ็อบเจ็กต์ด้วยตัวเอง

function isMyInterface(json: any): json is MyInterface {
    // silly condition to consider json as conform for MyInterface
    return typeof json.key === "string";
}

if (isMyInterface(json)) {
    console.log(json.key)
}
else {
        throw new Error(`Expected MyInterface, got '${json}'.`);
}

คุณสามารถเล่นกับตัวอย่างนี้ได้ที่นี่

โปรดทราบว่าความยากที่นี่คือการเขียนฟังก์ชัน isMyInterface ฉันหวังว่า TS จะเพิ่มตัวตกแต่ง (decorator) ในไม่ช้า เพื่อส่งออกการพิมพ์ที่ซับซ้อนไปยังเวลารันไทม์และให้เวลารันไทม์ตรวจสอบโครงสร้างของวัตถุเมื่อจำเป็น สำหรับตอนนี้ คุณสามารถใช้ตัวตรวจสอบสคีมา JSON ซึ่งจุดประสงค์โดยประมาณเดียวกัน หรือฟังก์ชันสร้างตัวตรวจสอบประเภทเวลารันไทม์นี้

คำตอบ #6

ฉันพบบทความที่น่าสนใจมากเกี่ยวกับการแปลง JSON เป็นคลาส Typescript แบบทั่วไป:

http://cloudmark.github.io/Json-Mapping/

คุณจะได้โค้ดดังต่อไปนี้:

let example = {
                "name": "Mark", 
                "surname": "Galea", 
                "age": 30, 
                "address": {
                  "first-line": "Some where", 
                  "second-line": "Over Here",
                  "city": "In This City"
                }
              };

MapUtils.deserialize(Person, example);  // custom class

คำตอบ #7

สรุปสั้น: หนึ่งบรรทัด

// This assumes your constructor method will assign properties from the arg.
.map((instanceData: MyClass) => new MyClass(instanceData));

คำตอบโดยละเอียด

ฉันจะ ไม่ แนะนำวิธี Object.assign เพราะอาจทำให้ instance ของคลาสของคุณเต็มไปด้วยคุณสมบัติที่ไม่เกี่ยวข้อง (รวมถึง closures ที่กำหนดไว้) ซึ่งไม่ได้ประกาศภายในคลาสเอง

ในคลาสที่คุณพยายามจะทำการ deserialize ฉันจะแน่ใจว่าคุณได้กำหนดคุณสมบัติใด ๆ ที่คุณต้องการให้ deserialize (เช่น เป็น null, อาร์เรย์ว่าง เป็นต้น) โดยการกำหนดคุณสมบัติของคุณด้วยค่าตั้งต้น คุณจะสามารถเปิดการมองเห็นของพวกมันเมื่อพยายามวนซ้ำสมาชิกของคลาสเพื่อกำหนดค่าให้ (ดูวิธี deserialize ด้านล่าง)

export class Person {
  public name: string = null;
  public favoriteSites: string[] = [];

  private age: number = null;
  private id: number = null;
  private active: boolean;

  constructor(instanceData?: Person) {
    if (instanceData) {
      this.deserialize(instanceData);
    }
  }

  private deserialize(instanceData: Person) {
    // Note this.active will not be listed in keys since it's declared, but not defined
    const keys = Object.keys(this);

    for (const key of keys) {
      if (instanceData.hasOwnProperty(key)) {
        this[key] = instanceData[key];
      }
    }
  }
}

ในตัวอย่างข้างต้น ฉันเพียงแค่สร้างเมธอด deserialize ขึ้นมา ในตัวอย่างในโลกจริง ฉันจะทำให้มันรวมศูนย์อยู่ในคลาสฐานหรือเมธอดบริการที่สามารถนำกลับมาใช้ใหม่ได้

นี่คือวิธีใช้สิ่งนี้ในบางสิ่งเช่นการตอบสนอง http...

this.http.get(ENDPOINT_URL)
  .map(res => res.json())
  .map((resp: Person) => new Person(resp) ) );

หาก tslint/ide แจ้งว่าประเภทของอาร์กิวเมนต์ไม่เข้ากัน ให้ทำการแปลงอาร์กิวเมนต์เป็นประเภทเดียวกันโดยใช้เครื่องหมายแหลม <YourClassName> ตัวอย่างเช่น:

const person = new Person(<Person> { name: 'John', age: 35, id: 1 });

หากคุณมีสมาชิกของคลาสที่เป็นประเภทเฉพาะ (เช่น: อินสแตนซ์ของคลาสอื่น) คุณสามารถให้พวกมันถูกแปลงเป็นอินสแตนซ์ที่มีประเภทผ่านเมธอด getter/setter ได้

export class Person {
  private _acct: UserAcct = null;
  private _tasks: Task[] = [];

  // ctor & deserialize methods...

  public get acct(): UserAcct {
    return this.acct;
  }
  public set acct(acctData: UserAcct) {
    this._acct = new UserAcct(acctData);
  }

  public get tasks(): Task[] {
    return this._tasks;
  }

  public set tasks(taskData: Task[]) {
    this._tasks = taskData.map(task => new Task(task));
  }
}

ตัวอย่างข้างต้นจะทำการแปรรูปข้อมูลทั้ง acct และรายการงานให้เป็นอินสแตนซ์ของคลาสที่สอดคล้องกัน

คำตอบ #8

สมมติว่า json มีคุณสมบัติเดียวกับคลาส TypeScript ของคุณ คุณไม่จำเป็นต้องคัดลอกคุณสมบัติของ Json ไปยังวัตถุ TypeScript ของคุณ คุณเพียงแค่ต้องสร้างวัตถุ TypeScript ของคุณโดยส่งข้อมูล json เข้าไปในตัวสร้าง

ในฟังก์ชัน callback ของ Ajax ของคุณ คุณจะได้รับบริษัทหนึ่ง:

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

   // call the methods on your newCompany object ...
}

เพื่อให้สิ่งนั้นได้ผล:

1) เพิ่มคอนสตรัคเตอร์ในคลาส Typescript ของคุณที่รับข้อมูล json เป็นพารามิเตอร์ ในคอนสตรัคเตอร์นั้นคุณต่อยอดอ็อบเจ็กต์ json ของคุณด้วย jQuery ดังนี้: $.extend( this, jsonData) $.extend ช่วยให้สามารถรักษาโปรโทไทป์ของจาวาสคริปต์ในขณะที่เพิ่มคุณสมบัติของอ็อบเจ็กต์ json ได้

2) โปรดทราบว่าคุณจะต้องทำเช่นเดียวกันสำหรับวัตถุที่เชื่อมโยง ในกรณีของพนักงานในตัวอย่างนี้ คุณยังต้องสร้างตัวสร้าง (constructor) ที่รับข้อมูลบางส่วนจาก json สำหรับพนักงาน จากนั้นคุณเรียกใช้ $.map เพื่อแปลง json ของพนักงานไปเป็นวัตถุ Employee ใน TypeScript

export class Company
{
    Employees : Employee[];

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

        if ( jsonData.Employees )
            this.Employees = $.map( jsonData.Employees , (emp) => {
                return new Employee ( emp );  });
    }
}

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

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

นี่คือวิธีแก้ปัญหาที่ดีที่สุดที่ฉันพบเมื่อจัดการกับคลาส Typescript และวัตถุ json

คำตอบ #9

ในกรณีของฉันมันใช้งานได้ ฉันได้ใช้ฟังก์ชัน Object.assign (target, sources ...) ก่อนอื่น สร้างอ็อบเจ็กต์ที่ถูกต้อง แล้วจึงคัดลอกข้อมูลจากอ็อบเจ็กต์ json ไปยัง target ตัวอย่าง:

let u:User = new User();
Object.assign(u , jsonUsers);

และตัวอย่างการใช้งานที่ซับซ้อนมากขึ้น ตัวอย่างการใช้งานที่ใช้แอเรย์

this.someService.getUsers().then((users: User[]) => {
  this.users = [];
  for (let i in users) {
    let u:User = new User();
    Object.assign(u , users[i]);
    this.users[i] = u;
    console.log("user:" + this.users[i].id);
    console.log("user id from function(test it work) :" + this.users[i].getId());
  }

});

export class User {
  id:number;
  name:string;
  fullname:string;
  email:string;

  public getId(){
    return this.id;
  }
}

บทความที่เกี่ยวข้อง