DEV Community

Rudolf Olah
Rudolf Olah

Posted on

Types for JSON data when Consuming APIs in Angular

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"
}
Enter fullscreen mode Exit fullscreen mode

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

interface EntryForm {
  first_name: string;
  email_address: string;
  postal_code: string;
}
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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);
}
Enter fullscreen mode Exit fullscreen mode

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
  }
}
Enter fullscreen mode Exit fullscreen mode

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))
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

Top comments (1)

Collapse
 
rudolfolah profile image
Rudolf Olah

It's 2023 and zod is extremely popular for type-checking JSON schemas