io-ts icon indicating copy to clipboard operation
io-ts copied to clipboard

How to properly encode an optional field in a type?

Open edaqa-uncountable opened this issue 3 years ago • 4 comments

📖 Documentation

I'm unable to figure out how to encode an optional field in an interface. For example, in TypeScript I have this type:

export type Filter = {
  type: string
  value: string|null
  target?: string|null
}

I want to create the same type in io-ts, so tried the following:

export const IOFilter = t.type({
  type: t.string,
  value: t.union([t.string, t.null]),
  target: t.union([t.string, t.undefined, t.null]),
})

export type Filter = t.TypeOf<typeof IOFilter>

But this new Filter is not the same as the original one. A structure missing the target field is no longer compatible:

// type error that `target` is missing
const a : Filter = {
  type: 'name',
  value: 'abc'
}

How do I create an optional field?

edaqa-uncountable avatar Apr 06 '21 06:04 edaqa-uncountable

t.type expects all of the specified keys to exist. To make a codec for a record with optional properties, use t.partial.

These two codecs can be intersected to get what you're looking for:

const FilterC = t.intersection([
  t.type({
    type: t.string,
    value: t.union([t.string, t.null])
  }),
  t.partial({
    target: t.union([t.string, t.null, t.undefined])
  })
])

type Filter = t.TypeOf<typeof FilterC>

const a: Filter = { // <-- typechecks!
  type: 'name',
  value: 'abc'
}

0x706b avatar Apr 06 '21 06:04 0x706b

I am fairly certain you need to use the partial operator. I had the need for optional structures so mine looked like this where my whole IOFilter would be optional.

The union operator is like a OR condition if your structure might have this or that type.

export const IOFilter = t.partial({
  type: t.string,
  value: t.union([t.string, t.null]),
  target: t.union([t.string, t.undefined, t.null]),
})

In your case this might work but I have not tested.


export const IOFilter = t.partial({
  type: t.string,
  value: t.partial(t.string),
  target: t.partial(t.string),
})
'''

dariusjs avatar Apr 06 '21 06:04 dariusjs

I have tried the proposed solution by @0x706b , and works great for optional properties.

However, the union types with null are inferred as string, do you know a possible solutions for this?

aperkaz avatar Apr 08 '21 10:04 aperkaz

Seems they have updated their SDKs over time and seems this is no longer possible

Kalanamith avatar Feb 01 '23 23:02 Kalanamith

@edaqa-uncountable cheers for the question and @0x706b cheers for the answer! 🙌

Should we close this as resolved? 🤷

dreamyguy avatar Apr 25 '23 12:04 dreamyguy