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))
);
}
}
Top comments (1)
It's 2023 and zod is extremely popular for type-checking JSON schemas