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

More idiomatic optional properties on runtime InterfaceCombinator output #26

Closed
mmkal opened this issue Aug 30, 2018 · 3 comments
Closed

Comments

@mmkal
Copy link
Contributor

mmkal commented Aug 30, 2018

It looks like from gcanti/io-ts#140 that it's non-trivial to declare a key as optional based on properties of the value, although I still hope that there'll be some sort of TypeScript 2.8 magic that makes it possible. But given that io-ts-codegen is generating types as a string, it should be possible to do better than this:

t.printRuntime(t.interfaceCombinator([t.property('foo', t.stringType, true)]))

// outputs
t.interface({
  foo: t.union([
    t.string,
    t.undefined
  ])
})

by using the isOptional flag on the PropertyCombinator. Changing printRuntimeInterfaceCombinator to something like this would maybe do the trick:

function printRuntimeInterfaceCombinator(interfaceCombinator: InterfaceCombinator, i: number): string {
  let requiredProperties: Property[] = []
  let optionalProperties: Property[] = []
  interfaceCombinator.properties.forEach(p => p.isOptional ? optionalProperties.push(p) : requiredProperties.push(p))
  
  if (requiredProperties.length > 0 && optionalProperties.length > 0) {
    return printRuntimeIntersectionCombinator(intersectionCombinator([
      exports.interfaceCombinator(requiredProperties),
      partialCombinator(optionalProperties),
    ]), i)
  }
  
  if (optionalProperties.length > 0) {
    return printRuntimePartialCombinator(partialCombinator(optionalProperties), i)
  }
  
  let s = 't.interface({\n'
  s += interfaceCombinator.properties.map(p => printRuntimeProperty({ ...p, isOptional: false }, i + 1)).join(',\n')
  s += `\n${indent(i)}}`
  s = addRuntimeName(s, interfaceCombinator.name)
  s += ')'
  return s
}

then you get fairly sensible output, at least until optional values can become more natural in io-ts itself:

return [
  t.printRuntime(t.interfaceCombinator([t.property('foo', t.stringType, true)])),
  t.printRuntime(t.interfaceCombinator([t.property('foo', t.stringType, true), t.property('bar', t.numberType)])),
  t.printRuntime(t.interfaceCombinator([t.property('foo', t.stringType), t.property('bar', t.numberType)])),
].join('\n\n')

// outputs:
t.partial({
  foo: t.string
})

t.intersection([
  t.interface({
    bar: t.number
  }),
  t.partial({
    foo: t.string
  })
])

t.interface({
  foo: t.string,
  bar: t.number
})

Advantages being 1) that the optional properties are truly optional, and don't have to be explicitly assigned to undefined, and 2) it's more readable than seeing t.union([t.blah, t.undefined]) everywhere.

If you like the idea, feel free to just take the code straight from here. Or I could open a PR with something along these lines (and add a couple of tests).

@gcanti
Copy link
Owner

gcanti commented Aug 31, 2018

@mmkal looks good to me, @giogonzo what do you think?

mmkal added a commit to mmkal/io-ts-codegen that referenced this issue Aug 31, 2018
For interfaces with optional properties, use either partialCombinator (for all optional) or intersectionCombinator (for mixed) to allow for true optional properties, which don't require explicitly assigning undefined.

Addresses gcanti#26
@giogonzo
Copy link

giogonzo commented Sep 3, 2018

@mmkal @gcanti makes a lot of sense, wanted this myself for a while. Thanks!

gcanti pushed a commit that referenced this issue Sep 3, 2018
For interfaces with optional properties, use either partialCombinator (for all optional) or intersectionCombinator (for mixed) to allow for true optional properties, which don't require explicitly assigning undefined.

Addresses #26
@gcanti
Copy link
Owner

gcanti commented Sep 3, 2018

Closing via #27

@gcanti gcanti closed this as completed Sep 3, 2018
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

3 participants