API responses mapping pitfalls

or don’t trust your API

Sviatoslav Melnychenko
2 min readFeb 19, 2020

--

Motivation

90% of our work is to present to user data, that we get from REST API. While it doesn’t seem rocket science, some responses could be so unpredictable, that the next app crash would make you facepalm. The very fresh example that I got:

"set": [
{},
{},
{}
]

The application crashed on this response. And code, that was handling converting API models to domain models, was (I know it’s KAVA):

And then somewhere in the presentation layer, we tried to parse an empty string to date with sad consequences.

Next to fixing this, I’ve decided to share my thoughts on handling unpredictable API with colleagues. As turned out, here at Appsfactory we use similar approaches, but slightly different implementations.

Proper response handling

could be described in one sentence: define vital data and propagate error if it is missed.

To be more specific, there are practical tips:

  • Domain entities should have only non-nullable properties.
    You can use some default values (empty strings, zeros, constants, etc.) for properties, that are not vital for business logic and can be omitted on screen.
  • Define vital properties: ids, timestamps, names etc.
    Without vital properties, the entity should not be created. If it’s part of a collection — don’t put it there. If feature asks for such an entity — propagate error.

I’ve applied those principles in a few projects. Here is a generic example:

It seems a lot of code, but it’s better to do dirty work once and don’t rely on the backend.

Fix

So, my vital field from the example at the beginning was time, and the fix was really quick:

val list = mutableListOf<DomainEntity>()
it.set.filter { apiModel -> !apiModel.time.isNullOrEmpty() }.forEach { apiModel->
list.add(DomainEntity(
time = apiModel.time ?: "",

Another [cleaner] example

One of my colleagues, has suggested sharing his way.

We have a safeCallApi method that handles any exceptions or timeouts during the request and returns specific errors.

We have our API model, where everything is nullable (because we do not trust the API at all) and our domain entity where everything it not nullable anymore.

Then we have a mapper extension that converts the API model to the domain model and decides which fields are mandatory and which not, handles the parsing of dates, etc.

The generateMappingError logs a specific error message. It currently always returns null, but it can also easily be changed to throw exceptions or whatever.

So a call to ApiUser(null, “01.01.202”).toEntityOrNull would log Mapping error in ‘ApiUser’ at property ‘name’.

The mapping in the example above would look like this with our approach. Clean and simple: it.set.mapNotNull { it?.toEntityOrNull() }

--

--

Sviatoslav Melnychenko
Sviatoslav Melnychenko

Responses (1)