代码之家  ›  专栏  ›  技术社区  ›  dwally89

如何在Typescript中进行类型安全的web服务调用

  •  0
  • dwally89  · 技术社区  · 8 年前

    使用Angular进行web服务调用时,不会验证返回对象的类型。这可能意味着我有一个Typescript类:

    export class Course {
      constructor(
        public id: number,
        public name: string,
        public description: string,
        public startDate: Date
      ) {}
    }
    

    和数据服务类:

    import { Injectable } from '@angular/core';
    import { Headers, Http, RequestOptions } from '@angular/http';
    import { Observable } from 'rxjs/Observable';
    import 'rxjs/add/operator/map';
    
    @Injectable()
    export class DataService {
      constructor(private http: Http) { }
    
      public get<T>(url: string): Observable<T> {
        const headers = new Headers();
        headers.append('content-type', 'application/json');
        const options = new RequestOptions({ headers: headers, withCredentials: 
    true });
        return this.http.get(url, options)
          .map(response => response.json() as T);
      }
    }
    

    然后执行以下操作:

    import { Component } from '@angular/core';
    import { DataService } from './data.service';
    import { Course } from './course';
    
    
    @Component({
      selector: 'app-course',
      templateUrl: './course.component.html',
      styleUrls: ['./course.component.css']
    })
    export class CourseComponent {
      private courseId: number;
    
      constructor(private dataService: DataService) { }
    
      public getData() {
        this.dataService.get<Course>(`http://myapi/course/${this.courseId}`)
        .subscribe(
          course => this.course = course;
        );
      }
    }
    

    而且我不会得到编译错误,因为我的数据服务正确地返回了“Course”类型的对象。

    然而,如果我的API实际返回了以下JSON:

    {
        "uniqueId": 123,
        "name": "CS 101",
        "summary": "An introduction to Computer Science",
        "beginDate": "2018-04-20"
    }
    

    我不会得到编译时错误,只有在尝试对不存在的属性(id、summary、startDate)执行一些操作时才会得到运行时错误。这将从TypeScript中删除一些类型安全性。

    1 回复  |  直到 8 年前
        1
  •  1
  •   dwally89    8 年前

    解决方案

    我们可以通过如下修改数据服务来解决此问题:

    import { Injectable } from '@angular/core';
    import { Headers, Http, RequestOptions } from '@angular/http';
    import { Observable } from 'rxjs/Observable';
    import 'rxjs/add/operator/map';
    
    
    @Injectable()
    export class DataService {
      constructor(private http: Http) { }
    
      private verifyObjectWithTemplate(template: any,  obj: any, graph: string[]) {
        if (!template) {
          return;
        }
    
        const graphString = graph.join('.');
    
        Object
          .getOwnPropertyNames(template)
          .forEach(property => {
            if (!obj.hasOwnProperty(property)) {
              console.error(`Object is missing property: ${graphString}.${property}`);
            } else {
              const newGraph = graph.map(i => i);
              newGraph.push(property);
              this.verifyObjectWithTemplate(template[property], obj[property], newGraph);
            }
          });
      }
    
      public get<T>(url: string, template: T): Observable<T> {
        const headers = new Headers();
        headers.append('content-type', 'application/json');
        const options = new RequestOptions({ headers: headers, withCredentials: true });
        return this.http.get(url, options)
          .map(response => {
            const obj = response.json() as T;
            this.verifyObjectWithTemplate(template, obj, []);
            return obj;
          });
      }
    }
    

    向我们的课程类添加“模板”:

    export class Course {
      public static readonly Template = new Course(-1, '', '', new Date());
      constructor(
        public id: number,
        public name: string,
        public description: string,
        public startDate: Date
      ) {}
    }
    

    并修改我们的课程组件以将模板传递给数据服务:

    import { Component } from '@angular/core';
    import { DataService } from './data.service';
    import { Course } from './course';
    
    @Component({
      selector: 'app-course',
      templateUrl: './course.component.html',
      styleUrls: ['./course.component.css']
    })
    export class CourseComponent {
      private courseId: number;
    
      constructor(private dataService: DataService) { }
    
      public getData() {
        this.dataService.get<Course>(`http://myapi/course/${this.courseId}`, Course.Template)
        .subscribe(
          course => this.course = course;
        );
      }
    }
    

    然后,数据服务将验证API返回的JSON是否具有成为有效课程对象所需的所有属性。

    阵列呢? 如果我们的一个类包含一个数组,例如我们的学生类:

    import { Course } from './course';
    
    export class Student {
      public static readonly Template = new Student(-1, '', [Course.Template]);
      constructor(
        public id: number,
        public name: string,
        public courses: Course[]
      ) {}
    }
    

    在这种情况下,我们需要确保模板中的任何数组都包含一个项,这样也可以进行验证。我们还需要更新数据服务,如下所示:

    import { Injectable } from '@angular/core';
    import { Headers, Http, RequestOptions } from '@angular/http';
    import { Observable } from 'rxjs/Observable';
    import 'rxjs/add/operator/map';
    
    
    @Injectable()
    export class DataService {
      constructor(private http: Http) { }
    
      private verifyObjectWithTemplate(template: any,  obj: any, graph: string[]) {
        if (!template) {
          return;
        }
    
        const graphString = graph.join('.');
    
        // Important that we compare object to undefined, and not to null.
        // Property being null can be valid.
        if (obj === undefined) {
          console.error(`Object is missing property: ${graphString}`);
          return;
        }
    
        if (obj === null) {
            // No need to check rest of graph if object is null.
            return;
        }
    
        if (Array.isArray(template)) {
          if (!template[0]) {
            console.error(`Template array is empty: ${graphString}`);
            return;
          }
    
          if (!Array.isArray(obj)) {
            console.error(`Object is not an array: ${graphString}`);
            return;
          }
    
          if (!obj[0]) {
            console.log(`Object array is empty so can't be verified: ${graphString}`);
            return;
          }
    
          template = template[0];
          obj = obj[0];
        }
    
        Object
          .getOwnPropertyNames(template)
          .forEach(property => {
            if (!obj.hasOwnProperty(property)) {
              console.error(`Object is missing property: ${graphString}.${property}`);
            } else {
              const newGraph = graph.map(i => i);
              newGraph.push(property);
              this.verifyObjectWithTemplate(template[property], obj[property], newGraph);
            }
          });
      }
    
      public get<T>(url: string, template: T): Observable<T> {
        const headers = new Headers();
        headers.append('content-type', 'application/json');
        const options = new RequestOptions({ headers: headers, withCredentials: 
    true });
        return this.http.get(url, options)
          .map(response => {
            const obj = response.json() as T;
            this.verifyObjectWithTemplate(template, obj, []);
            return obj;
          });
      }
    }
    

    现在,它应该能够处理所有类型的对象。

    实例 如果web服务返回JSON:

    {
        "uniqueId": 1,
        "name": "Daniel",
        "courses": [
            {
                "uniqueId": 123,
                "name": "CS 101",
                "summary": "An introduction to Computer Science",
                "beginDate": "2018-04-20"
            }
        ]
    }
    

    然后,我们将在控制台中看到以下消息: errors