Resolution
To address this issue, we need to make adjustments to our data service:
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 validateObjectWithTemplate(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.validateObjectWithTemplate(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.validateObjectWithTemplate(template, obj, []);
return obj;
});
}
}
We also need to introduce a "template" for our Course class:
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
) {}
}
Additionally, we should update our Course component to provide the template to the data service:
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 fetchData() {
this.dataService.get<Course>(`http://myapi/course/${this.courseId}`, Course.Template)
.subscribe(
course => this.course = course;
);
}
}
By following these steps, the data service will validate that the JSON response from the API meets the requirements to be considered a valid Course object.
What about arrays?
If our classes include arrays, such as in the case of our Student class:
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[]
) {}
}
In this scenario, we must ensure that any arrays in the template have at least one item for verification. The data service needs to be updated accordingly:
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 validateObjectWithTemplate(template: any, obj: any, graph: string[]) {
if (!template) {
return;
}
const graphString = graph.join('.');
if (obj === undefined) {
console.error(`Object is missing property: ${graphString}`);
return;
}
if (obj === 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 and cannot 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.validateObjectWithTemplate(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.validateObjectWithTemplate(template, obj, []);
return obj;
});
}
}
These adjustments enable the data service to handle all types of objects and arrays.
Example
If the API response contained the following JSON:
{
"uniqueId": 1,
"name": "Daniel",
"courses": [
{
"uniqueId": 123,
"name": "CS 101",
"summary": "An introduction to Computer Science",
"beginDate": "2018-04-20"
}
]
}
The console would display the following messages:
errors