Repository: gcanti/io-ts (ts)

Issue:
Can `t.partial` imply `{}`, not all JS objects
## 🚀 Feature request

### Current Behavior

To create a type that _disallows_ arrays but permissively _allows_ objects (using `t.partial`) I need to:

```ts
// There may be another way...
t.intersection([
    t.partial({
      my: t.string,
      permissive: t.string,
      type: t.string,
    }),
    t.brand(
      t.UnknownRecord,
      (v: any): v is t.Branded<any, NotArray> => !Array.isArray(v),
      'NotArray'
    ),
  ])
```

Without using `t.brand` and `t.intersection`, `t.partial` will allow arrays to pass "decode" step at runtime :c .

### Desired Behavior

I would like a way to be able to _negate_ certain runtime types (not static types) _or_ `t.partial` should imply objects `{}`, not arrays `[]`, even though:

```
 😅
typeof []
=> "object"
```

### Suggested Solution

Can `t.partial` by default not allow non-object types? If I would like to use an array type I can reach for `t.array`.

Or, less good:
```ts
// this seems not great, but less verbose than we currently have?
t.intersection([
    t.partial({
      my: t.string,
      permissive: t.string,
      type: t.string,
    }),
   t.not.array
  ])
```
<!-- NOTE: Feature Requests without suggested solutions may not be addressed or treated with the same level of urgency as those that have suggested solutions. -->

### Who does this impact? Who is this for?

<!-- Who is this for? All users? TypeScript users? Beginners? Advanced? Yourself? People using X, Y, X, etc.? -->

Everyone!

### Describe alternatives you've considered

<!-- A clear and concise description of any alternative solutions or features you've considered.  -->

See above

### Additional context

<!-- Add any other context or links about the feature request here. -->
None

### Your environment

<!-- PLEASE FILL THIS OUT -->

| Software   | Version(s) |
| ---------- | ---------- |
| io-ts      |       2.0.1     |
| fp-ts      |       2.3.1     |
| TypeScript |      3.7.2      |


Pull request description:
partial shouldn't allow arrays, fix #407

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 579cecc..7d5ecb2 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -14,6 +14,11 @@
 **Note**: Gaps between patch versions are faulty/broken releases. **Note**: A feature tagged as Experimental is in a
 high state of flux, you're at risk of it changing without notice.
 
+# 2.0.5
+
+- **Bug Fix**
+  - `partial` shouldn't allow arrays, fix #407 (@gcanti)
+
 # 2.0.4
 
 - **Bug Fix**
diff --git a/jest.config.js b/jest.config.js
new file mode 100644
index 0000000..247b387
--- /dev/null
+++ b/jest.config.js
@@ -0,0 +1,20 @@
+module.exports = {
+  preset: 'ts-jest',
+  testEnvironment: 'node',
+  collectCoverage: true,
+  collectCoverageFrom: ['src/**/*.ts'],
+  transform: {
+    '^.+\\.tsx?$': 'ts-jest'
+  },
+  testRegex: 'test',
+  moduleFileExtensions: ['ts', 'js'],
+  coverageThreshold: {
+    global: {
+      branches: 100,
+      functions: 100,
+      lines: 100,
+      statements: 100
+    }
+  },
+  modulePathIgnorePatterns: ['helpers.ts']
+}
diff --git a/package.json b/package.json
index 516e8a5..33e62ee 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "io-ts",
-  "version": "2.0.4",
+  "version": "2.0.5",
   "description": "TypeScript compatible runtime type system for IO validation",
   "files": [
     "lib",
@@ -37,6 +37,7 @@
     "url": "https://github.com/gcanti/io-ts/issues"
   },
   "homepage": "https://github.com/gcanti/io-ts",
+  "dependencies": {},
   "peerDependencies": {
     "fp-ts": "^2.0.0"
   },
@@ -73,28 +74,5 @@
     "inference",
     "types",
     "runtime"
-  ],
-  "jest": {
-    "collectCoverage": true,
-    "transform": {
-      "^.+\\.tsx?$": "ts-jest"
-    },
-    "testRegex": "test",
-    "moduleFileExtensions": [
-      "ts",
-      "js"
-    ],
-    "testPathIgnorePatterns": [
-      "helpers.ts"
-    ],
-    "coverageThreshold": {
-      "global": {
-        "branches": 100,
-        "functions": 100,
-        "lines": 100,
-        "statements": 100
-      }
-    }
-  },
-  "dependencies": {}
+  ]
 }
diff --git a/src/index.ts b/src/index.ts
index f8a0c19..5188823 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -215,8 +215,6 @@ const pushAll = <A>(xs: Array<A>, ys: Array<A>): void => {
 
 const getIsCodec = <T extends Any>(tag: string) => (codec: Any): codec is T => (codec as any)._tag === tag
 
-const isUnknownCodec = getIsCodec<UnknownType>('UnknownType')
-
 // tslint:disable-next-line: deprecation
 const isAnyCodec = getIsCodec<AnyType>('AnyType')
 
@@ -418,7 +416,7 @@ export class AnyDictionaryType extends Type<{ [key: string]: unknown }> {
   constructor() {
     super(
       'UnknownRecord',
-      (u): u is { [key: string]: unknown } => u !== null && typeof u === 'object',
+      (u): u is { [key: string]: unknown } => Object.prototype.toString.call(u) === '[object Object]',
       (u, c) => (this.is(u) ? success(u) : failure(u, c)),
       identity
     )
@@ -777,16 +775,16 @@ export const type = <P extends Props>(props: P, name: string = getInterfaceTypeN
   return new InterfaceType(
     name,
     (u): u is { [K in keyof P]: TypeOf<P[K]> } => {
-      if (!UnknownRecord.is(u)) {
-        return false
-      }
-      for (let i = 0; i < len; i++) {
-        const k = keys[i]
-        if (!hasOwnProperty.call(u, k) || !types[i].is(u[k])) {
-          return false
+      if (UnknownRecord.is(u)) {
+        for (let i = 0; i < len; i++) {
+          const k = keys[i]
+          if (!hasOwnProperty.call(u, k) || !types[i].is(u[k])) {
+            return false
+          }
         }
+        return true
       }
-      return true
+      return false
     },
     (u, c) =>
       chain(UnknownRecord.validate(u, c), o => {
@@ -884,17 +882,17 @@ export const partial = <P extends Props>(
   return new PartialType(
     name,
     (u): u is { [K in keyof P]?: TypeOf<P[K]> } => {
-      if (!UnknownRecord.is(u)) {
-        return false
-      }
-      for (let i = 0; i < len; i++) {
-        const k = keys[i]
-        const uk = u[k]
-        if (uk !== undefined && !props[k].is(uk)) {
-          return false
+      if (UnknownRecord.is(u)) {
+        for (let i = 0; i < len; i++) {
+          const k = keys[i]
+          const uk = u[k]
+          if (uk !== undefined && !props[k].is(uk)) {
+            return false
+          }
         }
+        return true
       }
-      return true
+      return false
     },
     (u, c) =>
       chain(UnknownRecord.validate(u, c), o => {
@@ -972,8 +970,6 @@ export type OutputOfDictionary<D extends Any, C extends Any> = { [K in OutputOf<
 export interface RecordC<D extends Mixed, C extends Mixed>
   extends DictionaryType<D, C, { [K in TypeOf<D>]: TypeOf<C> }, { [K in OutputOf<D>]: OutputOf<C> }, unknown> {}
 
-const isObject = (r: Record<string, unknown>) => Object.prototype.toString.call(r) === '[object Object]'
-
 /**
  * @since 1.7.1
  */
@@ -985,27 +981,21 @@ export const record = <D extends Mixed, C extends Mixed>(
   return new DictionaryType(
     name,
     (u): u is { [K in TypeOf<D>]: TypeOf<C> } => {
-      if (!UnknownRecord.is(u)) {
-        return false
-      }
-      if (!isUnknownCodec(codomain) && !isAnyCodec(codomain) && !isObject(u)) {
-        return false
+      if (UnknownRecord.is(u)) {
+        return Object.keys(u).every(k => domain.is(k) && codomain.is(u[k]))
       }
-      return Object.keys(u).every(k => domain.is(k) && codomain.is(u[k]))
+      return isAnyCodec(codomain) && Array.isArray(u)
     },
-    (u, c) =>
-      chain(UnknownRecord.validate(u, c), o => {
-        if (!isUnknownCodec(codomain) && !isAnyCodec(codomain) && !isObject(o)) {
-          return failure(u, c)
-        }
+    (u, c) => {
+      if (UnknownRecord.is(u)) {
         const a: { [key: string]: any } = {}
         const errors: Errors = []
-        const keys = Object.keys(o)
+        const keys = Object.keys(u)
         const len = keys.length
         let changed: boolean = false
         for (let i = 0; i < len; i++) {
           let k = keys[i]
-          const ok = o[k]
+          const ok = u[k]
           const domainResult = domain.validate(k, appendContext(c, k, domain, k))
           if (isLeft(domainResult)) {
             pushAll(errors, domainResult.left)
@@ -1023,8 +1013,13 @@ export const record = <D extends Mixed, C extends Mixed>(
             }
           }
         }
-        return errors.length > 0 ? failures(errors) : success((changed ? a : o) as any)
-      }),
+        return errors.length > 0 ? failures(errors) : success((changed ? a : u) as any)
+      }
+      if (isAnyCodec(codomain) && Array.isArray(u)) {
+        return success(u)
+      }
+      return failure(u, c)
+    },
     domain.encode === identity && codomain.encode === identity
       ? identity
       : a => {
@@ -1091,11 +1086,11 @@ export const union = <CS extends [Mixed, Mixed, ...Array<Mixed>]>(
     return new TaggedUnionType(
       name,
       (u): u is TypeOf<CS[number]> => {
-        if (!UnknownRecord.is(u)) {
-          return false
+        if (UnknownRecord.is(u)) {
+          const i = find(u[tag])
+          return i !== undefined ? codecs[i].is(u) : false
         }
-        const i = find(u[tag])
-        return i !== undefined ? codecs[i].is(u) : false
+        return false
       },
       (u, c) =>
         chain(UnknownRecord.validate(u, c), r => {
@@ -1203,7 +1198,7 @@ const mergeAll = (base: any, us: Array<any>): any => {
     if (u !== base) {
       equal = false
     }
-    if (isObject(u)) {
+    if (UnknownRecord.is(u)) {
       primitive = false
     }
   }
@@ -1766,7 +1761,12 @@ export const Dictionary: UnknownRecordC = UnknownRecord
 export class ObjectType extends Type<object> {
   readonly _tag: 'ObjectType' = 'ObjectType'
   constructor() {
-    super('object', UnknownRecord.is, UnknownRecord.validate, identity)
+    super(
+      'object',
+      (u): u is { [key: string]: unknown } => u !== null && typeof u === 'object',
+      (u, c) => (this.is(u) ? success(u) : failure(u, c)),
+      identity
+    )
   }
 }
 


Test patch:
diff --git a/test/ThrowReporter.ts b/test/ThrowReporter.ts
new file mode 100644
index 0000000..1fbee86
--- /dev/null
+++ b/test/ThrowReporter.ts
@@ -0,0 +1,19 @@
+import * as assert from 'assert'
+import * as t from '../src'
+import { ThrowReporter } from '../src/ThrowReporter'
+
+describe('ThrowReporter', () => {
+  it('should throw on invalid inputs', () => {
+    assert.throws(() => {
+      // tslint:disable-next-line: deprecation
+      ThrowReporter.report(t.string.decode(1))
+    })
+  })
+
+  it('should not throw on invalid inputs', () => {
+    assert.doesNotThrow(() => {
+      // tslint:disable-next-line: deprecation
+      ThrowReporter.report(t.string.decode('a'))
+    })
+  })
+})
diff --git a/test/default-types.ts b/test/default-types.ts
index 90f456d..ac4767c 100644
--- a/test/default-types.ts
+++ b/test/default-types.ts
@@ -2,23 +2,41 @@ import * as assert from 'assert'
 import * as t from '../src/index'
 import { assertSuccess, assertFailure } from './helpers'
 
-describe('Dictionary', () => {
-  it('should succeed validating a valid value', () => {
-    const T = t.UnknownRecord
-    assertSuccess(T.decode({}))
-    assertSuccess(T.decode([]))
-    assertSuccess(T.decode([1]))
-    assertSuccess(T.decode(new Number()))
-    assertSuccess(T.decode(new Date()))
+describe('UnknownRecord', () => {
+  describe('is', () => {
+    it('should return `true` for valid objects', () => {
+      const T = t.UnknownRecord
+      assert.strictEqual(T.is({}), true)
+      assert.strictEqual(T.is({ a: 1 }), true)
+    })
+
+    it('should return `false` for invalid objects', () => {
+      const T = t.UnknownRecord
+      assert.strictEqual(T.is(undefined), false)
+      assert.strictEqual(T.is(new Number()), false)
+      // #407
+      assert.strictEqual(T.is([]), false)
+    })
   })
 
-  it('should fail validating an invalid value', () => {
-    const T = t.UnknownRecord
-    assertFailure(T, 's', ['Invalid value "s" supplied to : UnknownRecord'])
-    assertFailure(T, 1, ['Invalid value 1 supplied to : UnknownRecord'])
-    assertFailure(T, true, ['Invalid value true supplied to : UnknownRecord'])
-    assertFailure(T, null, ['Invalid value null supplied to : UnknownRecord'])
-    assertFailure(T, undefined, ['Invalid value undefined supplied to : UnknownRecord'])
+  describe('decode', () => {
+    it('should succeed validating a valid value', () => {
+      const T = t.UnknownRecord
+      assertSuccess(T.decode({}))
+      assertSuccess(T.decode({ a: 1 }))
+    })
+
+    it('should fail validating an invalid value', () => {
+      const T = t.UnknownRecord
+      assertFailure(T, 's', ['Invalid value "s" supplied to : UnknownRecord'])
+      assertFailure(T, 1, ['Invalid value 1 supplied to : UnknownRecord'])
+      assertFailure(T, true, ['Invalid value true supplied to : UnknownRecord'])
+      assertFailure(T, null, ['Invalid value null supplied to : UnknownRecord'])
+      assertFailure(T, undefined, ['Invalid value undefined supplied to : UnknownRecord'])
+      assertFailure(T, new Number(), ['Invalid value 0 supplied to : UnknownRecord'])
+      // #407
+      assertFailure(T, [], ['Invalid value [] supplied to : UnknownRecord'])
+    })
   })
 })
 
diff --git a/test/partial.ts b/test/partial.ts
index c923eb2..6024000 100644
--- a/test/partial.ts
+++ b/test/partial.ts
@@ -16,35 +16,70 @@ describe('partial', () => {
   })
 
   describe('is', () => {
-    it('should check a isomorphic value', () => {
-      const T = t.partial({ a: t.number })
-      assert.strictEqual(T.is({}), true)
-      assert.strictEqual(T.is({ a: 1 }), true)
-      assert.strictEqual(T.is(undefined), false)
-      assert.strictEqual(T.is({ a: 'foo' }), false)
+    it('should return `true` on valid inputs', () => {
+      const T1 = t.partial({ a: t.number })
+      assert.strictEqual(T1.is({}), true)
+      assert.strictEqual(T1.is({ a: 1 }), true)
+
+      const T2 = t.partial({ a: NumberFromString })
+      assert.strictEqual(T2.is({}), true)
+      assert.strictEqual(T2.is({ a: 1 }), true)
     })
 
-    it('should check a prismatic value', () => {
-      const T = t.partial({ a: NumberFromString })
-      assert.strictEqual(T.is({}), true)
-      assert.strictEqual(T.is({ a: 1 }), true)
-      assert.strictEqual(T.is(undefined), false)
-      assert.strictEqual(T.is({ a: 'foo' }), false)
+    it('should return `false` on invalid inputs', () => {
+      const T1 = t.partial({ a: t.number })
+      assert.strictEqual(T1.is(undefined), false)
+      assert.strictEqual(T1.is({ a: 'foo' }), false)
+      // #407
+      assert.strictEqual(T1.is([]), false)
+
+      const T2 = t.partial({ a: NumberFromString })
+      assert.strictEqual(T2.is(undefined), false)
+      assert.strictEqual(T2.is({ a: 'foo' }), false)
+      // #407
+      assert.strictEqual(T2.is([]), false)
     })
   })
 
   describe('decode', () => {
-    it('should decode a isomorphic value', () => {
+    it('should succeed on valid inputs', () => {
       const T = t.partial({ a: t.number })
       assertSuccess(T.decode({}), {})
       assertSuccess(T.decode({ a: undefined }), { a: undefined })
       assertSuccess(T.decode({ a: 1 }), { a: 1 })
     })
 
-    it('should fail validating an invalid value', () => {
+    it('should fail on invalid inputs', () => {
       const T = t.partial({ a: t.number })
       assertFailure(T, null, ['Invalid value null supplied to : Partial<{ a: number }>'])
       assertFailure(T, { a: 's' }, ['Invalid value "s" supplied to : Partial<{ a: number }>/a: number'])
+      // #407
+      assertFailure(T, [], ['Invalid value [] supplied to : Partial<{ a: number }>'])
+    })
+
+    it('should not add optional keys', () => {
+      const T = t.partial({ a: t.number })
+      const input1 = {}
+      assertStrictEqual(T.decode(input1), input1)
+      const input2 = { a: undefined }
+      assertStrictEqual(T.decode(input2), input2)
+      assertSuccess(T.decode({ b: 1 }), { b: 1 } as any)
+      const input3 = { a: undefined, b: 1 }
+      assertStrictEqual(T.decode(input3), input3)
+    })
+
+    it('should return the same reference if validation succeeded', () => {
+      const T = t.partial({ a: t.number })
+      const value = {}
+      assertStrictEqual(T.decode(value), value)
+    })
+
+    it('should support default values', () => {
+      const T = t.partial({
+        name: withDefault(t.string, 'foo')
+      })
+      assertSuccess(T.decode({}), { name: 'foo' })
+      assertSuccess(T.decode({ name: 'a' }), { name: 'a' })
     })
   })
 
@@ -62,41 +97,16 @@ describe('partial', () => {
       assert.deepStrictEqual(T.encode({ a: undefined }), { a: undefined })
       assert.deepStrictEqual(T.encode({ a: 1 }), { a: '1' })
     })
-  })
-
-  it('should not add optional keys', () => {
-    const T = t.partial({ a: t.number })
-    const input1 = {}
-    assertStrictEqual(T.decode(input1), input1)
-    const input2 = { a: undefined }
-    assertStrictEqual(T.decode(input2), input2)
-    assertSuccess(T.decode({ b: 1 }), { b: 1 } as any)
-    const input3 = { a: undefined, b: 1 }
-    assertStrictEqual(T.decode(input3), input3)
-  })
 
-  it('should return the same reference if validation succeeded', () => {
-    const T = t.partial({ a: t.number })
-    const value = {}
-    assertStrictEqual(T.decode(value), value)
-  })
-
-  it('should return the same reference while encoding', () => {
-    const T = t.partial({ a: t.number })
-    assert.strictEqual(T.encode, t.identity)
-  })
-
-  it('should support default values', () => {
-    const T = t.partial({
-      name: withDefault(t.string, 'foo')
+    it('should return the same reference while encoding', () => {
+      const T = t.partial({ a: t.number })
+      assert.strictEqual(T.encode, t.identity)
     })
-    assertSuccess(T.decode({}), { name: 'foo' })
-    assertSuccess(T.decode({ name: 'a' }), { name: 'a' })
-  })
 
-  it('should preserve additional properties while encoding', () => {
-    const T = t.partial({ a: NumberFromString })
-    const x = { a: 1, b: 'foo' }
-    assert.deepStrictEqual(T.encode(x), { a: '1', b: 'foo' })
+    it('should preserve additional properties while encoding', () => {
+      const T = t.partial({ a: NumberFromString })
+      const x = { a: 1, b: 'foo' }
+      assert.deepStrictEqual(T.encode(x), { a: '1', b: 'foo' })
+    })
   })
 })
diff --git a/test/record.ts b/test/record.ts
index ce841ba..e982cc3 100644
--- a/test/record.ts
+++ b/test/record.ts
@@ -23,34 +23,41 @@ describe('record', () => {
   })
 
   describe('is', () => {
-    it('should check a isomorphic value', () => {
-      const T = t.record(t.string, t.number)
-      assert.strictEqual(T.is({}), true)
-      assert.strictEqual(T.is({ a: 1 }), true)
-      assert.strictEqual(T.is({ a: 'a' }), false)
-      assert.strictEqual(T.is(null), false)
-      assert.strictEqual(T.is([]), false)
-    })
+    it('should return `true` on valid inputs', () => {
+      const T1 = t.record(t.string, t.number)
+      assert.strictEqual(T1.is({}), true)
+      assert.strictEqual(T1.is({ a: 1 }), true)
 
-    it('should check a prismatic value', () => {
-      const T = t.record(t.string, NumberFromString)
-      assert.strictEqual(T.is({}), true)
-      assert.strictEqual(T.is({ a: 1 }), true)
-      assert.strictEqual(T.is({ a: 'a' }), false)
-      assert.strictEqual(T.is(null), false)
-      assert.strictEqual(T.is([]), false)
+      const T2 = t.record(t.string, NumberFromString)
+      assert.strictEqual(T2.is({}), true)
+      assert.strictEqual(T2.is({ a: 1 }), true)
+
+      const T3 = t.record(HyphenatedString, t.number)
+      assert.strictEqual(T3.is({}), true)
+      assert.strictEqual(T3.is({ 'a-a': 1 }), true)
     })
 
-    it('should check a prismatic key', () => {
-      const T = t.record(HyphenatedString, t.number)
-      assert.strictEqual(T.is({}), true)
-      assert.strictEqual(T.is({ 'a-a': 1 }), true)
-      assert.strictEqual(T.is({ aa: 1 }), false)
+    it('should return `false` on invalid inputs', () => {
+      const T1 = t.record(t.string, t.number)
+      assert.strictEqual(T1.is({ a: 'a' }), false)
+      assert.strictEqual(T1.is(null), false)
+      assert.strictEqual(T1.is(new Number()), false)
+      // #407
+      assert.strictEqual(T1.is([]), false)
+
+      const T2 = t.record(t.string, NumberFromString)
+      assert.strictEqual(T2.is({ a: 'a' }), false)
+      assert.strictEqual(T2.is(null), false)
+      // #407
+      assert.strictEqual(T2.is([]), false)
+
+      const T3 = t.record(HyphenatedString, t.number)
+      assert.strictEqual(T3.is({ aa: 1 }), false)
     })
 
-    it('should accept an array if the codomain is `unknown`', () => {
+    it('should not accept an array if the codomain is `unknown`', () => {
       const T = t.record(t.string, t.unknown)
-      assert.strictEqual(T.is([]), true)
+      assert.strictEqual(T.is([]), false)
     })
 
     it('should accept an array if the codomain is `any`', () => {
@@ -83,12 +90,12 @@ describe('record', () => {
       assertSuccess(T.decode({ ab: 1 }), { 'a-b': 1 })
     })
 
-    it('should accept an array if the codomain is `unknown`', () => {
+    it('should not decode an array if the codomain is `unknown`', () => {
       const T = t.record(t.string, t.unknown)
-      assertSuccess(T.decode([1]))
+      assertFailure(T, [1], ['Invalid value [1] supplied to : { [K in string]: unknown }'])
     })
 
-    it('should accept an array if the codomain is `any`', () => {
+    it('should decode an array if the codomain is `any`', () => {
       // tslint:disable-next-line: deprecation
       const T = t.record(t.string, t.any)
       assertSuccess(T.decode([1]))
@@ -98,24 +105,11 @@ describe('record', () => {
       const T = t.record(t.string, t.number)
       assertFailure(T, 1, ['Invalid value 1 supplied to : { [K in string]: number }'])
       assertFailure(T, { aa: 's' }, ['Invalid value "s" supplied to : { [K in string]: number }/aa: number'])
+      assertFailure(T, new Number(), ['Invalid value 0 supplied to : { [K in string]: number }'])
+      // #407
       assertFailure(T, [], ['Invalid value [] supplied to : { [K in string]: number }'])
+      // #407
       assertFailure(T, [1], ['Invalid value [1] supplied to : { [K in string]: number }'])
-      assertFailure(T, new Number(), ['Invalid value 0 supplied to : { [K in string]: number }'])
-    })
-
-    it('should fail decoding an invalid value when the codec is array if the codomain is `unknown`', () => {
-      const T = t.record(HyphenatedString, t.unknown)
-      assertFailure(
-        T,
-        [1],
-        ['Invalid value "0" supplied to : { [K in HyphenatedString]: unknown }/0: HyphenatedString']
-      )
-    })
-
-    it('should fail decoding an invalid value when the codec is array if the codomain is `any`', () => {
-      // tslint:disable-next-line: deprecation
-      const T = t.record(HyphenatedString, t.any)
-      assertFailure(T, [1], ['Invalid value "0" supplied to : { [K in HyphenatedString]: any }/0: HyphenatedString'])
     })
 
     it('should support literals as domain type', () => {
diff --git a/test/type.ts b/test/type.ts
index c2c4e6e..f7914cd 100644
--- a/test/type.ts
+++ b/test/type.ts
@@ -18,11 +18,17 @@ describe('type', () => {
   })
 
   describe('is', () => {
-    it('should check a isomorphic value', () => {
+    it('should return `true` on valid inputs', () => {
+      const T = t.type({ a: t.string })
+      assert.strictEqual(T.is({ a: 'a' }), true)
+    })
+
+    it('should return `false` on invalid inputs', () => {
       const T = t.type({ a: t.string })
       assert.strictEqual(T.is({}), false)
       assert.strictEqual(T.is({ a: 1 }), false)
-      assert.strictEqual(T.is({ a: 'a' }), true)
+      // #407
+      assert.strictEqual(T.is([]), false)
     })
 
     it('should allow additional properties', () => {
@@ -61,10 +67,12 @@ describe('type', () => {
       assertFailure(T, 1, ['Invalid value 1 supplied to : { a: string }'])
       assertFailure(T, {}, ['Invalid value undefined supplied to : { a: string }/a: string'])
       assertFailure(T, { a: 1 }, ['Invalid value 1 supplied to : { a: string }/a: string'])
+      // #407
+      assertFailure(T, [], ['Invalid value [] supplied to : { a: string }'])
     })
 
     it('should support the alias `interface`', () => {
-      const T = t.type({ a: t.string })
+      const T = t.interface({ a: t.string })
       assertSuccess(T.decode({ a: 'a' }))
     })
   })