Io-ts: Is it possible to write a codec whose output type properties names differ from the input?

Created on 30 Sep 2019  路  7Comments  路  Source: gcanti/io-ts

Because of code style requirements I'm in the situation where I can't use the default behaviour of object types in io-ts. I'd like to be able to specify the property name to be decoded into my own property name. How can I do that?

Basically I need something like:

const MyCodec = t.type({
  fullName: t.string({key: 'full_name'})
})

Is this possible with the current io-ts or will I have to write some glue code where I first rename the object keys?

Most helpful comment

I'm not sure if the newer Decoder and Encoder modules simplifies this

@angeloashmore with the newer modules would be something like

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

const MyDecoder = pipe(
  D.type({
    fullName: D.string
  }),
  D.map((o) => ({ full_name: o.fullName }))
)

or

import * as C from 'io-ts/lib/Codec'

const MyCodec = pipe(
  C.type({
    fullName: C.string
  }),
  C.imap(
    (o) => ({ full_name: o.fullName }),
    (o) => ({ fullName: o.full_name })
  )
)

All 7 comments

Yes. In general, the types of output (O) and runtime (A) can differ in io-ts. For more complex scenarios you'd have to implement your own validations by creating a new codec using new Type(...), but for this simple mapping it should be possible to use mapOutput from io-ts-types:

const T: t.Type<
  {
    full_name: string;
  },
  {
    fullName: string;
  }
> = mapOutput(t.type({ full_name: t.string }), o => ({ fullName: o.full_name }));

(annotation is not strictly needed, I put it there just to make the example clear)

Yes. In general, the types of output (O) and runtime (A) can differ in io-ts. For more complex scenarios you'd have to implement your own validations by creating a new codec using new Type(...), but for this simple mapping it should be possible to use mapOutput from io-ts-types:

const T: t.Type<
  {
    full_name: string;
  },
  {
    fullName: string;
  }
> = mapOutput(t.type({ full_name: t.string }), o => ({ fullName: o.full_name }));

(annotation is not strictly needed, I put it there just to make the example clear)

I don't get how can I create a codec that behaves like this

const MyCodec = t.type({
  fullName: t.string({key: 'full_name'})
})

cause if I create a new type for t.string invalidate function I don't have access the object so I get the field with the provided key

@mohaalak you can't. t.string has no access to its parent. The transformations needs to happen in the t.type thats what mapOutput is for.

Sorry to stir up this old ticket, but would mapOutput really work here?

Unless I'm super mistaken mapOutput is only invoked on encode but not decode.

For anyone else coming across this, indeed the above mapOutput example does not work with decode, unless I'm misunderstanding how to properly use it.

I'm using this function to map input properties. It could be combined with mapOutput if you need to go in both directions:

import * as t from 'io-ts'
import * as tt from 'io-ts-types'

function mapInput<A, O, I, H>(
  codec: t.Type<A, O, I>,
  f: (p: H) => I,
  name: string = codec.name,
): t.Type<A, O, H> {
  return new t.Type(
    name,
    codec.is,
    (i, c) => codec.validate(f(i), c),
    codec.encode,
  )
}

const T: t.Type<
  { fullName: string },
  { fullName: string },
  { full_name: string }
> = mapInput(
  t.type({
    fullName: t.string,
  }),
  (i) => ({
    fullName: i.full_name,
  }),
)

const U: t.Type<
  { fullName: string },
  { full_name: string },
  { full_name: string }
> = tt.mapOutput(
  mapInput(
    t.type({
      fullName: t.string,
    }),
    (i) => ({
      fullName: i.full_name,
    }),
  ),
  (o) => ({ full_name: o.fullName }),
)

I'm not sure if the newer Decoder and Encoder modules simplifies this. If not, would this be helpful to include in io-ts-types @gcanti?

I'm not sure if the newer Decoder and Encoder modules simplifies this

@angeloashmore with the newer modules would be something like

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

const MyDecoder = pipe(
  D.type({
    fullName: D.string
  }),
  D.map((o) => ({ full_name: o.fullName }))
)

or

import * as C from 'io-ts/lib/Codec'

const MyCodec = pipe(
  C.type({
    fullName: C.string
  }),
  C.imap(
    (o) => ({ full_name: o.fullName }),
    (o) => ({ fullName: o.full_name })
  )
)

@gcanti Thanks, that's beautiful 馃憣

Was this page helpful?
0 / 5 - 0 ratings

Related issues

xogeny picture xogeny  路  5Comments

philipp-schaerer-lambdait picture philipp-schaerer-lambdait  路  3Comments

steida picture steida  路  7Comments

cloudify picture cloudify  路  4Comments

Picoseconds picture Picoseconds  路  3Comments