1

When developing applications / domain models in general, I am wondering what would be the best way to deal with incomplete or optional data. Typed languages such as TypeScript and C# give us the power of typing our models. Sometimes this is powerful, as it put constraints on our model and can force it to be integer. However, usually, real-life data is incomplete and that appears to reduce the advantages of typing constraints drastically.

Imagine a simple data model in a sample application (TypeScript frontend and some backend) of an entity called Project which has an id, a name and an optional description. The data is retrieved from a backend.

The data model is defined in the frontend using interfaces:

export interface IProject {
    id: number;
    name: string;
    description: string;
}

The data is retrieved from the backend as follows:

export class ProjectService {
    public getProject(projectId: number): Observable<IProject> {
        const url = 'http://server/api/project/' + projectId;
        return this.httpClient.get<IProject>(url);
    }
}

Example response of a project which actually has a description.

{
    "id": 1
    "name": "My first project",
    "description": "My first project is just a demo"
}

In the frontend application we display the retrieved data. For example, let's display the first 10 characters of the project description.

alert(project.description.substr(0,10));

So far, everything fine.

But imagine that our user created "Project two" without filling in the optional description. Now the backend can respond with:

{
    "id": 2
    "name": "Project two"
}

or

{
    "id": 2
    "name": "Project two",
    "description" : null
}

Now we get a null-reference exception in the frontend: Cannot read property 'substr' of null. Of course it's possible to add an if statement that checks for the description to be null:

if(project.description) {
  alert(project.description.substr(0,10));
}

This works, but it means adding null checks across the whole application. The whole code base would be full with those checks, along with the dangers of masking bugs instead of preventing them. That just does not feel right to me.

A possible solution could be to always return a description, hence returning an empty string if none was filled in.

{
    "id": 2
    "name": "Project two",
    "description": ""
}

Now the frontend does not need null checks for description any more, but it is no longer possible to distinguish between an explicitly filled-in empty description and a not(-yet) filled-in description.

What is the best way to deal with these kind of problems? The example above is just to illustrate, the scope is quite generic. It seem to plague all typed languages in which a typed contract/interface is defined which cannot always be fulfilled.

  • *What is the best way to deal with these kind of problems?* What is the problem? ... you are showing javascript code how this is connected with C# ? You can PHP backend sending the same json and ... in not typed language you also have to checked if given property exists – Selvin Oct 04 '19 at 09:35
  • @Selvin "in not typed language you also have to checked if given property exists" True, but in those languages there's no contract; basicly the value can be anything; hence checking whether a property exists makes sense. My problem in this case using a typed language is the need to check for property existence while the type "Project" claims to have a description property by its contract. – Wouter van Koppen Oct 04 '19 at 09:53
  • It's been a while since this question was asked, but in many languages there is the concept of an Optional type. In TypeScript, fp-ts provides the type `Option` which is either `some(value)` or `none`. In Kotlin, Arrow provides the same thing, but natively you can explicitly provide for nullability and you will have a compile-time error if you treat a nullable type as if it were not nullable (i.e., retrieve a sub-value of a nullable type withut checking for nullarity). Rust has this. Scala has this. etc. – user1713450 Jun 24 '20 at 04:44

1 Answers1

3

In C# 8 you can use Nullable Reference Types to explicitly mark the description field as being optional1:

class Project
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string? Description { get; set; }
}

Here, we declare Description as a string? -- a string which may be null. Then, whenever we try and call a method on Description which first checking it for null, the compiler will give us a warning.

C# also gives you syntax for working with null. For example, if you wanted to get the first 10 characters of the description but return an empty string if the description is null, you can write:

project.Description?.Substring(0, 10) ?? "";

(Note that this will throw if your description not null, but is less than 10 characters long, so you'll need some extra logic here in practice).

It looks like Typescript has similar concepts.


1 This class will give you a warning because Name isn't nullable, but it's also not initialised. There are a few ways of solving this: give it a default value of ""; give Project a constructor which sets Name; forcefully break the non-nullable contract for a short period before the type is deserialized by assigning null!; some others.

canton7
  • 37,633
  • 3
  • 64
  • 77
  • What does it change? Seriosuly ... how did you understand the question in one sentence? you still have to do null check - still the convention is: nullable is optional – Selvin Oct 04 '19 at 09:47
  • 1
    @Selvin I don't understand your question I'm afraid. What does what change? Which "one sentence" are you referring to? Could you be more descriptive, please – canton7 Oct 04 '19 at 09:49
  • ok how it will help with *distinguish between an explicitly filled-in empty description and a not(-yet) filled-in description* ? – Selvin Oct 04 '19 at 09:52
  • 1
    @Selvin The OP gave two options: allow `null` and be forced to add null-checks everywhere (which clutters the code, and risk missing them and getting an exception at runtime); or make Description not-nullable (which masks the difference between "not set" and "set but empty"). My answer allows null, so keeps the difference between "not set" and "set but empty", but uses a C# 8 feature to get the compiler to make sure that we add null-checks where appropriate, and describes syntax to deal with null without cluttering the codebase too much, thus minimising the downsides the OP described. – canton7 Oct 04 '19 at 09:55
  • Nullable in C# is a good solution, I was not aware that this exists in TypeScript as well (using an _optional_ modifier). This allows defining a more realistic data model contract. – Wouter van Koppen Oct 04 '19 at 10:25