gcanti / io-ts

Runtime type system for IO decoding/encoding
https://gcanti.github.io/io-ts/
MIT License
6.7k stars 328 forks source link

Add support for empty tuples #633

Open GioAc96 opened 2 years ago

GioAc96 commented 2 years ago

🚀 Feature request

Current Behavior

Currently, the tuple combinator requires at least one codec, preventing it to be used as an empty tuple codec

Desired Behavior

Allow the tuple combinator to also accept an empty array of codecs:

const emptyArray = t.tuple([])

Alternatives

The same codec can also be implemented as follows:

const isEmptyArray = (input: unknown): input is [] => Array.isArray(input) && input.length < 1

export const emptyArray = new t.Type<[], [], unknown>(
  'emptyArray',
  isEmptyArray,
  (input, context) => (isEmptyArray(input) ? t.success(input) : t.failure(input, context)),
  t.identity
)

This alternative works just fine, but I don't understand why io-ts would impose a tuple to have at least one codec in it while typescript supports the empty tuple type []

Additional context

I found myself looking for this feature while building a codec to encode an http response that would have an empty array errors field in case of success. I am aware that omitting the errors field when no errors occur would be a better solution, but that is not an option in my case.

My environment

Software Version(s)
io-ts 2.2.16
fp-ts 2.11.5
TypeScript 4.5.4
mlegenhausen commented 2 years ago

Allowing zero arguments on t.tuple will not allow you to construct an empty array codec because internally the implementation is slicing away all additional values which is not considered an error. So you will need to implement it by yourself anyway.

GioAc96 commented 2 years ago

Of course, I'm not suggesting that only the signature of the t.tuple method is changed, but the implementation would be changed as well to allow empty tuples. io-ts proudly implements most features of the typescript type system, it's weird that it does not account for something as simple as an empty tuple type.

mlegenhausen commented 2 years ago

I think this change will not happen cause it would introduce a breaking change in the behavior and it seems to be intended by @gcanti. You can see that is explicitly tested here: https://github.com/gcanti/io-ts/blob/master/test/2.1.x/tuple.ts#L71-L75

The behavior seems to be the same as with type and interface where additional properties will not result in a failure during a decoding.

GioAc96 commented 2 years ago

I really don't see how this feature would introduce a breaking change, since it would loosen the requirements for the first argument of the tuple combinator. Also, the test that @mlegenhausen linked checks that additional components are stripped from the tuple, which has little to do with the requested feature. An empty tuple would simply strip all properties from the input, although I understand that this could cause some confusion since the codec t.tuple([]) would decode any array/tuple to an empty tuple. However, this is already not aligned with Typescript's behaviour, since the type checker already complains about additional components, as shown here:

type EmptyTuple = []
const a: EmptyTuple = ['hola'] // error: Source has 1 element(s) but target allows only 0

type StringNumberTuple = [string, number]
const b: StringNumberTuple = ['hola', 1, true] // error: Source has 3 element(s) but target allows only 2

Playground Link

Maybe @gcanti has more insights about why the tuple combinator was designed to reject empty tuples

voltrevo commented 2 years ago

t.tuple([]) is currently a compiler error so this shouldn't be a breaking change.

cyberixae commented 1 year ago

As @mlegenhausen pointed out, the current t.tuple implementation ignores any extra elements. To be consistent with that t.tuple([]) would have to accept any array, since any array is a 0-tuple as long as you ignore all the elements. Therefore, adding 0-tuple support to the current t.tuple implementation is not very useful. See #503 for more general discussion about tuple length validation.