Encoder<A>
gives
encode: (a: A) => unknown
Encoder<A, B>
giving
encode: (a: A) => B
I have been using io-ts for encoding from private data types to request bodies, where the unknown type is fine, but also to things like graphql schemas where the destination typed, e.g.:
interface GraphqlProduct {
__typename: "Product";
name: string;
// ... etc
}
Now that Encoder always returns unknown I have to cast, which is obviously not ideal.
I started working through the functions in Encoder.ts but I'm stumped on what to do for:
export function type<A>(properties: { [K in keyof A]: Encoder<A[K]> }): Encoder<A>
I suppose there would be a subsequent impact to the schema types too. Perhaps if you decide to derive an Encoder instance, you have the option to provide an output type for each schema element?
I'm stumped on what to do for:
export function type(properties: { [K in keyof A]: Encoder
Me too, that's why I chose the simpler Encoder<A> model
Does it mean there is no Output type anymore in v3?
@gcanti @leemhenson what about something like this?
export interface Encoder<A, B> {
readonly encode: (a: A) => B
}
export type TypeOf<E> = E extends Encoder<infer A, any> ? A : never
export type OutputOf<E> = E extends Encoder<any, infer B> ? B : never
// ...
export function type<Props extends Record<string, Encoder<any, any>>>(
properties: Props
): Encoder<{ [K in keyof Props]: TypeOf<Props[K]> }, { [K in keyof Props]: OutputOf<Props[K]> }> {
// runtime implementation same as before
}
It seems to work ok when used directly:
/** simplified version of the existing `export const encoder: Contravariant<URI> & Schemable<URI>` */
const e = {
string: id as Encoder<string, string>,
number: id as Encoder<number, number>,
type,
array,
/** an asymmetric encoder, similar to io-ts-type's `DateFromISOString` */
dateFromString: {
encode: date => date.toISOString()
} as Encoder<Date, string>,
}
const Person = e.type({
name: e.string,
age: e.number,
dob: e.dateFromString,
orders: e.array(e.type({
id: e.string,
date: e.dateFromString
}))
})
// types look correct:
import { expectTypeOf } from 'expect-type'
expectTypeOf<TypeOf<typeof Person>>().toEqualTypeOf<{
name: string;
age: number;
dob: Date;
orders: Array<{ id: string; date: Date }>
}>()
expectTypeOf<OutputOf<typeof Person>>().toEqualTypeOf<{
name: string;
age: number;
dob: string;
orders: Array<{ id: string; date: string }>
}>()
expectTypeOf(Person.encode).returns.toEqualTypeOf<OutputOf<typeof Person>>()
The only trouble I had was with HKT/Schemable's assumptions about what the type combinator looks like. It doesn't allow for it to have an extends clause in the generic, or to return a type with two typeargs rather than one. By not using the Schemable interface, it works as expected.
By not using the Schemable interface, it works as expected
@mmkal @leemhenson if we don't use Schemable we can implement a PEncoder data type
export interface PEncoder<O, A> {
readonly encode: (a: A) => O
}
and then derive Encoder<A> = PEncoder<unknown, A> from it, see https://github.com/gcanti/io-ts/blob/028392215b00d02840586fedf668f8fbe709d7e2/src/PEncoder.ts.
@leemhenson would PEncoder serve your use case?
I have been using io-ts for encoding from private data types to request bodies
Actually we could derive from PEncoder any O-fixed encoder, for example a JsonEncoder = PEncoder<Json, A>
export interface JsonArray extends Array<Json> {}
export type JsonObject = { [key: string]: Json }
export type Json = null | string | number | boolean | JsonObject | JsonArray
export interface JsonEncoder<A> {
readonly encode: (a: A) => Json
}
@mlegenhausen there's no v3 in the works, everything I was working on in the last few months has been backported to v2.2.x (as experimental modules)
@mmkal @leemhenson thinking twice, since the main goal of decoders / encoders is to handle JSON payloads, JsonEncoder seems more useful than the current Encoder which outputs unknown, so I would propose:
Encoder to JsonEncoder and change its fixed output type to JsonJsonEncoder in CodecPEncoder to Encoderwhat do you think?
use JsonEncoder in Codec
I'd propose JsonCodec (hardcoded Json output type) vs Codec (reusing generic Encoder) instead
Yeah this way around makes much more sense to me, and I would imagine to folks migrating up from earlier versions.
Any chance we could get this and the rest of the upcoming 2.2.3 changes published as a io-ts@next so I can try them out?
@leemhenson https://github.com/gcanti/io-ts/pull/471
Closed by #471
Most helpful comment
I'd propose
JsonCodec(hardcoded Json output type) vsCodec(reusing genericEncoder) instead