loading...

Optional booleans?

twitter logo ・1 min read

If you define APIs, you usually discuss names and types a lot. One of these discussions brought up that one of my colleagues' dislike of optional boolean properties - and there's also a good reason for it, because undefined and false are coerced to the same value, which can lead to confusion.

I personally value the developer experience of having sensible defaults whenever possible over the possible misunderstanding of false and undefined.

Any other advantages and disadvantages I have overlooked? Please discuss!

twitter logo DISCUSS (10)
markdown guide
 

If something can have three states, it shouldn't be a boolean. If it needs to have one of two states, then existing data should be updated with the default (whether that's true or false).

"Optional booleans" only really pop up when boolean has been added to a schema, but the developers haven't made it required because they don't want to break backwards-compatibility. They should set the field to the default on all existing records.

 

Sorry, but then you just get a lot of enums

enum Whatever {
  Default,
  On,
  Off
}

I don't think that's an improvement, rather the contrary.

Especially for a web/react/Vue/etc component, that makes no sense at all.

 

Hmm, I interpreted Ben's comment about 'defaults' as "always have one, and when adding one retroactively be sure to backfill existing data," not "have a 'default' value that is distinct from all others in the type". This seems to align with and extend your own comment:

I personally value the developer experience of having sensible defaults whenever possible

Did I read you right, Ben? (@moopet )

But if you say booleans cannot be optional, then the consequence is that there cannot be default values for boolean properties.

I think that makes sense, though, right? If it's required, a default isn't necessary or applicable. If it's optional, you need a sane default.

But I didn't say anything about not having optional booleans: quite the contrary, I was talking about the case where you do have optional booleans, and paraphrasing Ben's comment about it.

@dabrady yeah, that's what I meant. If there's a useful semantic difference between three states then it should be an enum or something, but if the third state is just there beacuse it's unfilled, why not fill it?

Because it might only be for one of the use cases of your API. Should the developer using it need to care about all the use cases that don't matter to him? I should think that is a waste of his time.

 

If you use TypeScript probably also with runtypes, it's easy to do 'x' | 'y' | 'z', and all are truthy.

I believe API server should validate the input anyway (actually client-side as well), probably with JSON Schema and AJV.

But it might can be done with runtypes as well.

 

Optional booleans in APIs make sense to me in the same way that optionals in general make sense: they aren't optional to the API implementation, they're optional to the API consumer.

It's important to remember an optional boolean is not a boolean, it is a union type of boolean and whatever the type is for "value absence".

For many languages, this is tricky to work with, but Typescript and JavaScript make it easy.

A lot of people will rely on the simplicity of type coercion for implementing presence checks: if the value is "falsey", it's considered "absent."

function foo(x) {
  if (x) {
    alert("x is present");
  } else {
    alert("x is absent");
  }
}

But this of course breaks when the value is false exactly!

Instead, we should remember that in JS variables don't have types, only values do, and so doing a "presence check" on something that is supposed to be a boolean is as simple as asking typeof x == "boolean"!

function foo(x) {
  if (typeof x == "boolean") {
    alert("x is present");
  } else {
    alert("x is absent or not a boolean");
  }
}

In JS, if no value was provided, the type will be the string "undefined", and if a value is provided but isn't true or false the type will be something else.

This is amazing because it avoids the bugs that can happen when you try to determine presence based on the value itself.

 

Consider this more modern TS example:

const whatever = ({ darkMode = false, ...rest }) => null

It is not three different values, but a boolean value with a default so it can be optional.

If this was a component, every user would be forced to declare the darkMode property like it or not if it was not optional, which to me makes no sense if you can have a default.

Classic DEV Post from May 10 '19

Why I ignore the hype (and you should too)

It’s been 20 years since I made my first website. I've been burned by the hype ti...

Alex Lohr profile image
...former musician, voice actor, martial artist, turned senior front-end developer.