loading...

Types for JSON data when Consuming APIs in Angular

rudolfolah profile image Rudolf Olah ・2 min read

It is neat that Angular can use TypeScript to type-check the JSON data from an API. What is less neat is that some APIs do not follow the camel-case naming convention for their field names.

Problem

For instance, if you have a Rails server providing an API and the response returns a JSON object with three fields: first_name, email_address and postal_code.

An example JSON response may look like this:

{
  "first_name": "Rudolf",
  "email_address": "rudolf@localhost",
  "postal_code": "A1B 2C3"
}

Within TypeScript you would have to make the interface look like this:

interface EntryForm {
  first_name: string;
  email_address: string;
  postal_code: string;
}

Which does not follow convention and if the field names change, that change will propagate throughout the code base.

Solution: the Adapter pattern and the adapt function

I found a solution to this problem in this blog post "Consuming APIs in Angular: The Model-Adapter Pattern". Essentially we can use the Adapter pattern to solve the problem:

An adapter allows two incompatible interfaces to work together. This is the real-world definition for an adapter. Interfaces may be incompatible, but the inner functionality should suit the need. The adapter design pattern allows otherwise incompatible classes to work together by converting the interface of one class into an interface expected by the clients.

Interface Definition

Using the above example, we can define a generic adapter that will map any JSON object to any other object class:

function adapt(mapper: any, json: any): any {
  let adaptedObj: any = {};
  const fields: Array<string> = Object.keys(mapper);
  for (let field of fields) {
    const targetField: any = mapper[field];
    adaptedObj[targetField] = json[field];
  }
  return adaptedObj;
}

We can then define a specific mapper:

function EntryFormAdapter(json: any): EntryForm {
  const mapper = {
      'first_name': 'firstName',
      'email_address': 'emailAddress',
      'postal_code': 'postalCode'
  };
  return adapt(mapper, json);
}

Preserving Type-Checking

There doesn't seem to be a good way to keep type-checking since we are diving into the land of any types with the above adapter.

If you really need type-checking, you can remove the adapt function, define an interface for the JSON object and then refactor the EntryFormAdapter:

interface JsonEntryForm {
  first_name: string;
  email_address: string;
  postal_code: string;
}

function EntryFormAdapter(json: JsonEntryForm): EntryForm {
  return {
    emailAddress: json.email_address,
    firstName: json.first_name,
    postalCode: json.postal_code
  }
}

This will ensure there is type-checking while you are working on the code.

Usage With HttpClient

To use this with Angular's HttpClient, you will need to pipe and map the JSON data response from the HTTP observable result into the adapter:

class MyService {
  constructor(private http: HttpClient) { }
  get(): Observable<EntryForm> {
    return this.http.get('/path/to/api/').pipe(
      map((json: JsonEntryForm) => EntryFormAdapter(json))
    );
  }
}

Posted on by:

rudolfolah profile

Rudolf Olah

@rudolfolah

Software and video course developer

Discussion

markdown guide