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

adding SemiPartialType and associated tests #654

Open
wants to merge 3 commits into
base: master
Choose a base branch
from

Conversation

mjburghoffer
Copy link

@mjburghoffer mjburghoffer commented Aug 19, 2022

Since having a struct with a mix of required and optional properties is so common, I figured it would be nice (and more performant) to have a purpose-built type. I know you could achieve similar by doing intersection(type(...), partial(...)), but this approach feels more readable and easy to refactor.

To address the elephant in the room: I am aware that SemiPartial is not a great name - so maybe renaming the type is in order :)

It is worth noting that the method signature for semiPartial is 100% backwards compatible with type -- therefore, it would be an option to simply replace type with this code, and simply extend its capabilities.

It has been requested before: #450

There is another old but still active PoC pull request: #266

And it has been asked on stackoverflow a number of times (some of many examples below):
https://stackoverflow.com/questions/48230773/how-to-create-a-partial-like-that-requires-a-single-property-to-be-set
https://stackoverflow.com/questions/61311182/a-partly-partial-with-io-ts

In addition to the new functionality, the behavior of TypeOf is exactly as you would define an interface. In other words the typechecker will treat the two following examples identically:

interface NameTag {
  name: string
  nickName?: string
}

const value: NameTag = {
  name: 'test',
}

const missingOptional: NameTag = {
  name: 'test',
}

const presentOptional: NameTag = {
  name: 'test',
  nickName: 'nick',
}

// has compiler error
// Property 'name' is missing in type '{ nickName: string; }' but required in type 'NameTag'.ts(2741)
const requiredMissing: NameTag = {
  nickName: 'nick'
}
import * as t from 'io-ts'

const NameTag = t.semiPartial(
  {
    name: t.string,
    nickName: { type: t.string, optional: true }
  }
)

type NameTag = t.TypeOf<typeof NameTag>

const missingOptional: NameTag = {
  name: 'test',
}

const presentOptional: NameTag = {
  name: 'test',
  nickName: 'nick',
}

// has compiler error
// Property 'name' is missing in type '{ nickName: string; }' but required in type '{ name: string; nickName?: string | undefined; }'.ts(2741)
const requiredMissing: NameTag = {
  nickName: 'nick'
}

@kalda341
Copy link

I actually quite like SemiPartial as a name. I'm less keen on the proposed API though - personally I think something like the following looks and feels a little nicer to use:

t.semiPartial({
  requiredField: t.required(t.string),
  optionalField: t.optional(t.string),
});

I think the part that I dislike about your API is that optional and required fields are specified in quite different ways.
Another thing to note is that there is no implementation for the unstable API. I'm unsure about where this package is going with regards to it (there haven't been a lot of updates on the issue recently), but I'm currently using a lot of the unstable API in production.

This is great work - it's about time someone decided to tackle this long standing issue, and regardless of the API I think it will be much nicer to use than the current intersection solution.

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

Successfully merging this pull request may close these issues.

2 participants