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

Add case insensitivity for string type literals

Open piersmacdonald opened this issue 6 years ago • 10 comments

it's helpful to define an in put as being t.keyof({ firstName: null, preferredName: null }); but it would be nice if they could send firstName, firstname, fIrStNaMe`

piersmacdonald avatar Jul 26 '18 20:07 piersmacdonald

@piersmacdonald we strive keeping io-ts mininal and lean. Io-ts-types repo is where such additional Types may be defined.

sledorze avatar Jul 26 '18 20:07 sledorze

Fair enough, thanks for the quick response.

piersmacdonald avatar Jul 26 '18 23:07 piersmacdonald

@piersmacdonald Did you come up with this type? I couldn't find it in io-ts-types.

@gcanti is there easy way to achieve this with current Decoder?

One way to do this is to have all literals in lowercase and then convert incoming string to lowercase and then forward it to D.literals decoder

But this forces me to define all literal types in lowercase.

 const lowerCase = pipe(
    D.string,
    D.map((s) => s.toLowerCase())
  )

  const ComponentType = pipe(lowerCase, D.compose(D.literal('hcd', 'assembly')))
  type ComponentType = D.TypeOf<typeof ComponentType>

  console.log(ComponentType.decode('HCD'))

kpritam avatar Jul 15 '20 15:07 kpritam

@kpritam I would return the original input

import * as E from 'fp-ts/lib/Either'
import { flow } from 'fp-ts/lib/function'
import * as D from 'io-ts/lib/Decoder'

const decoder: D.Decoder<unknown, string> = {
  decode: flow(
    D.string.decode,
    E.chainFirst((s) => D.literal('hcd', 'assembly').decode(s.toLowerCase()))
  )
}

console.log(decoder.decode('HCD'))
// => { _tag: 'Right', right: 'HCD' }

gcanti avatar Jul 15 '20 18:07 gcanti

@gcanti but this forces to define all the literals in lowercase, that might not what you always want.

Basically is it possible to make decoding of following literals case insensitive?

const compType = D.literal('Hcd', 'Assembly')

// all the following cases should pass and all should return **Hcd**
compType.decode('hcd')
compType.decode('Hcd')
compType.decode('HCD')

kpritam avatar Jul 15 '20 18:07 kpritam

all the following cases should pass and all should return Hcd

Ah ok, I misinterpreted the requirements, I thought you always wanted the original input back

gcanti avatar Jul 15 '20 18:07 gcanti

import * as E from 'fp-ts/lib/Either'
import { flow, pipe } from 'fp-ts/lib/function'
import * as O from 'fp-ts/lib/Option'
import * as A from 'fp-ts/lib/ReadonlyArray'
import * as D from 'io-ts/lib/Decoder'

export const iliteral = <A extends readonly [string, ...Array<string>]>(
  ...values: A
): D.Decoder<unknown, A[number]> => {
  const message = values.map((value) => JSON.stringify(value)).join(' | ')
  return {
    decode: flow(
      D.string.decode,
      E.chain((s) =>
        pipe(
          values,
          A.findIndex((value) => value.toLowerCase() === s.toLowerCase()),
          O.fold(
            () => D.failure(s, message),
            (i) => D.success(values[i])
          )
        )
      )
    )
  }
}

gcanti avatar Jul 15 '20 19:07 gcanti

@gcanti great, thanks.

Does it make sense for this to go into main library or io-ts-types?

It is pretty common use case that these literal types are defined as case insensitive enums in other languages and then its upto codec library to decide how to encode/decode them.

kpritam avatar Jul 15 '20 19:07 kpritam

@kpritam ended up switching libraries for the job. This question was asked in the context of validating user requests for a REST service. So I switched to class-validator which is more specific to my use case. Still make some use of io-ts but not in that context.

piersmacdonald avatar Jul 15 '20 19:07 piersmacdonald

Does it make sense for this to go into main library or io-ts-types?

:+1: once Decoder becomes official, moving to io-ts-types as a reminder

gcanti avatar Jul 16 '20 06:07 gcanti