Adds pattern matching, optional properties, and several other helpers and types, to io-ts.
npm install io-ts-extraAdds pattern matching, optional properties, and several other helpers and types, to io-ts.



* Pattern matching
* Optional properties
* Advanced refinement types
* Regex types
* Parser helpers
The maintainers of io-ts are (rightly) strict about keeping the API surface small and manageable, and the implementation clean. As a result, io-ts is a powerful but somewhat low-level framework.
This library implements some higher-level concepts for use in real-life applications with complex requirements - combinators, utilities, parsers, reporters etc.
io-ts-types exists for similar reasons. This library will aim to be orthogonal to io-ts-types, and avoid re-inventing the wheel by exposing types that already exist there.
io-ts-extra will also aim to provide more high-level utilities and combinators than pre-defined codecs.
Philosophically, this library will skew slightly more towards pragmatism at the expense of type soundness - for example the stance on t.refinement vs t.brand.
This package is also less mature. It's currently in v0, so will have a different release cadence than io-ts-types.
#### match
Match an object against a number of cases. Loosely based on Scala's pattern matching.
##### Example
``typescriptthe message is ${s}
// get a value which could be a string or a number:
const value = Math.random() < 0.5 ? 'foo' : Math.random() * 10
const stringified = match(value)
.case(String, s => )the number is ${n}
.case(7, () => 'exactly seven')
.case(Number, n => )`
.get()
Under the hood, io-ts is used for validation. The first argument can be a "shorthand" for a type, but you can also pass in io-ts codecs directly for more complex types:
##### Example
`typescriptthe number is ${n}
// get a value which could be a string or a number:
const value = Math.random() < 0.5 ? 'foo' : 123
const stringified = match(value)
.case(t.number, n => )the message is ${s}
.case(t.string, s => )`
.get()
you can use a predicate function or t.refinement for the equivalent of scala's case x: Int if x > 2:
##### Example
`typescriptbig number: ${n}
// value which could be a string, or a real number in 0, 10):
const value = Math.random() < 0.5 ? 'foo' : Math.random() * 10
const stringified = match(value)
.case(Number, n => n > 2, n => )small number: ${n}
.case(Number, n => )not a number: ${x}
.default(x => )`
.get()
##### Example
`typescriptbig number: ${n}
// value which could be a string, or a real number in [0, 10):
const value = Math.random() < 0.5 ? 'foo' : Math.random() * 10
const stringified = match(value)
.case(t.refinement(t.number, n => n > 2), n => )small number: ${n}
.case(t.number, n => )not a number: ${x}
.default(x => )`
.get()
note: when using predicates or t.refinement, the type being refined is not considered exhaustively matched, so you'll usually need to add a non-refined option, or you can also use .default as a fallback case (the equivalent of .case(t.any, ...))
##### Params
|name|description |
|----|--------------------------------|
|obj |the object to be pattern-matched|
#### [matcher
Like @see match but no object is passed in when constructing the case statements. Instead .get is a function into which a value should be passed.
##### Example
`typescript
const Email = t.type({sender: t.string, subject: t.string, body: t.string})
const SMS = t.type({from: t.string, content: t.string})
const Message = t.union([Email, SMS])
type Message = typeof Message._A
const content = matcher
.case(SMS, s => s.content)
.case(Email, e => e.subject + '\n\n' + e.body)
.get({from: '123', content: 'hello'})
expect(content).toEqual('hello')
`
The function returned by .get is stateless and has no this context, you can store it in a variable and pass it around:
##### Example
`typescript
const getContent = matcher
.case(SMS, s => s.content)
.case(Email, e => e.subject + '\n\n' + e.body)
.get
const allMessages: Message[] = getAllMessages();
const contents = allMessages.map(getContent);
`
#### Shorthand
The "shorthand" format for type specifications maps to io-ts types as follows:
#### codecFromShorthand
Gets an io-ts codec from a shorthand input:
|shorthand|io-ts type|
|-|-|
|String, Number, Boolean|t.string, t.number, t.boolean|7
|Literal raw strings, numbers and booleans e.g. or 'foo'|t.literal(7), t.literal('foo') etc.|/^foo/
|Regexes e.g. |see regexp|null
| and undefined|t.null and t.undefined|undefined
|No input (_not_ the same as explicitly passing )|t.unknown|{ foo: String, bar: { baz: Number } }
|Objects e.g. |t.type(...) e.g. t.type({foo: t.string, bar: t.type({ baz: t.number }) })Array
||t.unknownArray|Object
||t.object|[String]
|One-element arrays e.g. |t.array(...) e.g. t.array(t.string)|[2, [String, Number]]
|Tuples with explicit length e.g. |t.tuple e.g. t.tuple([t.string, t.number])|
|io-ts codecs|unchanged|
|Unions, intersections, partials, tuples with more than 3 elements, and other complex types|not supported, except by passing in an io-ts codec|
#### sparseType
Can be used much like t.type from io-ts, but any property types wrapped with optional from this package need not be supplied. Roughly equivalent to using t.intersection with t.type and t.partial.
##### Example
`typescript
const Person = sparseType({
name: t.string,
age: optional(t.number),
})
// no error - age is optional`
const bob: typeof Person._A = { name: 'bob' }
##### Params
|name |description |
|-----|----------------------------------------------|
|props|equivalent to the props passed into t.type|
##### Returns
a type with props field, so the result can be introspected similarly to a type built witht.type or t.partial - which isn't the case if you manually use t.intersection([t.type({...}), t.partial({...})])
#### optional
unions the passed-in type with null and undefined.
#### mapper
A helper for building "parser-decoder" types - that is, types that validate an input, transform it into another type, and then validate the target type.
##### Example
`typescript`
const StringsFromMixedArray = mapper(
t.array(t.any),
t.array(t.string),
mixedArray => mixedArray.filter(value => typeof value === 'string')
)
StringsFromMixedArray.decode(['a', 1, 'b', 2]) // right(['a', 'b'])
StringsFromMixedArray.decode('not an array') // left(...)
##### Params
|name |description |
|-----|-----------------------------------------------|
|from |the expected type of input value |
|to |the expected type of the decoded value |
|map |transform (decode) a from type to a to type|to
|unmap|transfrom a type back to a from type |
#### parser
A helper for parsing strings into other types. A wrapper around mapper where the from type is t.string.
##### Example
`typescript`
const IntFromString = parser(t.Int, parseFloat)
IntFromString.decode('123') // right(123)
IntFromString.decode('123.4') // left(...)
IntFromString.decode('not a number') // left(...)
IntFromString.decode(123) // left(...)
##### Params
|name |description |
|------|--------------------------------------------|
|type |the target type |
|decode|transform a string into the target type |
|encode|transform the target type back into a string|
#### strict
Like t.type, but fails when any properties not specified in props are defined.
##### Example
`typescript
const Person = strict({name: t.string, age: t.number})
expectRight(Person.decode({name: 'Alice', age: 30}))
expectLeft(Person.decode({name: 'Bob', age: 30, unexpectedProp: 'abc'}))
expectRight(Person.decode({name: 'Bob', age: 30, unexpectedProp: undefined}))
`
##### Params
|name |description |
|-----|-------------------------------------------------------|
|props|dictionary of properties, same as the input to t.type|
|name |optional type name |
note:
- additional properties explicitly set to undefined _are_ permitted.sparseType
- internally, is used, so optional properties are supported.
#### narrow
Like io-ts's refinement type but:
1. Not deprecated (see https://github.com/gcanti/io-ts/issues/373)
2. Passes in Context to the predicate argument, so you can check parent key names etc.
3. Optionally allows returning another io-ts codec instead of a boolean for better error messages.
##### Example
`typescript${database.username}:${database.password}
const CloudResources = narrow(
t.type({
database: t.type({username: t.string, password: t.string}),
service: t.type({dbConnectionString: t.string}),
}),
({database}) => t.type({
service: t.type({dbConnectionString: t.literal()}),
})
)
const valid = CloudResources.decode({
database: {username: 'user', password: 'pass'},
service: {dbConnectionString: 'user:pass'},
})
// returns a Right
const invalid = CloudResources.decode({
database: {username: 'user', password: 'pass'},
service: {dbConnectionString: 'user:wrongpassword'},
})
// returns a Left - service.dbConnectionString expected "user:pass", but got "user:wrongpassword"`
#### validationErrors
Similar to io-ts's PathReporter, but gives slightly less verbose output.
##### Params
|name |description |
|----------|------------------------------------------------------------------------------------------------------------------------------------|
|validation|Usually the result of calling .decode with an io-ts codec. |
|typeAlias |io-ts type names can be verbose. If the type you're using doesn't have a name,
you can use this to keep error messages shorter.|
#### regexp
A type which validates its input as a string, then decodes with String.prototype.match, succeeding with the RegExpMatchArray result if a match is found, and failing if no match is found.
##### Example
`typescript`
const AllCaps = regexp(/\b([A-Z]+)\b/)
AllCaps.decode('HELLO') // right([ 'HELLO', index: 0, input: 'HELLO' ])
AllCaps.decode('hello') // left(...)
AllCaps.decode(123) // left(...)
#### instanceOf
Validates that a value is an instance of a class using the instanceof operator
##### Example
`typescript``
const DateType = instanceOf(Date)
DateType.is(new Date()) // right(Date(...))
DateType.is('abc') // left(...)