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?
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 usingnew Type(...), but for this simple mapping it should be possible to usemapOutputfromio-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 馃憣
Most helpful comment
@angeloashmore with the newer modules would be something like
or