Repository: gcanti/io-ts-types (ts)

Issue:
Refactor createOptionFromJSON and createEitherFromJSON
`createOptionFromJSON` should handle the following encoding

```ts
type JSONOption<A> = { _tag: 'None' } | { _tag: 'Some'; value: A }
```

`createEitherFromJSON` should handle the following encoding

```ts
type JSONEither<L, A> = { _tag: 'left', value: L } | { _tag: 'Right'; value: A }
```

These changes would allow handy roundtrips when using `JSON.stringify`

```ts
import * as assert from 'assert'
import { right } from 'fp-ts/lib/Either'

const testRoundtrip = <A>(codec: t.Type<A>) => {
  const OptionFromJSON = createOptionFromJSON(codec)
  return (ma: Option<A>) => {
    const result = OptionFromJSON.decode(JSON.parse(JSON.stringify(ma)))
    assert.deepStrictEqual(result, right(ma))
  }
}

const test = testRoundtrip(t.number)

test(none)
test(some(1))
```

Pull request description:
closes #79

Patch:
diff --git a/docs/modules/IntFromString.ts.md b/docs/modules/IntFromString.ts.md
index 23c39cc..f24d764 100644
--- a/docs/modules/IntFromString.ts.md
+++ b/docs/modules/IntFromString.ts.md
@@ -1,6 +1,6 @@
 ---
 title: IntFromString.ts
-nav_order: 16
+nav_order: 15
 parent: Modules
 ---
 
@@ -8,10 +8,19 @@ parent: Modules
 
 <h2 class="text-delta">Table of contents</h2>
 
+- [IntFromStringC (interface)](#intfromstringc-interface)
 - [IntFromString (constant)](#intfromstring-constant)
 
 ---
 
+# IntFromStringC (interface)
+
+**Signature**
+
+```ts
+export interface IntFromStringC extends t.Type<t.Int, string, unknown> {}
+```
+
 # IntFromString (constant)
 
 A codec that succeeds if a string can be parsed to an integer
@@ -19,7 +28,7 @@ A codec that succeeds if a string can be parsed to an integer
 **Signature**
 
 ```ts
-export const IntFromString: t.Type<t.Int, string, unknown> = ...
+export const IntFromString: IntFromStringC = ...
 ```
 
 **Example**
@@ -34,3 +43,5 @@ assert.deepStrictEqual(PathReporter.report(IntFromString.decode('1.1')), [
   'Invalid value "1.1" supplied to : IntFromString'
 ])
 ```
+
+Added in v0.4.4
diff --git a/docs/modules/JSON/JSONFromString.ts.md b/docs/modules/JSON/JSONFromString.ts.md
index 6a8cf1b..7116769 100644
--- a/docs/modules/JSON/JSONFromString.ts.md
+++ b/docs/modules/JSON/JSONFromString.ts.md
@@ -1,6 +1,6 @@
 ---
 title: JSON/JSONFromString.ts
-nav_order: 17
+nav_order: 16
 parent: Modules
 ---
 
diff --git a/docs/modules/JSON/JSONTypeRT.ts.md b/docs/modules/JSON/JSONTypeRT.ts.md
index 3f63717..6e1f120 100644
--- a/docs/modules/JSON/JSONTypeRT.ts.md
+++ b/docs/modules/JSON/JSONTypeRT.ts.md
@@ -1,6 +1,6 @@
 ---
 title: JSON/JSONTypeRT.ts
-nav_order: 18
+nav_order: 17
 parent: Modules
 ---
 
diff --git a/docs/modules/clone.ts.md b/docs/modules/clone.ts.md
index 99f5dfa..9b7879d 100644
--- a/docs/modules/clone.ts.md
+++ b/docs/modules/clone.ts.md
@@ -30,3 +30,5 @@ import * as t from 'io-ts'
 
 assert.deepStrictEqual(clone(t.string), t.string)
 ```
+
+Added in v0.4.3
diff --git a/docs/modules/eitherFromJSON.ts.md b/docs/modules/eitherFromJSON.ts.md
new file mode 100644
index 0000000..f408b49
--- /dev/null
+++ b/docs/modules/eitherFromJSON.ts.md
@@ -0,0 +1,68 @@
+---
+title: eitherFromJSON.ts
+nav_order: 7
+parent: Modules
+---
+
+---
+
+<h2 class="text-delta">Table of contents</h2>
+
+- [EitherFromJSONC (interface)](#eitherfromjsonc-interface)
+- [JSONEither (type alias)](#jsoneither-type-alias)
+- [eitherFromJSON (function)](#eitherfromjson-function)
+
+---
+
+# EitherFromJSONC (interface)
+
+**Signature**
+
+```ts
+export interface EitherFromJSONC<L extends t.Mixed, R extends t.Mixed>
+  extends t.Type<Either<t.TypeOf<L>, t.TypeOf<R>>, JSONEither<t.OutputOf<L>, t.OutputOf<R>>, unknown> {}
+```
+
+# JSONEither (type alias)
+
+**Signature**
+
+```ts
+export type JSONEither<L, A> = { _tag: 'Left'; value: L } | { _tag: 'Right'; value: A }
+```
+
+# eitherFromJSON (function)
+
+Given a codec representing a type `L` and a codec representing a type `A`, returns a codec representing `Either<L, A>` that is able to deserialize
+the JSON representation of an `Either`.
+
+**Signature**
+
+```ts
+export function eitherFromJSON<L extends t.Mixed, R extends t.Mixed>(
+  leftCodec: L,
+  rightCodec: R,
+  name: string = `Either<${leftCodec.name}, ${rightCodec.name}>`
+): EitherFromJSONC<L, R> { ... }
+```
+
+**Example**
+
+```ts
+import { eitherFromJSON } from 'io-ts-types/lib/eitherFromJSON'
+import { Either, left, right } from 'fp-ts/lib/Either'
+import * as t from 'io-ts'
+import { PathReporter } from 'io-ts/lib/PathReporter'
+
+const toJSON = <L, A>(ma: Either<L, A>): unknown => JSON.parse(JSON.stringify(ma))
+
+const T = eitherFromJSON(t.string, t.number)
+
+assert.deepStrictEqual(T.decode(toJSON(right(1))), right(right(1)))
+assert.deepStrictEqual(T.decode(toJSON(left('a'))), right(left('a')))
+assert.deepStrictEqual(PathReporter.report(T.decode(right('a'))), [
+  'Invalid value "a" supplied to : Either<string, number>/value: number'
+])
+```
+
+Added in v0.4.4
diff --git a/docs/modules/fallback.ts.md b/docs/modules/fallback.ts.md
index cf65913..2397bc8 100644
--- a/docs/modules/fallback.ts.md
+++ b/docs/modules/fallback.ts.md
@@ -1,6 +1,6 @@
 ---
 title: fallback.ts
-nav_order: 7
+nav_order: 8
 parent: Modules
 ---
 
diff --git a/docs/modules/fp-ts/createEitherFromJSON.ts.md b/docs/modules/fp-ts/createEitherFromJSON.ts.md
deleted file mode 100644
index 4dafc97..0000000
--- a/docs/modules/fp-ts/createEitherFromJSON.ts.md
+++ /dev/null
@@ -1,105 +0,0 @@
----
-title: fp-ts/createEitherFromJSON.ts
-nav_order: 8
-parent: Modules
----
-
----
-
-<h2 class="text-delta">Table of contents</h2>
-
-- [EitherFromJSONC (interface)](#eitherfromjsonc-interface)
-- [JSONLeft (interface)](#jsonleft-interface)
-- [JSONRight (interface)](#jsonright-interface)
-- [JSONEither (type alias)](#jsoneither-type-alias)
-- [EitherFromJSONType (class)](#eitherfromjsontype-class)
-- [createEitherFromJSON (function)](#createeitherfromjson-function)
-
----
-
-# EitherFromJSONC (interface)
-
-**Signature**
-
-```ts
-export interface EitherFromJSONC<L extends t.Mixed, R extends t.Mixed>
-  extends EitherFromJSONType<
-    L,
-    R,
-    Either<t.TypeOf<L>, t.TypeOf<R>>,
-    JSONEither<t.OutputOf<L>, t.OutputOf<R>>,
-    unknown
-  > {}
-```
-
-# JSONLeft (interface)
-
-**Signature**
-
-```ts
-export interface JSONLeft<L> {
-  type: 'Left'
-  value: L
-}
-```
-
-# JSONRight (interface)
-
-**Signature**
-
-```ts
-export interface JSONRight<A> {
-  type: 'Right'
-  value: A
-}
-```
-
-# JSONEither (type alias)
-
-**Signature**
-
-```ts
-export type JSONEither<L, A> = JSONLeft<L> | JSONRight<A>
-```
-
-# EitherFromJSONType (class)
-
-**Signature**
-
-```ts
-export class EitherFromJSONType<L, R, A, O, I> {
-  constructor(
-    name: string,
-    is: EitherFromJSONType<L, R, A, O, I>['is'],
-    validate: EitherFromJSONType<L, R, A, O, I>['validate'],
-    serialize: EitherFromJSONType<L, R, A, O, I>['encode'],
-    readonly left: L,
-    readonly right: R
-  ) { ... }
-  ...
-}
-```
-
-# createEitherFromJSON (function)
-
-**Signature**
-
-```ts
-export const createEitherFromJSON = <L extends t.Mixed, R extends t.Mixed>(
-  leftCodec: L,
-  rightCodec: R,
-  name: string = `Either<${leftCodec.name}, ${rightCodec.name}>`
-): EitherFromJSONC<L, R> => ...
-```
-
-**Example**
-
-```ts
-import * as t from 'io-ts'
-import { createEitherFromJSON } from 'io-ts-types/lib/fp-ts/createEitherFromJSON'
-import { right, left } from 'fp-ts/lib/Either'
-
-const T = createEitherFromJSON(t.string, t.number)
-assert.deepStrictEqual(T.decode({ type: 'Left', value: 's' }), right(left('s')))
-assert.deepStrictEqual(T.decode({ type: 'Right', value: 1 }), right(right(1)))
-```
diff --git a/docs/modules/fp-ts/createOptionFromJSON.ts.md b/docs/modules/fp-ts/createOptionFromJSON.ts.md
deleted file mode 100644
index 55cc417..0000000
--- a/docs/modules/fp-ts/createOptionFromJSON.ts.md
+++ /dev/null
@@ -1,78 +0,0 @@
----
-title: fp-ts/createOptionFromJSON.ts
-nav_order: 10
-parent: Modules
----
-
----
-
-<h2 class="text-delta">Table of contents</h2>
-
-- [JSONOption (interface)](#jsonoption-interface)
-- [OptionFromJSONC (interface)](#optionfromjsonc-interface)
-- [OptionFromJSONType (class)](#optionfromjsontype-class)
-- [createOptionFromJSON (function)](#createoptionfromjson-function)
-
----
-
-# JSONOption (interface)
-
-**Signature**
-
-```ts
-export interface JSONOption<A> {
-  type: 'Option'
-  value: A | null | undefined
-}
-```
-
-# OptionFromJSONC (interface)
-
-**Signature**
-
-```ts
-export interface OptionFromJSONC<C extends t.Mixed>
-  extends OptionFromJSONType<C, Option<t.TypeOf<C>>, JSONOption<t.OutputOf<C>>, unknown> {}
-```
-
-# OptionFromJSONType (class)
-
-**Signature**
-
-```ts
-export class OptionFromJSONType<C, A, O, I> {
-  constructor(
-    name: string,
-    is: OptionFromJSONType<C, A, O, I>['is'],
-    validate: OptionFromJSONType<C, A, O, I>['validate'],
-    serialize: OptionFromJSONType<C, A, O, I>['encode'],
-    readonly type: C
-  ) { ... }
-  ...
-}
-```
-
-# createOptionFromJSON (function)
-
-**Signature**
-
-```ts
-export const createOptionFromJSON = <C extends t.Mixed>(
-  codec: C,
-  name: string = `Option<${codec.name}>`
-): OptionFromJSONC<C> => ...
-```
-
-**Example**
-
-```ts
-import * as t from 'io-ts'
-import { createOptionFromJSON } from 'io-ts-types/lib/fp-ts/createOptionFromJSON'
-import { right } from 'fp-ts/lib/Either'
-import { none, some } from 'fp-ts/lib/Option'
-
-const T = createOptionFromJSON(t.number)
-assert.deepStrictEqual(T.decode({ type: 'Option', value: null }), right(none))
-assert.deepStrictEqual(T.decode({ type: 'Option', value: undefined }), right(none))
-assert.deepStrictEqual(T.decode({ type: 'Option', value: 1 }), right(some(1)))
-```
diff --git a/docs/modules/fp-ts/createOptionFromNullable.ts.md b/docs/modules/fp-ts/createOptionFromNullable.ts.md
index 470cbf3..e70927f 100644
--- a/docs/modules/fp-ts/createOptionFromNullable.ts.md
+++ b/docs/modules/fp-ts/createOptionFromNullable.ts.md
@@ -1,6 +1,6 @@
 ---
 title: fp-ts/createOptionFromNullable.ts
-nav_order: 11
+nav_order: 10
 parent: Modules
 ---
 
diff --git a/docs/modules/fp-ts/createSetFromArray.ts.md b/docs/modules/fp-ts/createSetFromArray.ts.md
index e626c0a..e97221e 100644
--- a/docs/modules/fp-ts/createSetFromArray.ts.md
+++ b/docs/modules/fp-ts/createSetFromArray.ts.md
@@ -1,6 +1,6 @@
 ---
 title: fp-ts/createSetFromArray.ts
-nav_order: 12
+nav_order: 11
 parent: Modules
 ---
 
diff --git a/docs/modules/fromNullable.ts.md b/docs/modules/fromNullable.ts.md
index 9abd472..4a4dd4c 100644
--- a/docs/modules/fromNullable.ts.md
+++ b/docs/modules/fromNullable.ts.md
@@ -1,6 +1,6 @@
 ---
 title: fromNullable.ts
-nav_order: 14
+nav_order: 13
 parent: Modules
 ---
 
diff --git a/docs/modules/index.ts.md b/docs/modules/index.ts.md
index 29c3228..e059497 100644
--- a/docs/modules/index.ts.md
+++ b/docs/modules/index.ts.md
@@ -1,6 +1,6 @@
 ---
 title: index.ts
-nav_order: 15
+nav_order: 14
 parent: Modules
 ---
 
diff --git a/docs/modules/mapOutput.ts.md b/docs/modules/mapOutput.ts.md
index c84dcbe..78b120c 100644
--- a/docs/modules/mapOutput.ts.md
+++ b/docs/modules/mapOutput.ts.md
@@ -1,6 +1,6 @@
 ---
 title: mapOutput.ts
-nav_order: 19
+nav_order: 18
 parent: Modules
 ---
 
diff --git a/docs/modules/monocle-ts/TypePrismIso.ts.md b/docs/modules/monocle-ts/TypePrismIso.ts.md
index a84c430..ebe2027 100644
--- a/docs/modules/monocle-ts/TypePrismIso.ts.md
+++ b/docs/modules/monocle-ts/TypePrismIso.ts.md
@@ -1,6 +1,6 @@
 ---
 title: monocle-ts/TypePrismIso.ts
-nav_order: 22
+nav_order: 21
 parent: Modules
 ---
 
diff --git a/docs/modules/monocle-ts/lensesFromInterface.ts.md b/docs/modules/monocle-ts/lensesFromInterface.ts.md
index 37793b8..5649b84 100644
--- a/docs/modules/monocle-ts/lensesFromInterface.ts.md
+++ b/docs/modules/monocle-ts/lensesFromInterface.ts.md
@@ -1,6 +1,6 @@
 ---
 title: monocle-ts/lensesFromInterface.ts
-nav_order: 20
+nav_order: 19
 parent: Modules
 ---
 
diff --git a/docs/modules/monocle-ts/lensesFromProps.ts.md b/docs/modules/monocle-ts/lensesFromProps.ts.md
index 26240b4..556a2ab 100644
--- a/docs/modules/monocle-ts/lensesFromProps.ts.md
+++ b/docs/modules/monocle-ts/lensesFromProps.ts.md
@@ -1,6 +1,6 @@
 ---
 title: monocle-ts/lensesFromProps.ts
-nav_order: 21
+nav_order: 20
 parent: Modules
 ---
 
diff --git a/docs/modules/newtype-ts/fromNewtype.ts.md b/docs/modules/newtype-ts/fromNewtype.ts.md
index 6f57bdb..5ed74a4 100644
--- a/docs/modules/newtype-ts/fromNewtype.ts.md
+++ b/docs/modules/newtype-ts/fromNewtype.ts.md
@@ -1,6 +1,6 @@
 ---
 title: newtype-ts/fromNewtype.ts
-nav_order: 23
+nav_order: 22
 parent: Modules
 ---
 
diff --git a/docs/modules/newtype-ts/fromRefinement.ts.md b/docs/modules/newtype-ts/fromRefinement.ts.md
index a2b5e9f..422b3d4 100644
--- a/docs/modules/newtype-ts/fromRefinement.ts.md
+++ b/docs/modules/newtype-ts/fromRefinement.ts.md
@@ -1,6 +1,6 @@
 ---
 title: newtype-ts/fromRefinement.ts
-nav_order: 24
+nav_order: 23
 parent: Modules
 ---
 
diff --git a/docs/modules/number/IntegerFromString.ts.md b/docs/modules/number/IntegerFromString.ts.md
deleted file mode 100644
index f1812d1..0000000
--- a/docs/modules/number/IntegerFromString.ts.md
+++ /dev/null
@@ -1,45 +0,0 @@
----
-title: number/IntegerFromString.ts
-nav_order: 25
-parent: Modules
----
-
----
-
-<h2 class="text-delta">Table of contents</h2>
-
-- [IntegerFromStringC (interface)](#integerfromstringc-interface)
-- [~~IntegerFromString~~ (constant)](#integerfromstring-constant)
-
----
-
-# IntegerFromStringC (interface)
-
-**Signature**
-
-```ts
-export interface IntegerFromStringC extends t.RefinementType<NumberFromStringType, number, string, unknown> {}
-```
-
-# ~~IntegerFromString~~ (constant)
-
-Use `io-ts-types/lib/IntFromString` instead.
-
-**Signature**
-
-```ts
-export const IntegerFromString: IntegerFromStringC = ...
-```
-
-**Example**
-
-```ts
-import { IntegerFromString } from 'io-ts-types/lib/number/IntegerFromString'
-import { right } from 'fp-ts/lib/Either'
-import { PathReporter } from 'io-ts/lib/PathReporter'
-
-assert.deepStrictEqual(IntegerFromString.decode('1'), right(1))
-assert.deepStrictEqual(PathReporter.report(IntegerFromString.decode('1.1')), [
-  'Invalid value 1.1 supplied to : IntegerFromString'
-])
-```
diff --git a/docs/modules/number/NumberFromString.ts.md b/docs/modules/number/NumberFromString.ts.md
index 2b458ef..832ab93 100644
--- a/docs/modules/number/NumberFromString.ts.md
+++ b/docs/modules/number/NumberFromString.ts.md
@@ -1,6 +1,6 @@
 ---
 title: number/NumberFromString.ts
-nav_order: 26
+nav_order: 24
 parent: Modules
 ---
 
diff --git a/docs/modules/optionFromJSON.ts.md b/docs/modules/optionFromJSON.ts.md
new file mode 100644
index 0000000..9ac4ced
--- /dev/null
+++ b/docs/modules/optionFromJSON.ts.md
@@ -0,0 +1,68 @@
+---
+title: optionFromJSON.ts
+nav_order: 25
+parent: Modules
+---
+
+---
+
+<h2 class="text-delta">Table of contents</h2>
+
+- [OptionFromJSONC (interface)](#optionfromjsonc-interface)
+- [JSONOption (type alias)](#jsonoption-type-alias)
+- [optionFromJSON (function)](#optionfromjson-function)
+
+---
+
+# OptionFromJSONC (interface)
+
+**Signature**
+
+```ts
+export interface OptionFromJSONC<C extends t.Mixed>
+  extends t.Type<Option<t.TypeOf<C>>, JSONOption<t.OutputOf<C>>, unknown> {}
+```
+
+# JSONOption (type alias)
+
+**Signature**
+
+```ts
+export type JSONOption<A> = { _tag: 'None' } | { _tag: 'Some'; value: A }
+```
+
+# optionFromJSON (function)
+
+Given a codec representing a type `A`, returns a codec representing `Option<A>` that is able to deserialize
+the JSON representation of an `Option`.
+
+**Signature**
+
+```ts
+export function optionFromJSON<C extends t.Mixed>(
+  codec: C,
+  name: string = `Option<${codec.name}>`
+): OptionFromJSONC<C> { ... }
+```
+
+**Example**
+
+```ts
+import { optionFromJSON } from 'io-ts-types/lib/optionFromJSON'
+import { right } from 'fp-ts/lib/Either'
+import { Option, none, some } from 'fp-ts/lib/Option'
+import * as t from 'io-ts'
+import { PathReporter } from 'io-ts/lib/PathReporter'
+
+const toJSON = <A>(ma: Option<A>): unknown => JSON.parse(JSON.stringify(ma))
+
+const T = optionFromJSON(t.number)
+
+assert.deepStrictEqual(T.decode(toJSON(none)), right(none))
+assert.deepStrictEqual(T.decode(toJSON(some(1))), right(some(1)))
+assert.deepStrictEqual(PathReporter.report(T.decode(some('a'))), [
+  'Invalid value "a" supplied to : Option<number>/value: number'
+])
+```
+
+Added in v0.4.4
diff --git a/docs/modules/string/uuid.ts.md b/docs/modules/string/uuid.ts.md
index dcccdf0..af002bd 100644
--- a/docs/modules/string/uuid.ts.md
+++ b/docs/modules/string/uuid.ts.md
@@ -1,6 +1,6 @@
 ---
 title: string/uuid.ts
-nav_order: 27
+nav_order: 26
 parent: Modules
 ---
 
diff --git a/docs/modules/withMessage.ts.md b/docs/modules/withMessage.ts.md
index 54319f5..5e83269 100644
--- a/docs/modules/withMessage.ts.md
+++ b/docs/modules/withMessage.ts.md
@@ -1,6 +1,6 @@
 ---
 title: withMessage.ts
-nav_order: 28
+nav_order: 27
 parent: Modules
 ---
 
@@ -35,3 +35,5 @@ const T = withMessage(t.number, () => 'Invalid number')
 assert.deepStrictEqual(T.decode(1), right(1))
 assert.deepStrictEqual(PathReporter.report(T.decode(null)), ['Invalid number'])
 ```
+
+Added in v0.4.3
diff --git a/docs/modules/withValidate.ts.md b/docs/modules/withValidate.ts.md
index 1fd539d..10437d9 100644
--- a/docs/modules/withValidate.ts.md
+++ b/docs/modules/withValidate.ts.md
@@ -1,6 +1,6 @@
 ---
 title: withValidate.ts
-nav_order: 29
+nav_order: 28
 parent: Modules
 ---
 
@@ -35,3 +35,5 @@ const T = withValidate(t.number, (u, c) => t.number.validate(u, c).map(n => n *
 assert.deepStrictEqual(T.decode(1), right(2))
 assert.deepStrictEqual(PathReporter.report(T.decode(null)), ['Invalid value null supplied to : number'])
 ```
+
+Added in v0.4.3
diff --git a/package-lock.json b/package-lock.json
index 4e058c6..7edb986 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,6 +1,6 @@
 {
   "name": "io-ts-types",
-  "version": "0.4.2",
+  "version": "0.4.4",
   "lockfileVersion": 1,
   "requires": true,
   "dependencies": {
@@ -1458,7 +1458,7 @@
       }
     },
     "docs-ts": {
-      "version": "github:gcanti/docs-ts#4e9e9463352fafee1a221cd3f9688b318c588062",
+      "version": "github:gcanti/docs-ts#1d4276c3dad969f99d163fbea3cd1e5d318f9d1c",
       "from": "github:gcanti/docs-ts",
       "dev": true,
       "requires": {
@@ -1506,9 +1506,9 @@
           "dev": true
         },
         "ts-node": {
-          "version": "8.0.2",
-          "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-8.0.2.tgz",
-          "integrity": "sha512-MosTrinKmaAcWgO8tqMjMJB22h+sp3Rd1i4fdoWY4mhBDekOwIAKI/bzmRi7IcbCmjquccYg2gcF6NBkLgr0Tw==",
+          "version": "8.0.3",
+          "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-8.0.3.tgz",
+          "integrity": "sha512-2qayBA4vdtVRuDo11DEFSsD/SFsBXQBRZZhbRGSIkmYmVkWjULn/GGMdG10KVqkaGndljfaTD8dKjWgcejO8YA==",
           "dev": true,
           "requires": {
             "arg": "^4.1.0",
diff --git a/src/IntFromString.ts b/src/IntFromString.ts
index 2181cc8..99279c0 100644
--- a/src/IntFromString.ts
+++ b/src/IntFromString.ts
@@ -1,6 +1,8 @@
 import * as t from 'io-ts'
 import { NumberFromString } from './number/NumberFromString'
 
+export interface IntFromStringC extends t.Type<t.Int, string, unknown> {}
+
 /**
  * A codec that succeeds if a string can be parsed to an integer
  *
@@ -11,8 +13,10 @@ import { NumberFromString } from './number/NumberFromString'
  *
  * assert.deepStrictEqual(IntFromString.decode('1'), right(1))
  * assert.deepStrictEqual(PathReporter.report(IntFromString.decode('1.1')), ['Invalid value "1.1" supplied to : IntFromString'])
+ *
+ * @since 0.4.4
  */
-export const IntFromString: t.Type<t.Int, string, unknown> = new t.Type<t.Int, string, unknown>(
+export const IntFromString: IntFromStringC = new t.Type<t.Int, string, unknown>(
   'IntFromString',
   t.Int.is,
   (u, c) => NumberFromString.validate(u, c).chain(n => (t.Int.is(n) ? t.success(n) : t.failure(u, c))),
diff --git a/src/clone.ts b/src/clone.ts
index 2297a01..cd68c60 100644
--- a/src/clone.ts
+++ b/src/clone.ts
@@ -8,6 +8,8 @@ import * as t from 'io-ts'
  * import * as t from 'io-ts'
  *
  * assert.deepStrictEqual(clone(t.string), t.string)
+ *
+ * @since 0.4.3
  */
 export function clone<C extends t.Any>(t: C): C {
   const r = Object.create(Object.getPrototypeOf(t))
diff --git a/src/eitherFromJSON.ts b/src/eitherFromJSON.ts
new file mode 100644
index 0000000..b097c47
--- /dev/null
+++ b/src/eitherFromJSON.ts
@@ -0,0 +1,60 @@
+import * as t from 'io-ts'
+import { Either, Left, Right, left, right } from 'fp-ts/lib/Either'
+
+export type JSONEither<L, A> = { _tag: 'Left'; value: L } | { _tag: 'Right'; value: A }
+
+const jleft = <L, A>(value: L): JSONEither<L, A> => ({ _tag: 'Left', value })
+
+const jright = <L, A>(value: A): JSONEither<L, A> => ({ _tag: 'Right', value })
+
+export interface EitherFromJSONC<L extends t.Mixed, R extends t.Mixed>
+  extends t.Type<Either<t.TypeOf<L>, t.TypeOf<R>>, JSONEither<t.OutputOf<L>, t.OutputOf<R>>, unknown> {}
+
+/**
+ * Given a codec representing a type `L` and a codec representing a type `A`, returns a codec representing `Either<L, A>` that is able to deserialize
+ * the JSON representation of an `Either`.
+ *
+ * @example
+ * import { eitherFromJSON } from 'io-ts-types/lib/eitherFromJSON'
+ * import { Either, left, right } from 'fp-ts/lib/Either'
+ * import * as t from 'io-ts'
+ * import { PathReporter } from 'io-ts/lib/PathReporter'
+ *
+ * const toJSON = <L, A>(ma: Either<L, A>): unknown => JSON.parse(JSON.stringify(ma))
+ *
+ * const T = eitherFromJSON(t.string, t.number)
+ *
+ * assert.deepStrictEqual(T.decode(toJSON(right(1))), right(right(1)))
+ * assert.deepStrictEqual(T.decode(toJSON(left('a'))), right(left('a')))
+ * assert.deepStrictEqual(PathReporter.report(T.decode(right('a'))), ['Invalid value "a" supplied to : Either<string, number>/value: number'])
+ *
+ * @since 0.4.4
+ */
+export function eitherFromJSON<L extends t.Mixed, R extends t.Mixed>(
+  leftCodec: L,
+  rightCodec: R,
+  name: string = `Either<${leftCodec.name}, ${rightCodec.name}>`
+): EitherFromJSONC<L, R> {
+  const leftC = t.type({
+    value: leftCodec
+  })
+  const rightC = t.type({
+    value: rightCodec
+  })
+  return new t.Type(
+    name,
+    (u): u is Either<t.TypeOf<L>, t.TypeOf<R>> =>
+      (u instanceof Left && leftCodec.is(u.value)) || (u instanceof Right && rightCodec.is(u.value)),
+    (u, c) =>
+      t.UnknownRecord.validate(u, c).chain(o => {
+        if (o._tag === 'Left') {
+          return leftC.validate(o, c).map(s => left(s.value))
+        } else if (o._tag === 'Right') {
+          return rightC.validate(o, c).map(s => right(s.value))
+        } else {
+          return t.failure(u, c)
+        }
+      }),
+    a => a.fold(l => jleft(leftCodec.encode(l)), a => jright(rightCodec.encode(a)))
+  )
+}
diff --git a/src/fp-ts/createEitherFromJSON.ts b/src/fp-ts/createEitherFromJSON.ts
index ccd76a9..3545750 100644
--- a/src/fp-ts/createEitherFromJSON.ts
+++ b/src/fp-ts/createEitherFromJSON.ts
@@ -1,3 +1,7 @@
+/**
+ * @file Use `io-ts-types/lib/eitherFromJSON` instead.
+ * @deprecated
+ */
 import * as t from 'io-ts'
 
 import { Either, Right, Left, left, right } from 'fp-ts/lib/Either'
@@ -38,6 +42,8 @@ export interface EitherFromJSONC<L extends t.Mixed, R extends t.Mixed>
   > {}
 
 /**
+ * Use `io-ts-types/lib/eitherFromJSON` instead.
+ *
  * @example
  * import * as t from 'io-ts'
  * import { createEitherFromJSON } from 'io-ts-types/lib/fp-ts/createEitherFromJSON'
@@ -46,6 +52,7 @@ export interface EitherFromJSONC<L extends t.Mixed, R extends t.Mixed>
  * const T = createEitherFromJSON(t.string, t.number)
  * assert.deepStrictEqual(T.decode({ type: 'Left', value: 's' }), right(left('s')))
  * assert.deepStrictEqual(T.decode({ type: 'Right', value: 1 }), right(right(1)))
+ * @deprecated
  */
 export const createEitherFromJSON = <L extends t.Mixed, R extends t.Mixed>(
   leftCodec: L,
diff --git a/src/fp-ts/createOptionFromJSON.ts b/src/fp-ts/createOptionFromJSON.ts
index 691912a..f5094ec 100644
--- a/src/fp-ts/createOptionFromJSON.ts
+++ b/src/fp-ts/createOptionFromJSON.ts
@@ -1,3 +1,7 @@
+/**
+ * @file Use `io-ts-types/lib/optionFromJSON` instead.
+ * @deprecated
+ */
 import * as t from 'io-ts'
 
 import { Option, None, Some, fromNullable } from 'fp-ts/lib/Option'
@@ -24,6 +28,8 @@ export interface OptionFromJSONC<C extends t.Mixed>
   extends OptionFromJSONType<C, Option<t.TypeOf<C>>, JSONOption<t.OutputOf<C>>, unknown> {}
 
 /**
+ * Use `io-ts-types/lib/optionFromJSON` instead.
+ *
  * @example
  * import * as t from 'io-ts'
  * import { createOptionFromJSON } from 'io-ts-types/lib/fp-ts/createOptionFromJSON'
@@ -34,6 +40,7 @@ export interface OptionFromJSONC<C extends t.Mixed>
  * assert.deepStrictEqual(T.decode({ type: 'Option', value: null }), right(none))
  * assert.deepStrictEqual(T.decode({ type: 'Option', value: undefined }), right(none))
  * assert.deepStrictEqual(T.decode({ type: 'Option', value: 1 }), right(some(1)))
+ * @deprecated
  */
 export const createOptionFromJSON = <C extends t.Mixed>(
   codec: C,
diff --git a/src/number/IntegerFromString.ts b/src/number/IntegerFromString.ts
index ed029d1..c55cd58 100644
--- a/src/number/IntegerFromString.ts
+++ b/src/number/IntegerFromString.ts
@@ -1,3 +1,7 @@
+/**
+ * @file Use `io-ts-types/lib/IntFromString` instead.
+ * @deprecated
+ */
 import * as t from 'io-ts'
 import { NumberFromString, NumberFromStringType } from './NumberFromString'
 
diff --git a/src/optionFromJSON.ts b/src/optionFromJSON.ts
new file mode 100644
index 0000000..3413006
--- /dev/null
+++ b/src/optionFromJSON.ts
@@ -0,0 +1,56 @@
+import * as t from 'io-ts'
+import { Option, none, some, None, Some } from 'fp-ts/lib/Option'
+
+export type JSONOption<A> = { _tag: 'None' } | { _tag: 'Some'; value: A }
+
+const jnone: JSONOption<never> = { _tag: 'None' }
+
+const jsome = <A>(value: A): JSONOption<A> => ({ _tag: 'Some', value })
+
+export interface OptionFromJSONC<C extends t.Mixed>
+  extends t.Type<Option<t.TypeOf<C>>, JSONOption<t.OutputOf<C>>, unknown> {}
+
+/**
+ * Given a codec representing a type `A`, returns a codec representing `Option<A>` that is able to deserialize
+ * the JSON representation of an `Option`.
+ *
+ * @example
+ * import { optionFromJSON } from 'io-ts-types/lib/optionFromJSON'
+ * import { right } from 'fp-ts/lib/Either'
+ * import { Option, none, some } from 'fp-ts/lib/Option'
+ * import * as t from 'io-ts'
+ * import { PathReporter } from 'io-ts/lib/PathReporter'
+ *
+ * const toJSON = <A>(ma: Option<A>): unknown => JSON.parse(JSON.stringify(ma))
+ *
+ * const T = optionFromJSON(t.number)
+ *
+ * assert.deepStrictEqual(T.decode(toJSON(none)), right(none))
+ * assert.deepStrictEqual(T.decode(toJSON(some(1))), right(some(1)))
+ * assert.deepStrictEqual(PathReporter.report(T.decode(some('a'))), ['Invalid value "a" supplied to : Option<number>/value: number'])
+ *
+ * @since 0.4.4
+ */
+export function optionFromJSON<C extends t.Mixed>(
+  codec: C,
+  name: string = `Option<${codec.name}>`
+): OptionFromJSONC<C> {
+  const someC = t.type({
+    value: codec
+  })
+  return new t.Type(
+    name,
+    (u): u is Option<t.TypeOf<C>> => u instanceof None || (u instanceof Some && codec.is(u.value)),
+    (u, c) =>
+      t.UnknownRecord.validate(u, c).chain(o => {
+        if (o._tag === 'None') {
+          return t.success(none)
+        } else if (o._tag === 'Some') {
+          return someC.validate(o, c).map(s => some(s.value))
+        } else {
+          return t.failure(u, c)
+        }
+      }),
+    a => a.fold(jnone, a => jsome(codec.encode(a)))
+  )
+}
diff --git a/src/withMessage.ts b/src/withMessage.ts
index 5a96399..323c370 100644
--- a/src/withMessage.ts
+++ b/src/withMessage.ts
@@ -14,6 +14,8 @@ import { withValidate } from './withValidate'
  *
  * assert.deepStrictEqual(T.decode(1), right(1))
  * assert.deepStrictEqual(PathReporter.report(T.decode(null)), ['Invalid number'])
+ *
+ * @since 0.4.3
  */
 export function withMessage<C extends t.Any>(codec: C, message: (i: t.InputOf<C>) => string): C {
   return withValidate(codec, (i, c) =>
diff --git a/src/withValidate.ts b/src/withValidate.ts
index 85f6fb0..2f92600 100644
--- a/src/withValidate.ts
+++ b/src/withValidate.ts
@@ -14,6 +14,8 @@ import { clone } from './clone'
  *
  * assert.deepStrictEqual(T.decode(1), right(2))
  * assert.deepStrictEqual(PathReporter.report(T.decode(null)), ['Invalid value null supplied to : number'])
+ *
+ * @since 0.4.3
  */
 export function withValidate<C extends t.Any>(codec: C, validate: C['validate'], name: string = codec.name): C {
   const r: any = clone(codec)


Test patch:
diff --git a/docs/modules/fp-ts/createStrMapFromDictionary.ts.md b/docs/modules/fp-ts/createStrMapFromDictionary.ts.md
index 207333f..14b88d5 100644
--- a/docs/modules/fp-ts/createStrMapFromDictionary.ts.md
+++ b/docs/modules/fp-ts/createStrMapFromDictionary.ts.md
@@ -1,6 +1,6 @@
 ---
 title: fp-ts/createStrMapFromDictionary.ts
-nav_order: 13
+nav_order: 12
 parent: Modules
 ---
 
diff --git a/test/IntFromString.ts b/test/IntFromString.ts
index 8cd42cf..05432b6 100644
--- a/test/IntFromString.ts
+++ b/test/IntFromString.ts
@@ -24,6 +24,8 @@ describe('fallback', () => {
       assertFailure(T, null, ['Invalid value null supplied to : IntFromString'])
       assertFailure(T, '1.1', ['Invalid value "1.1" supplied to : IntFromString'])
       assertFailure(T, '1a', ['Invalid value "1a" supplied to : IntFromString'])
+      assertFailure(T, '2a', ['Invalid value "2a" supplied to : IntFromString'])
+      assertFailure(T, '2.a', ['Invalid value "2.a" supplied to : IntFromString'])
     })
   })
 
diff --git a/test/eitherFromJSON.ts b/test/eitherFromJSON.ts
new file mode 100644
index 0000000..a6f5754
--- /dev/null
+++ b/test/eitherFromJSON.ts
@@ -0,0 +1,49 @@
+import * as assert from 'assert'
+import { left, right } from 'fp-ts/lib/Either'
+import * as t from 'io-ts'
+import { eitherFromJSON } from '../src/eitherFromJSON'
+import { assertFailure, assertSuccess } from './helpers'
+import { NumberFromString } from '../src/number/NumberFromString'
+
+describe('eitherFromJSON', () => {
+  it('name', () => {
+    const T1 = eitherFromJSON(t.string, t.number)
+    assert.strictEqual(T1.name, 'Either<string, number>')
+    const T2 = eitherFromJSON(t.string, t.number, 'T')
+    assert.strictEqual(T2.name, 'T')
+  })
+
+  it('is', () => {
+    const T = eitherFromJSON(t.string, t.number)
+    assert.strictEqual(T.is(right(1)), true)
+    assert.strictEqual(T.is(right('foo')), false)
+    assert.strictEqual(T.is(left(1)), false)
+    assert.strictEqual(T.is(left('foo')), true)
+  })
+
+  it('decode', () => {
+    const T1 = eitherFromJSON(t.string, t.number)
+    assertSuccess(T1.decode({ _tag: 'Left', value: 's' }), left<string, number>('s'))
+    assertSuccess(T1.decode({ _tag: 'Right', value: 1 }), right<string, number>(1))
+    assertFailure(T1, null, ['Invalid value null supplied to : Either<string, number>'])
+    assertFailure(T1, {}, ['Invalid value {} supplied to : Either<string, number>'])
+    assertFailure(T1, { _tag: 'Left', value: true }, [
+      'Invalid value true supplied to : Either<string, number>/value: string'
+    ])
+    assertFailure(T1, { _tag: 'Right', value: 'a' }, [
+      'Invalid value "a" supplied to : Either<string, number>/value: number'
+    ])
+
+    const T2 = eitherFromJSON(t.string, NumberFromString)
+    assertSuccess(T2.decode({ _tag: 'Right', value: '1' }), right<string, number>(1))
+  })
+
+  it('encode', () => {
+    const T1 = eitherFromJSON(t.string, t.number)
+    assert.deepStrictEqual(T1.encode(left('a')), { _tag: 'Left', value: 'a' })
+    assert.deepStrictEqual(T1.encode(right(1)), { _tag: 'Right', value: 1 })
+
+    const T2 = eitherFromJSON(t.string, NumberFromString)
+    assert.deepStrictEqual(T2.encode(right(1)), { _tag: 'Right', value: '1' })
+  })
+})
diff --git a/test/index.ts b/test/index.ts
index 1a6afc7..3caafa3 100644
--- a/test/index.ts
+++ b/test/index.ts
@@ -73,6 +73,7 @@ describe('fp-ts', () => {
   })
 
   it('createOptionFromJSON', () => {
+    // tslint:disable-next-line: deprecation
     const T = createOptionFromJSON(t.number)
     assert.deepEqual(T.decode({ type: 'Option', value: null }), right(none))
     assert.deepEqual(T.decode({ type: 'Option', value: undefined }), right(none))
@@ -82,11 +83,13 @@ describe('fp-ts', () => {
     assert.strictEqual(T.is(some(1)), true)
     assert.strictEqual(T.is(some('foo')), false)
 
+    // tslint:disable-next-line: deprecation
     const T2 = createOptionFromJSON(t.number, 'T2')
     assert.strictEqual(T2.name, 'T2')
   })
 
   it('createOptionOfOptionFromJSON', () => {
+    // tslint:disable-next-line: deprecation
     const T = createOptionFromJSON(createOptionFromJSON(t.number))
     assert.deepEqual(T.decode({ type: 'Option', value: { type: 'Option', value: 1 } }), right(some(some(1))))
     assert.deepEqual(T.encode(some(some(1))), { type: 'Option', value: { type: 'Option', value: 1 } })
@@ -97,6 +100,7 @@ describe('fp-ts', () => {
   })
 
   it('createEitherFromJSON', () => {
+    // tslint:disable-next-line: deprecation
     const T = createEitherFromJSON(t.string, t.number)
     assert.deepEqual(T.decode({ type: 'Left', value: 's' }), right(left('s')))
     assert.deepEqual(T.decode({ type: 'Right', value: 1 }), right(right(1)))
@@ -107,11 +111,13 @@ describe('fp-ts', () => {
     assert.strictEqual(T.is(left(1)), false)
     assert.strictEqual(T.is(left('foo')), true)
     assert.deepEqual(PathReporter.report(T.decode(null)), ['Invalid value null supplied to : Either<string, number>'])
+    // tslint:disable-next-line: deprecation
     const T2 = createEitherFromJSON(t.string, t.number, 'T2')
     assert.strictEqual(T2.name, 'T2')
   })
 
   it('createEitherOfOptionFromJSON', () => {
+    // tslint:disable-next-line: deprecation
     const T = createEitherFromJSON(createOptionFromJSON(t.string), createOptionFromJSON(t.number))
     assert.deepEqual(T.decode({ type: 'Left', value: { type: 'Option', value: 's' } }), right(left(some('s'))))
     assert.deepEqual(T.decode({ type: 'Right', value: { type: 'Option', value: 1 } }), right(right(some(1))))
diff --git a/test/optionFromJSON.ts b/test/optionFromJSON.ts
new file mode 100644
index 0000000..9ca53a7
--- /dev/null
+++ b/test/optionFromJSON.ts
@@ -0,0 +1,60 @@
+import { assertSuccess, assertFailure } from './helpers'
+import { optionFromJSON } from '../src/optionFromJSON'
+import * as t from 'io-ts'
+import * as assert from 'assert'
+import { NumberFromString } from '../src/number/NumberFromString'
+import { none, some, Option } from 'fp-ts/lib/Option'
+
+const toJSON = <A>(ma: Option<A>): unknown => JSON.parse(JSON.stringify(ma))
+
+describe('optionFromJSON', () => {
+  it('name', () => {
+    const T1 = optionFromJSON(t.number)
+    assert.strictEqual(T1.name, 'Option<number>')
+    const T2 = optionFromJSON(t.number, 'T')
+    assert.strictEqual(T2.name, 'T')
+  })
+
+  it('is', () => {
+    const T1 = optionFromJSON(t.number)
+    assert.strictEqual(T1.is(some(1)), true)
+    assert.strictEqual(T1.is(some('foo')), false)
+  })
+
+  it('decode', () => {
+    const T1 = optionFromJSON(t.number)
+    assertSuccess(T1.decode(toJSON(none)), none)
+    assertSuccess(T1.decode(toJSON(some(1))), some(1))
+    assertFailure(T1, toJSON(some('a')), ['Invalid value "a" supplied to : Option<number>/value: number'])
+    assertSuccess(T1.decode({ _tag: 'None', a: 'a' }), none)
+    assertSuccess(T1.decode({ _tag: 'Some', value: 1, a: 'a' }), some(1))
+
+    assertFailure(T1, null, ['Invalid value null supplied to : Option<number>'])
+    assertFailure(T1, {}, ['Invalid value {} supplied to : Option<number>'])
+    assertFailure(T1, { _tag: 'Some', value: 'a' }, ['Invalid value "a" supplied to : Option<number>/value: number'])
+
+    const T2 = optionFromJSON(NumberFromString)
+    assertSuccess(T2.decode(toJSON(none)), none)
+    assertSuccess(T2.decode(toJSON(some('1'))), some(1))
+
+    const T3 = optionFromJSON(optionFromJSON(t.number))
+    assertSuccess(T3.decode({ _tag: 'Some', value: { _tag: 'Some', value: 1 } }), some(some(1)))
+    assertSuccess(T3.decode({ _tag: 'Some', value: { _tag: 'None' } }), some(none))
+    assertSuccess(T3.decode({ _tag: 'None' }), none)
+  })
+
+  it('encode', () => {
+    const T1 = optionFromJSON(t.number)
+    assert.deepStrictEqual(T1.encode(none), toJSON(none))
+    assert.deepStrictEqual(T1.encode(some(1)), toJSON(some(1)))
+
+    const T2 = optionFromJSON(NumberFromString)
+    assert.deepStrictEqual(T2.encode(none), toJSON(none))
+    assert.deepStrictEqual(T2.encode(some(1)), toJSON(some('1')))
+
+    const T3 = optionFromJSON(optionFromJSON(t.number))
+    assert.deepStrictEqual(T3.encode(none), { _tag: 'None' })
+    assert.deepStrictEqual(T3.encode(some(some(1))), { _tag: 'Some', value: { _tag: 'Some', value: 1 } })
+    assert.deepStrictEqual(T3.encode(some(none)), { _tag: 'Some', value: { _tag: 'None' } })
+  })
+})