Skip to content

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.

@mwilliamson
Copy link

Just in case anybody in the future stumbles across this, I had a play around with this branch and fixed a few issues, specifically:

  • The semi-partial type couldn't be used with exact() since it didn't implement HasProps
  • It didn't support prop types where the encoded type differed from the value type
  • Unions with semi-partial types didn't work properly since they couldn't extract possible tags from the semi-partial tags
  • Some prop types, such as array types, were incorrectly detected as TypedProp, leading to errors since they would be treated as the element type rather than an array of those elements

My (possibly buggy!) changes are here: https://github.com/mwilliamson/io-ts/tree/semi-partial

I think I'll probably ending up taking a different approach, but this branch was definitely useful as inspiration!

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.

3 participants