In my TypeScript project, I am working with an API (specifically OData API) to retrieve data.
This API allows the selection of specific fields to retrieve. For example,
api/some/getbyid(42)?$select=x,y,z
can be used to get fields x
, y
, and z
, along with some default technical fields such as id
or author
.
I have model types that correspond to the data retrieved from the API:
type APIItem = {
id : number;
author : string;
}
type CustomerModel = APIItem & {
firstName : string;
lastName:string;
age : number
}
I encapsulate the data retrieval logic in a function that takes the ID and fields to be retrieved, makes the API call, and processes the result:
const fakeApi = (id: number, fields : string[]): Promise<APIItem> => {
// Construct API URL, fetch data, etc.
const result = JSON.parse(`{
"id": ${id}, "firstName": "Mark", "lastName": "Hamill", "age": 20, "author": "<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="83e7c3f0eceee6ade0ecee">[email protected]</a>"
}`) ;
return Promise.resolve(result);
}
const loadFromDB = async <
TModel extends APIItem
>(
id: number, // Item's ID in the API
fields : (keyof Omit<TModel, 'id'> & string)[] // Fields to retrieve. Exclude ID because it is always included.
): Promise<TModel> => {
const fromDb = await fakeApi(id, fields);
const result = fromDb as TModel;
return Promise.resolve(result);
}
const customerPromise = loadFromDB<CustomerModel>(42, ['firstName']); // Error in code: missing fields
customerPromise.then(console.log).catch(console.error);
The functionality works as expected except for one issue: the consumer code must specify all fields to retrieve. In the example above, only the firstName
field is retrieved, resulting in an incomplete object.
Is there a simple way to ensure that all fields from the model are provided in the API call?
As far as I know, TypeScript does not offer a way to iterate over keys in a type since types do not translate to actual JavaScript output.
I would like to enforce that the calling code specifies ALL fields (or allow the function to determine the necessary fields on its own):
loadFromDB<CustomerModel>(42, ['firstName', 'lastName', 'age']);
This approach ensures that the models will always be complete.