Skip to content

Instantly share code, notes, and snippets.

@joshburgess
Last active February 5, 2019 03:48
Show Gist options
  • Save joshburgess/462c35963c8a6166f369e05325c309d7 to your computer and use it in GitHub Desktop.
Save joshburgess/462c35963c8a6166f369e05325c309d7 to your computer and use it in GitHub Desktop.
io-ts Option encoding/decoding
import * as t from 'io-ts'
import { Option, None, Some, none, fromEither } from 'fp-ts/lib/Option'
export type JSONNone = { _tag: 'None' }
export type JSONSome<A> = { _tag: 'Some'; value: A }
export type JSONOption<A> = JSONNone | JSONSome<A>
export const jsonNone: JSONOption<never> = { _tag: 'None' }
export const jsonSome = <A>(value: A): JSONOption<A> => {
return { _tag: 'Some', value }
}
export const isJSONNone = <A>(x: JSONOption<A>): x is JSONNone =>
x._tag === 'None'
export const isJSONSome = <A>(x: JSONOption<A>): x is JSONSome<A> =>
x._tag === 'Some'
export const fold = <A, R>(
fa: JSONOption<A>,
onNone: R,
onSome: (value0: A) => R,
): R => (isJSONNone(fa) ? onNone : onSome(fa.value))
export const createOptionFromJSON = <C extends t.Mixed>(
codec: C,
name: string = `Option<${codec.name}>`,
): t.Type<Option<t.TypeOf<C>>, JSONOption<t.OutputOf<C>>, t.mixed> => {
const JSONOption_ = t.taggedUnion('_tag', [
t.type({ _tag: t.literal('None') }),
t.type({ _tag: t.literal('Some'), value: codec }),
])
return new t.Type<Option<t.TypeOf<C>>, JSONOption<t.OutputOf<C>>, t.mixed>(
name,
(u): u is Option<t.TypeOf<C>> =>
u instanceof None || (u instanceof Some && codec.is(u.value)),
(u, c) =>
JSONOption_.validate(u, c).chain(o =>
t.success(fold(o, none, a => fromEither(codec.decode(a)))),
),
o => o.fold(jsonNone, a => jsonSome(codec.encode(a))),
)
}
import * as t from 'io-ts'
import { Either, right } from 'fp-ts/lib/Either'
import { Option, some, none } from 'fp-ts/lib/Option'
import { createOptionFromJSON } from './io-ts-option-encoding-decoding'
type RoundTripTest<A> = {
result: Either<t.Errors, Option<A>>
expected: Either<t.Errors, Option<A>>
}
const roundTrip = <A>(codec: t.Type<A>) => {
const OptionFromJSON = createOptionFromJSON(codec)
return (ma: Option<A>): RoundTripTest<A> => {
const stringified = JSON.stringify(ma)
const parsed = JSON.parse(stringified)
const result = OptionFromJSON.decode(parsed)
const expected = right<t.Errors, Option<A>>(ma)
return { result, expected }
}
}
const roundTripTest = roundTrip(t.number)
describe('createOptionFromJSON', () => {
it('properly encodes and decodes Some', () => {
const numOpt = some(1)
const { result, expected } = roundTripTest(numOpt)
expect(result).toEqual(expected)
})
it('properly encodes and decodes None', () => {
const { result, expected } = roundTripTest(none)
expect(result).toEqual(expected)
})
})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment