Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

nullable and optional combinators in stable module #617

Open
etiennebatise opened this issue Nov 5, 2021 · 4 comments
Open

nullable and optional combinators in stable module #617

etiennebatise opened this issue Nov 5, 2021 · 4 comments

Comments

@etiennebatise
Copy link

etiennebatise commented Nov 5, 2021

🚀 Feature request

Current Behavior

To define optional or nullable fields using the stable api (residing in the index module), we have to define unions :

const Person = t.type({
  name: t.string,
  nullable: t.union([t.null, t.number]),
  optional: t.union([t.undefined, t.string])
})

An annoying consequence of that is described in this issue. tldr: when reporting, we get an error for each member of the union More on why this is annoying, at least to me, later.

Desired Behavior

It'd be nice to have some combinators to make those type definitions much more readable.

const Person = t.type({
  name: t.string,
  nullable: t.nullable(number),
  optional: t.optional(t.string)
})

And it'd be nice to get around the issue of distinct errors for the same field.

A nullable combinator is already available in the Decoder module.

Suggested Solution

I was not able to come up for a solution by myself so I just took the author's code from the issue mentioned above. Note that I had to add a smelly as undefined to make this typecheck on my local setup.

function optional<A, O>(
  codec: t.Type<A, O, unknown>,
  name: string = `${codec.name} | undefined`
): t.Type<A | undefined, O | undefined, unknown> {
  return new t.Type(
    name,
    (u: unknown): u is A | undefined => u === undefined || codec.is(u),
    (u, c) => (u === undefined ? t.success(u) : codec.validate(u, c)),
    a => (a === undefined ? a as undefined : codec.encode(a))
  ) //                        ^ Smelly code right here
}

Similar thing for nullable. I monkey patched the above with no shame:

function nullable<A, O>(
  codec: t.Type<A, O, unknown>,
  name: string = `${codec.name} | null`
): t.Type<A | null, O | null, unknown> {
  return new t.Type(
    name,
    (u: unknown): u is A | null => u === null || codec.is(u),
    (u, c) => (u === null ? t.success(u) : codec.validate(u, c)),
    a => (a === null ? a as null : codec.encode(a))
  ) //                   ^ Smelly code right here again
}

Who does this impact? Who is this for?

This is for all users.

Describe alternatives you've considered

We could also provide those snippets as part of the docs or some other lib (eg: io-ts-types)

Additional context

My initial goal was to create a JSON path reporter for my types. Unfortunately, when crawling the error contexts, I could not understand how to efficiently distinguish union context errors from array items errors.
For instance, given the following type

export const Person = t.type({
  books: t.union([t.null, t.array(
    t.type({
        title: t.string
    })
  )])
})
const foo = Person.decode({ books:[{title: "Foo"}, {}]})

My custom error reporter would give me the following:

[
  '.books.[0]',
  '.books.[1].[1].title'
 ]

There is obviously no array of arrays in the original type but the only hint I get in the error context is if the key is a number (with isNan(parseInt)). In that case, I wrap the key between brackets. Also, this won't even work for dictionaries with number fields.

Maybe I am missing something completely obvious and written in bold in the docs and all this is useless.

Your environment

Software Version(s)
io-ts 2.2.16
fp-ts 2.11.4
TypeScript 4.4.4
@etiennebatise etiennebatise changed the title nullable and optional constructors in index (types) module nullable and optional combinators in index (types) module Nov 5, 2021
@etiennebatise etiennebatise changed the title nullable and optional combinators in index (types) module nullable and optional combinators in stable module Nov 5, 2021
@mlegenhausen
Copy link
Contributor

@etiennebatise feel free to provide a PR. That would make it a lot easier to review the code and give you feedback.

@etiennebatise
Copy link
Author

@mlegenhausen will do 👍

@ragnese
Copy link

ragnese commented Nov 14, 2022

As of version 2.2.19, there is a nullable combinator in the Type sub-module, but I think its typing is not ideal/complete. This is the declaration:

export declare const nullable: <A>(or: Type<A>) => Type<A | null>

Where Type is,

export interface Type<A> extends t.Type<A, unknown, unknown> {}

But that is incomplete because it loses the input and output information for the codec being wrapped with nullable. Rather, I would think the combinator should be more generic. Something like:

export declare const nullable: <C extends Mixed>(or: C) => Type<C[_A] | null, C[_O] | null, C[_I]>

which, I believe, is closer to what you get when you do t.union[t.null, someOtherCodec]

@danielo515
Copy link

So right now there is not code that can do

null |undefined | A -> Option<A>

Right? I just want to be sure about that

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants