Giter VIP home page Giter VIP logo

io-ts's Introduction

build status npm downloads

Installation

To install the stable version

npm i io-ts fp-ts

Note. fp-ts is a peer dependency for io-ts

Usage

Stable features

Experimental modules (version 2.2+)

Experimental modules (*) are published in order to get early feedback from the community, see these tracking issues for further discussions and enhancements.

The experimental modules are independent and backward-incompatible with stable ones.

(*) A feature tagged as Experimental is in a high state of flux, you're at risk of it changing without notice.

io-ts's People

Contributors

aeirola avatar americk0 avatar dmorosinotto avatar eivindml avatar emnlmn avatar ericcrosson avatar flegall avatar gcanti avatar giogonzo avatar hallettj avatar jjoekoullas avatar lawson-ng avatar leemhenson avatar micimize avatar mikkom avatar mixedcase avatar mlegenhausen avatar nn--- avatar oliverjash avatar osdiab avatar safareli avatar sledorze avatar sompylasar avatar sukkaw avatar tarikdemirci avatar tgfisher4 avatar thewilkybarkid avatar uldissturms avatar waynevanson avatar zhuangya avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

io-ts's Issues

Sharing?

It might be silly, but I just finished to io-ts-ize GeoJSON and thought it would have been so nice to got it from somewhere. No idea if it would happen often to have this sort of thing to share but at least it could feed an examples folder.

For the record, the fruit of labour.

// tslint:disable:variable-name
import * as io from 'io-ts';

export const i = io.interface;
export const u = io.union;
export const l = io.literal;
export const a = io.array;
export const t = io.tuple;
export const p = io.partial;

/**
 * https://tools.ietf.org/html/rfc7946#section-1.4
 */
export const DirectGeometryTypeIO = u([
    l('Point'),
    l('Polygon'),
    l('LineString'),
    l('MultiPoint'),
    l('MultiPolygon'),
    l('MultiLineString'),
], 'DirectGeometryTypeIO');

export const GeometryCollectionTypeIO = l('GeometryCollection', 'GeometryCollectionTypeIO');

export const GeometryTypeIO = io.intersection([
    DirectGeometryTypeIO,
    GeometryCollectionTypeIO,
], 'GeometryTypeIO');

export const GeoJsonTypeIO = io.intersection([
    GeometryTypeIO,
    u([
        l('Feature'),
        l('FeatureCollection'),
    ]),
], 'GeoJsonTypeIO');

/**
 * https://tools.ietf.org/html/rfc7946#section-5
 * 
 * I leave it un-done for the moment as I won't use it here.
 * Would need specific validate
 */
export const BoundingBoxIO = u([a(a(io.number)), a(a(a(io.number)))], 'BoundingBoxIO');


/***
* https://tools.ietf.org/html/rfc7946#section-3
*/
export const GeoJsonObjectIO = io.intersection([
    i({
        type: GeoJsonTypeIO,
    }),
    p({
        bbox: BoundingBoxIO,
    }),
], 'GeoJsonObjectIO');

/***
* https://tools.ietf.org/html/rfc7946#section-3.1.1
*/
export const PositionIO = a(io.number, 'PositionIO');

export const CoordinatesIO = u([
    PositionIO,
    a(PositionIO),
    a(a(PositionIO)),
    a(a(a(PositionIO))),
], 'CoordinatesIO');

/***
* https://tools.ietf.org/html/rfc7946#section-3.1
*/
export const DirectGeometryObjectIO = io.intersection([
    GeoJsonObjectIO,
    i({
        type: DirectGeometryTypeIO,
        coordinates: CoordinatesIO,
    }),
], 'DirectGeometryObjectIO');

export const GeometryCollectionIO = io.intersection([
    GeoJsonObjectIO,
    i({
        type: GeometryCollectionTypeIO,
    }),
], 'GeometryCollectionIO');

export const GeometryObjectIO = u([DirectGeometryObjectIO, GeometryCollectionIO], 'GeometryObjectIO');

/***
* https://tools.ietf.org/html/rfc7946#section-3.1.2
*/
export const PointIO = io.intersection([
    DirectGeometryObjectIO,
    i({
        type: l('Point'),
        coordinates: PositionIO,
    }),
], 'PointIO');

/***
* https://tools.ietf.org/html/rfc7946#section-3.1.3
*/
export const MultiPointIO = io.intersection([
    DirectGeometryObjectIO,
    i({
        type: l('MultiPoint'),
        coordinates: a(PositionIO),
    }),
], 'MultiPointIO');

/***
* https://tools.ietf.org/html/rfc7946#section-3.1.4
*/
export const LineStringIO = io.intersection([
    DirectGeometryObjectIO,
    i({
        type: l('LineString'),
        coordinates: a(PositionIO),
    }),
], 'LineStringIO');

/***
* https://tools.ietf.org/html/rfc7946#section-3.1.5
*/
export const MultiLineStringIO = io.intersection([
    DirectGeometryObjectIO,
    i({
        type: l('MultiLineString'),
        coordinates: a(a(PositionIO)),
    }),
], 'MultiLineStringIO');

/***
* https://tools.ietf.org/html/rfc7946#section-3.1.6
*/
export const PolygonIO = io.intersection([
    DirectGeometryObjectIO,
    i({
        type: l('Polygon'),
        coordinates: a(a(PositionIO)),
    }),
], 'PolygonIO');

/***
* https://tools.ietf.org/html/rfc7946#section-3.1.7
*/
export const MultiPolygonIO = io.intersection([
    DirectGeometryObjectIO,
    i({
        type: l('MultiPolygon'),
        coordinates: a(a(a(PositionIO))),
    }),
], 'MultiPolygonIO');

/***
* https://tools.ietf.org/html/rfc7946#section-3.1.8
* 
* Because of difficulty to express nested types here and the RFC states that:
* "To maximize interoperability, implementations SHOULD avoid nested
   GeometryCollections"
* We do not implement nested collections.
*/
export const GeometryCollection = io.intersection([
    GeoJsonObjectIO,
    i({
        type: l('GeometryCollection'),
        geometries: a(DirectGeometryObjectIO),
    }),
]);

/***
* https://tools.ietf.org/html/rfc7946#section-3.2
*/
export const PropertiesIO = u([io.dictionary(io.string, io.any), io.null], 'PropertiesIO');

export const FeatureIO = io.intersection([
    GeoJsonObjectIO,
    i({
        type: l('Feature'),
        geometry: u([GeometryObjectIO, io.null]),
        properties: PropertiesIO,
    }),
    p({
        id: u([io.string, io.number]),
    }),
], 'FeatureIO');

/***
* https://tools.ietf.org/html/rfc7946#section-3.3
*/
export const FeatureCollectionIO = io.intersection([
    GeoJsonObjectIO,
    i({
        type: l('FeatureCollection'),
        features: a(FeatureIO),
    }),
], 'FeatureCollectionIO');



export type GeometryType = io.TypeOf<typeof GeometryTypeIO>;
export type GeoJsonObject = io.TypeOf<typeof GeoJsonObjectIO>;
export type Position = io.TypeOf<typeof PositionIO>;
export type DirectGeometryObject = io.TypeOf<typeof DirectGeometryObjectIO>;
export type GeometryObject = io.TypeOf<typeof GeometryObjectIO>;
export type Point = io.TypeOf<typeof PointIO>;
export type MultiPoint = io.TypeOf<typeof MultiPointIO>;
export type LineString = io.TypeOf<typeof LineStringIO>;
export type MultiLineString = io.TypeOf<typeof MultiLineStringIO>;
export type Polygon = io.TypeOf<typeof PolygonIO>;
export type MultiPolygon = io.TypeOf<typeof MultiPolygonIO>;
export type Feature = io.TypeOf<typeof FeatureIO>;
export type FeatureCollection = io.TypeOf<typeof FeatureCollectionIO>;
export type Properties = io.TypeOf<typeof PropertiesIO>;
```

Help needed?

So I am a big TypeScript fan and have been applying it for a couple of years in my Node projects both personally and professionally. I am also into Scala and functional programming. I noticed that this library used FP concepts and it lead me to the your fp-ts project. Both of these projects are very interesting to me and I would be interested in contributing if you need / want the help.

add prism combinator

This would allow to chain validations

export type GetOption<S, A> = (s: S) => Option<A>

declare function prism<RT extends Any, B>(type: RT, getOption: GetOption<TypeOf<RT>, B>, name?: string): PrismType<RT, B>

Usage

const parseNumber = (s: string): Option<number> => {
  const n = parseFloat(s)
  return isNaN(n) ? none : some(n)
}
const NumberFromString = t.prism(t.string, parseNumber)

TypeOf doesn't extract nested interfaces

import * as T from 'io-ts'

type Foo = T.TypeOf<typeof $Foo>
const $Foo = T.interface({
    foo1: T.number,
    foo2: T.string
});

type Bar = T.TypeOf<typeof $Bar>
const $Bar = T.interface({
    bar1: T.string,
    bar2: $Foo
})
type Bar = {
    bar1: string;
    bar2: T.InterfaceOf<{
        foo1: T.Type<number>;
        foo2: T.Type<string>;
    }>;
}

better error messages from UnionTypes

Hola

I've got a a big t.union of several (~ 7) different large t.interface types. To make things more interesting, these are also wrapped up in an t.array. When I validate my incoming array against this schema, I get a validation failure. Because the union type doesn't know which of the types was the "right one" for the object being validated, it can't give me a descriptive path down through the object to the field where the problem was. I just get a Invalid value { ...all the object } supplied to Array<( interfaceType1 | interfaceType2 | ... )>. I'm looking at several screen's worth of output, non of which is particularly helpful for finding the one bit which is causing the problem.

Looking at UnionType it basically keeps validating the incoming value against each type in the union until it gets a Right. Then, if no Right was found, it returns that shallow list of ValidationError. What do you think about modifying it to collect each ValidationError[] for each type, and then returning all of them if the union can't find a match? You'd get even more output on the screen, but at least you'd be able to figure out which set of errors you should be looking at for the source value, and then you'd be able to see the path through the value to the field that contained the cause of the validation failure.

I'm gonna have a hack around but I thought I'd see if you had any bright ideas. ๐Ÿ’ก

enum support

Is there a way to cleanly express the equivalent of:

export enum Options {
  optionA = 'optionA',
  optionB = 'optionB',
  optionC = 'optionC'
}

as runtime type and still get code completion?
(e.g. in an if clause if (val === Options.optionA))

Lenses

Hello again

Would it be possible to write some sort of extension that allowed me to derive lenses for each property on an interface without having to manually write them? E.g. something like:

const PersonInterface = t.interface({
  name: t.string,
  age: t.number,
});

const Person = t.Typeof<typeof PersonInterface>;

const personLenses = deriveLenses(PersonInterface); // ish

personLenses.age.get(personX);  // => number

I've got as far as this:

type DerivedLenses<P extends t.Props, T extends {}> = { [K in keyof P]: Lens<T, K> };

const PersonProps = {
  name: t.string,
  age: t.number,
};

type PersonLenses = DerivedLenses<typeof PersonProps, Person>;

// This doesn't compile, :/
const lenses: PersonLenses = {
  name: Lens.fromProp<Person, "name">("name"),
  age: Lens.fromProp<Person, "age">("age"),
};

But now my brain hurts. ๐Ÿ˜…

Am i mad to try this?

tag recursion

I'm currently writing a lib to generate Generators for 'testcheck' (https://github.com/leebyron/testcheck-js) library (btw any constructive criticism on that choice is welcome)

I've covered so far:

interface, string, number, boolean, literal, union, intersection (of recursive unions/intersections of interfaces/partials), array, partial
I can't implement recursion because io-ts currently does not provide a tag on it.
Can it be done?

Thanks!

Problem with source-map-loader when using webpack: "The keyword 'interface' is reserved"

(Issue using io-ts 0.5.0, 0.5.1 and 0.6.0)
The problem is that Webpack transform userland code of io-ts like this:

import { _A, array, boolean, failure, interface, intersection, number, partial, recursion, string, success } from 'io-ts/index';

importing directly 'interface' even if the original code was using it like so:

import * as t from 'io-ts';

const myType = t.interface({
...
})

Resulting in an error when source-map-loader kicks in:

The keyword 'interface' is reserved (1:38) You may need an appropriate loader to handle this file type.

Also this generates a visual error in visual code studio.

A solution would be to provide an alternative keyword.

Overall experience with the library

Hello everyone!

I'd like to wrap up here all the thoughts I have regarding the library so far which I think might worth consideration. They might be valid or not the intention is mostly to open discussion which might lead to some decisions regarding API and roadmap.

Background

For over a year now I am playing Elm programming language. It is quite good form different points of view, however is not applicable for every project.

For sometime now I have been developing React Native application and thus had to consider usage of TypeScript to achieve some kind of reliability close to Elm in it. One thing that was still uncovered - API data type safety. In Elm it is solved with decoders and I always enjoyed being able to caught errors with the data being incorrect early in my code. Thats why I was looking for something in this area and io-ts appeared to be brilliant solution for this problem! Than you @gcanti for making it!

Issues with the library I have so far

Just to underline it a bit: my primary use case is typifying API responses. Especially when API is out of your control.

Validation vs Decoding

As I already wrote here it seems like library already go a bit beyond just simple validation. I believe this is a good and useful feature and we can a bit change API direction to support it more seamlessly.

Let's say we do receive date as ISO string from the API. Most of the time string do not make a lot of sense for us because we want to be able work with dates. If we will remove ability to modify data during "validation" one would need to define two types within his program. One type with date being string which you can get immediately from your validator and one type with date being Date which you will return from your function.

The functionality described above is already possible, however is not really aligned with "validation" meaning and thats why might be not clear/misleading for library consumers.

One the other hand it looks like part of io-ts audience as @gcanti himself come to this library in order to have similar "decoding" they have in Elm/PureScript/Haskell/... so decoding will seem more familiar to them.

Either vs Promise

Since I am using this library for API data validation I have to use it in Promise context. For example:

axios
  .get(url)
  .then(res => validate(res.data, schema).fold(/* convert to Promise */)

Using Either might be more aligned to how it is done in functional languages, but in JavaScript/TypeScript land Promise seem to be main concept for this kind of cases. I would say that in order to simplify API and being more friendly to JS/TS people we should consider using promises as a default.

Make reporting nicer and part of core

I believe that most of the consumers do not need ability to customize error reporting somehow. When validation fails you most likely just want to know exact field where it failed, what was expected and what was given. With reporters as is I had quite bad time figuring out what is the exact issue with my data. So I ended up writing following:

function decode<T>(value: any, type: t.Type<T>): Promise<T> {
  const validation = t.validate(value, type);
  return validation
    .fold(
      ([e, ..._unused]) => {
        const actualErr = _.last(e.context);
        const errPath = e.context
          .map(c => c.key)
          .filter(key => key.length)
          .join('.');
        return Promise
          .reject(`${errPath} expected ${actualErr.type.name}, but got ${JSON.stringify(_.get(value, errPath))}`);
      },
      value => Promise.resolve(value),
    );
}

With this I am able to quickly find what is wrong in my validator definition or in data I receive form the API.

In combination with previous topic I would personally enjoy API close to this:

axios
  .get(url)
  .then(res => t.decode(res.data, schema))

// Error: some.nested.[0].field is expected to be string, but is given number: 123

Validator could be chainable

For example in case of data decoding instead of

const DateFromISOString = new t.Type<Date>(
  'Date',
  (v, c) => {
    const d = new Date(v)
    return isNaN(d.getTime()) ? t.failure<Date>(v, c) : t.success(d)
  }
)

one can leverage power of existing decoder

t.string.then(str => {
  const d = new Date(v)
  return isNaN(d.getTime()) ? t.failure<Date>(str) : t.success(d)
})

And there is no need to pass context down.

Add the ability to serialize types

io-ts is superb not only for validation but also for deserialization. And if I have this structures:

// This is the shape that comes from server
type TData {
  id?: number
}
// And this is how I want to deal with it on the client
type T = {
  id: Option<number>
}

, I can easily deserialize TData to T using validation method. But when I need to pass the data to the server, I should do serialization manually.

What do you think about adding serialize method to the Type?

Can't migrate to version 0.5.0

The code below produces very interesting effects.

  • The compiler of version 2.4.0 just hangs
  • The compiler of version 2.4.1 crashes with OOM
  • The compiler of version 2.3.4 produces error.
    Code:
export const AccountTypeEtType = t.union([
  t.literal("ISSUE"),
  t.literal("EMITENT"),
  t.literal("OWNER"),
  t.literal("NOMINEE"),
  t.literal("TRUSTEE"),
  t.literal("PLEDGEE"),
  t.literal("UNKNOWN"),
  t.literal("NOTARY"),
  t.literal("RIGHTS"),
  t.literal("TREASURY"),
  t.literal("DEPOSIT"),
  t.literal("NDCD"),
  t.literal("FORNOMINEE"),
  t.literal("FORAUTHOR"),
  t.literal("DEPOPROG")
], "AccountTypeEt")
export type AccountTypeEt = t.TypeOf<typeof AccountTypeEtType>

Error:

src/core/types/polaris.ts(8,42): error TS2345: Argument of type '[LiteralType<"ISSUE">, LiteralType<"EMITENT">, LiteralType<"OWNER">, LiteralType<"NOMINEE">, Lite...' is not assignable to parameter of type '[LiteralType<"ISSUE">, LiteralType<"EMITENT">]'.
  Types of property 'pop' are incompatible.
    Type '() => LiteralType<"ISSUE"> | LiteralType<"EMITENT"> | LiteralType<"OWNER"> | LiteralType<"NOMINEE...' is not assignable to type '() => LiteralType<"ISSUE"> | LiteralType<"EMITENT"> | undefined'.
      Type 'LiteralType<"ISSUE"> | LiteralType<"EMITENT"> | LiteralType<"OWNER"> | LiteralType<"NOMINEE"> | L...' is not assignable to type 'LiteralType<"ISSUE"> | LiteralType<"EMITENT"> | undefined'.
        Type 'LiteralType<"OWNER">' is not assignable to type 'LiteralType<"ISSUE"> | LiteralType<"EMITENT"> | undefined'.
          Type 'LiteralType<"OWNER">' is not assignable to type 'LiteralType<"EMITENT">'.
            Type '"OWNER"' is not assignable to type '"EMITENT"'.

The problem disappears when I leave only 5 enum values. I know that the union function is specialized for up to 5 type parameters maximum. But it worked with the io-ts version 0.4.0.

Document Context type

Hi there, thanks for the fantastic library.

I observe that, when validation fails, the last item in the Context array will be the key that failed. Is this correct?

I wonder if the data structure could be more obvious here?

Alternatively, could we add some docs to note this behaviour?

Thanks!

Flow, TypeScript, tcomb...

Previous discussion: gcanti/tcomb-form#377 (comment)

@eteeselink

what's your motivation for moving from flow to ts?

There's plenty of reasons (I should write down a list..), maturity over all.

Do you consider it the spiritual successor to tcomb?

Yes and no, io-ts has a narrowed scope and wants to align with the TypeScript type system at the cost of possibly sacrifice some feature

I'm only sad that I can't use typescript class or interface syntax with it

Not sure I'm following

What's the point of runtime type checking inside a 100% typescript project

Mainly for IO validation but there are 4 use cases I'm interesting in

  • IO validation
  • runtime type introspection (which makes possible something like tcomb-form)
  • "static types extraction"
  • refinements

An example (POC) https://github.com/gcanti/prop-types-ts. There you get static AND runtime safety (so you please both TypeScript and non TypeScript users) with a single schema definition

Tagged Union of Types

I really love this library and was hoping to be able to write something similar to Haskell's generic functions. But to leverage totality checking I need to be able to form some kind of tagged union of the Types in this package. But I'm having some trouble.

I basically want something like this, where I can pass a Type object and a map of folds to handle each type of Type to build database schemas and stuff from the Type definition.

Any help would be greatly appreciated.

const assertNever = (x: never): never => {throw new Error(`Unexpected object ${x}`)}

type Types<A = any, B extends Props = {}, C extends Any[] = Any[], D = any> =
    | Type<A>
    | InterfaceType<B>
    | UnionType<C, D>

function genericFold<A = any, B extends Props = {}, C extends Any[] = Any[], D = any>(x : Types<A, B, C, D>, f: any) {
    if (x instanceof UnionType) {
        // Do something
        if (x.t instanceof Type) genericFold(x.t);
    } else if (x instanceof InterfaceType) {

    } else if (x instanceof Type) {

    } else {
        return assertNever(x);
    }
}

remove fromValidation API

Because doesn't convey enough info. A better version could be defined in userland using validation, fold and default (or custom) reporters

Default values

What do you think about having an ability to define default value. So if value is null or undefined it is got swapped with the value provided.

t.fromValidation(null, t.withDefault(t.string, 'hello world')) // 'hello world'

How to define inheritance, e.g. `interface Derived extends Base`?

// export interface Base {
//   foo: string;
// }

export const BaseIO = iots.interface({
  foo: iots.string,
});
export type = iots.TypeOf<typeof BaseIO>;

// export interface Derived extends Base {
//   bar: string
// }

export const DerivedIO = iots.interface({
  bar: iots.string,
});
// How to add `BaseIO` as an ancestor?
export type Derived = iots.TypeOf<typeof DerivedIO>;

Compare runtime types?

I need to compare runtime types, i.e. understand whether two types are identical, one a sub-type of the other, or incompatible. It seems that no straightforward way to achieve this was built into the library, or am I missing something?

I'd like to create a function like:

function isSubtype(subtype: Type<A>, supertype: Type<B>): boolean

Currently I can look at the name property, which is good for basic types (e.g. t.string.name === 'string') but must be parsed when dealing with complex types (e.g. t.array(t.boolean).name === 'Array<boolean>'). Any better option?

Type Parameters?

As I'm currently testing to use io-ts with ts-runtime I was wondering, if generics are on the roadmap?

type Foo<T extends any = null> = {
  prop: T;
};

let foo: Foo<number> = { prop: 1 };

To validate the TypeScript code from above with, e.g., something like this in JavaScript:

// const Foo = ...
let foo = t.validate({ prop: 1 }, t.get(Foo, t.number));

I'm trying to get as close as possible to TypeScript's static type check behavior at runtime with ts-runtime (which I still consider experimental, but I want to move it forward), and I think this library offers more flexibility to introduce custom types/features at a smaller footprint (than flow-runtime).

Serialization and deserialization

I'm trying to create a typed API using io-ts.

If I want to send objects that are not primitives I need a two-way conversion from and to JSON. The simplest example is a Date.

I define the API as follows

export type ApiType = {
    [name: string]: {
        request: t.Type<any>,
        response: t.Type<any>,
    }
};

export const Api = {
    addObject: {
        request: t.interface({
            name: t.string
        }),
        response: t.interface({
            id: t.number,
            created: t.string
        })
    }
}
export type ApiServer<Api extends ApiType> = {
    [k in keyof Api]: (request: t.TypeOf<Api[k]["request"]>) => Promise<t.TypeOf<Api[k]["response"]>>;
}

// called on server
export function makeServer<Api extends ApiType>(api: Api, server: Partial<ApiServer<Api>>): Koa.Middleware {...}
// called on client
export function makeClient<Api extends ApiType>(api: Api): ApiServer<Api> {...}

This already works well, but currently I need to manually serialize and deserialize created, which should actually be a ISO Date String. Using the DateFromString example from the readme I can solve the problem halfway, but I still need to do serialization manually.
I'm not sure how to solve this problem nicely without changing io-ts source code.

I basically need a way to define a type with a SerializedType (string), a DeserializedType (Date), a validation/deserialization function (new Date(string), as io-ts can already do) and a serialization function (date.toISOString())

fail to generate definitions

Because interfaceType is not exported, it triggers

error TS4023: Exported variable 'i' has or is using name 'interfaceType' from external module "[...]/node_modules/io-ts/lib/index" but cannot be named.

Add `keyof` combinator

Usage

const AlertType = t.keyof({
  info: true,
  success: true
})

type AlertTypeT = t.TypeOf<typeof AlertType>; // => same as type AlertTypeT = 'info' | 'success';

Incorrect compile time type for dictionary

Given

const Dic = t.dictionary(t.literal('foo'), t.string);
type DicT = t.TypeOf<typeof Dic>;

const dic: DicT = {
    foo: 'bar',
    baz: 'bob'
};

I expect this to error because baz in not an allowed property. DicT should be equivalent to { [key in 'foo']: string } (mapped type with string literals), however it is equivalent to { [key: string]: string }.

Is this intentional or can we fix it?

JSON parsing

Hey!

Would it make sense for this library to include a function equivalent to t.validate, but accepting a string instead of a parsed object?

Perhaps:

<T>(type: t.Type<T>, value: string) => Either<ValidationErrors | ParsingError, T>

Currently I am wrapping t.validate to do this myself, but it could be useful to other people. Most often, when performing IO validation, you start with a string?

Thanks,
Olly

Interop with other Either types

It would be nice if io-ts worked with different Either types. Perhaps we could provide an adapter? If we did this, we'd need to think about which parts of the Either type are required.

For context, on one project I am experimenting with using the Either type from funfix: https://funfix.org/, and I want to avoid duplicate Either types in my project.

Serialization of data types

This is what I get from Option now

import { fromNullable } from 'fp-ts/lib/Option'

const x = fromNullable(1)
const y = fromNullable(null)

console.log(JSON.stringify(x)) // => {"value":1}
console.log(JSON.stringify(y)) // => {}

In order to behave more like flow-static-land which doesn't change the underline representation when you call inj / prj, would be useful a toNullable function

declare function toNullable<A>(fa: HKTOption<A>): A | null

such that the pair inj / prj is equivalent to the pair fromNullable / toNullable. Also both None and Some could implement the toJSON method.

Proof of concept

export class None<A> {
  ...
  toJSON(): null {
    return null
  }
}

export class Some<A> {
  ...
  toJSON(): A {
    return this.value
  }
}

export function fromNullable<A>(a: A | null | undefined): Option<A> {
  return a == null ? none : new Some(a)
}

export function toNullable<A>(fa: HKTOption<A>): A | null {
  return (fa as Option<A>).toJSON()
}

Result

const x = fromNullable(1)
const y = fromNullable(null)

console.log(toNullable(x)) // => 1
console.log(toNullable(y)) // => null

console.log(JSON.stringify(x)) // => 1
console.log(JSON.stringify(y)) // => null

/cc @danielepolencic

Simplify Reporter type

The docs state a Reporter type as:

interface Reporter<A> {
  report: (validation: Validation<any>) => A;
}

However, it could just be a function instead:

(validation: Validation<any>) => A

This is a nitpick, but I noticed I accidentally deviated from your standard in my library, https://github.com/OliverJAsh/io-ts-reporters, where I just export a reporter function.

Does it make sense to simplify the type here, or are there reasons for keeping it as a function on an object?

Simpler optional props syntax?

I'm using io-ts in a current project and its working quite well, but there is one thing I'm not so keen on.

The current mechanism for creating an interface with some optional props is rather verbose.

const A = t.interface({
  foo: t.string
})

const B = t.partial({
  bar: t.number
})

const C = t.intersection([A, B])

type CT = t.TypeOf<typeof C>

Would it be possible to have something like the following instead, even if, under the hood, it ended up creating the more complex version?

const A = t.interface({
  foo: t.string,
  bar: t.optional(t.number)
})

type CT = t.TypeOf<typeof C>

I haven't yet looked at the code, but I would think you could iterate over the props and collect the ones set to optional and create a t.interface() and a t.partial() and combine them with t.intersection() as above.

add mapWithName and Functor instance

function mapWithName<A, B>(f: (a: A) => B, type: t.Type<A>, name: string): t.Type<B> {
  return new t.Type(
    name,
    (v, c) => type.validate(v, c).map(f)
  )
}

Embedded optional types?

The documentation suggests making optional members of a type using intersection:

const MandatoryRunSettings = t.interface({
	appName: FilledString,
});

const OptionalRunSettings = t.partial({
	shortAppName: FilledString,
});

const RunSettings = t.intersection([MandatoryRunSettings, OptionalRunSettings]);

However this can start to look a bit ugly if you have lots of members of a type and want related members to be next to one another. Could we implement an "optional" tag for types so that we can just associate that data with the type? Then we could say something like:

const RunSettings = t.interface({
	appName: FilledString,
	shortAppName: t.OptionalType<FilledString>,
});

Add a type guard named `is` to `Type`

Example

const Person = t.record({
  name: t.string
})

function f(p: {}): string | undefined {
  if (Person.is(p)) {
    // here p is narrowed to type { name: string }
    return p.name
  }
}

Wrong type inference?

While trying to create an example of the issue #54, I created repo https://github.com/vegansk/io-ts-type-issue with some parts of the bigger application. And I ran into another issue: the code in this line compiles ok despite the fact that it lacks the required field taInfo. Here is the link to it's description

The function mkType is implemented in src/adapters.ts like this:

export function mkType<R extends t.Props, O extends t.Props>(
    required: R, optional: O, name?: string
) {
    return t.readonly(t.intersection([t.interface(required), t.partial(optional)]), name)
}

But if I change the value of another required field, for example taDocNum of type string to some number, the compiler fails with error:

src/index.ts(5,11): error TS2352: Type '{ version: string; uid: string; msgHeader: { sender: { id: string; participantType: "REG"; name: ...' cannot be converted to type 'Readonly<InterfaceOf<{ version: StringType; msgHeader: ReadonlyType<IntersectionType<[InterfaceTy...'.
  Type '{ version: string; uid: string; msgHeader: { sender: { id: string; participantType: "REG"; name: ...' is not comparable to type 'Readonly<InterfaceOf<{ version: StringType; msgHeader: ReadonlyType<IntersectionType<[InterfaceTy...'.
    Types of property 'taDocInfo' are incompatible.
      Type '{ taDocNum: number; taDocDate: Date; }' is not comparable to type 'Readonly<InterfaceOf<{ taInfo: ReadonlyType<IntersectionType<[InterfaceType<{ taInfo: ReadonlyTyp...'.
        Property 'taInfo' is missing in type '{ taDocNum: number; taDocDate: Date; }'.

Is it the problem in my code, in io-ts (version 0.5.0 is used), or in the typescript compiler?

UPDATE: checked with typescript 2.3.4 and 2.4.0, both behaves the same.

Incorrect inference of nested objects

Code sample

const nestedObj = t.object({
  foo: t.object({
    bar: t.string,
  }),
});

type NestedObj = t.TypeOf<typeof nestedObj>;

Actual behaviour

type NestedObj = {
  foo: any
}

Expected behaviour

type NestedObj = {
  foo: {
    bar: string
  }
}

Pattern matching / catamorphism (unions)

import * as t from 'io-ts'

type Match<RT extends t.Any, R> = (value: t.TypeOf<RT>) => R
type Clause<RT extends t.Any, R> = [RT, Match<RT, R>]

export function match<A extends t.Any, B extends t.Any, R>(value: t.TypeOf<A> | t.TypeOf<B>, clauses: [Clause<A, R>, Clause<B, R>]): R
export function match<A extends t.Any, B extends t.Any, C extends t.Any, R>(value: t.TypeOf<A> | t.TypeOf<B> | t.TypeOf<C>, clauses: [Clause<A, R>, Clause<B, R>, Clause<C, R>]): R
// more overloadings here...
export function match<R>(value: any, clauses: Array<Clause<t.Any, R>>): R {
  for (let i = 0, len = clauses.length; i < len; i++) {
    const [type, match]: [t.Any, Match<t.Any, R>] = clauses[i]
    if (type.is(value)) {
      return match(value)
    }
  }
  throw new Error('No match')
}

// arguments types are inferred correctly
const x = match('hello', [ // value: string | number
  [t.string, s => s.length], // s: string
  [t.number, n => n] // n: number
])

const y = match(2, [
  [t.string, s => s.length],
  [t.number, n => n]
])

console.log(x) // => 5
console.log(y) // => 2

const z = match(true, [ // <= error Argument of type 'true' is not assignable to parameter of type 'string | number'
  [t.string, s => s.length],
  [t.number, n => n]
])

JSONFromString type

Something along the lines of

type JSONObject = { [key: string]: JSON }
interface JSONArray extends Array<JSON> {}
type JSON = null | string | number | boolean | JSONArray | JSONObject

const JSONType = new t.Type<JSON>(
  'JSON',
  (v, c) => t.string.validate(v, c).chain(s => {
    try {
      return t.success(JSON.parse(s))
    } catch (e) {
      return t.failure(v, c)
    }
  })
)

Returns a JSON from a string

Instances for FP-TS Option and such?

Are there any plans to add support for fp-ts's Option in io-ts? It would make life a lot easier for me, but maybe it makes for a mess because that same data can't be converted back to JSON for free.

Incorrect maybe inference

Let's take a look at different examples with maybe

// As expected
t.TypeOf<typeof t.maybe(t.string)> // undefined | null | string

// Wrong
t.TypeOf<typeof t.maybe(t.string)> // t.MaybeOf<t.Type<string>>[]

// Wrong
t.TypeOf<typeof t.object({ foo: t.maybe(t.string) })> // { foo: t.MaybeOf<t.Type<string>> }

I would personally expect maybe to be inferred as undefined | null | Type in every case, because JavaScript primitive types make more sense for me as final consumer. I do know how null or undefined behave, but I have no idea what kind of beast is t.MaybeOf<t.Type<string>>[].

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.