Repository: blitz-js/superjson
Pull request #29 (2020-07-23 15:39:42)

Transmit map key type as part of the value type
Since Maps are forced to have a uniform key type (see https://github.com/blitz-js/superjson/pull/6#issuecomment-661693147), it's bonkers to include it for every single key.
This commit replaces the `map` type with `map:number`, `map:string`, `map:bigint` and `map:boolean` to save some space.

Skn0tt: Similar behaviour could be interesting for `Set` - if it contains uniformily typed values
that require custom `SuperJSON` annotations, we can add that information to the `set` annotation itself.

Patch:
diff --git a/package.json b/package.json
index 2266046..b9137a3 100644
--- a/package.json
+++ b/package.json
@@ -32,10 +32,7 @@
     "printWidth": 80,
     "semi": true,
     "singleQuote": true,
-    "trailingComma": "es5",
-    "plugins": [
-      "./node_modules/prettier-plugin-import-sort/src/index.js"
-    ]
+    "trailingComma": "es5"
   },
   "name": "superjson",
   "author": "merelinguist",
@@ -46,9 +43,6 @@
   },
   "devDependencies": {
     "husky": "^4.2.5",
-    "import-sort-style-module": "^6.0.0",
-    "prettier": "^2.0.5",
-    "prettier-plugin-import-sort": "^0.0.4",
     "tsdx": "^0.13.2",
     "tslib": "^2.0.0",
     "typescript": "^3.9.6"
diff --git a/src/annotator.ts b/src/annotator.ts
index b80235a..02c1a73 100644
--- a/src/annotator.ts
+++ b/src/annotator.ts
@@ -1,4 +1,3 @@
-import { isMap } from './is';
 import { mapDeep } from './mapDeep';
 import {
   StringifiedPath,
@@ -8,24 +7,18 @@ import {
 } from './pathstringifier';
 import { Walker } from './plainer';
 import {
-  KeyTypeAnnotation,
   TypeAnnotation,
-  isKeyTypeAnnotation,
   isTypeAnnotation,
-  transformKey,
   transformValue,
-  untransformKey,
   untransformValue,
 } from './transformer';
-import * as IteratorUtils from "./iteratorutils"
 
 export interface Annotations {
   root?: TypeAnnotation;
   values?: Record<StringifiedPath, TypeAnnotation>;
-  keys?: Record<StringifiedPath, KeyTypeAnnotation>;
 }
 
-export function isAnnotations(object: any): object is Annotations {
+export const isAnnotations = (object: any): object is Annotations => {
   if (!!object.root && !isTypeAnnotation(object.root)) {
     return false;
   }
@@ -36,40 +29,13 @@ export function isAnnotations(object: any): object is Annotations {
     );
   }
 
-  if (!!object.keys) {
-    return Object.entries(object.keys).every(
-      ([key, value]) => isStringifiedPath(key) && isKeyTypeAnnotation(value)
-    );
-  }
-
   return true;
-}
+};
 
 export const makeAnnotator = () => {
   const annotations: Annotations = {};
 
   const annotator: Walker = ({ path, node }) => {
-    if (isMap(node)) {
-      const newNode = new Map<string, any>();
-
-      IteratorUtils.forEach(node.entries(), ([key, value]) => {
-        const transformed = transformKey(key);
-
-        if (transformed) {
-          newNode.set(transformed.key, value);
-
-          if (!annotations.keys) {
-            annotations.keys = {};
-          }
-
-          annotations.keys[stringifyPath([...path, transformed.key])] =
-            transformed.type;
-        }
-      })
-
-      node = newNode;
-    }
-
     const transformed = transformValue(node);
 
     if (transformed) {
@@ -82,6 +48,7 @@ export const makeAnnotator = () => {
 
         annotations.values[stringifyPath(path)] = transformed.type;
       }
+
       return transformed.value;
     } else {
       return node;
@@ -102,27 +69,12 @@ export const applyAnnotations = (plain: any, annotations: Annotations): any => {
     );
 
     for (const [path, type] of annotationsWithPathsLeavesToRoot) {
-      plain = mapDeep(plain, path, (v) =>
+      plain = mapDeep(plain, path, v =>
         untransformValue(v, type as TypeAnnotation)
       );
     }
   }
 
-  if (annotations.keys) {
-    for (const [key, type] of Object.entries(annotations.keys)) {
-      const path = parsePath(key);
-      const mapKey = path[path.length - 1];
-      const pathToMap = path.slice(0, path.length - 1);
-      const untransformedKey = untransformKey(mapKey, type);
-
-      plain = mapDeep(plain, pathToMap, (v: Map<any, any>) => {
-        v.set(untransformedKey, v.get(mapKey));
-        v.delete(mapKey);
-        return v;
-      });
-    }
-  }
-
   if (annotations.root) {
     plain = untransformValue(plain, annotations.root);
   }
diff --git a/src/iteratorutils.ts b/src/iteratorutils.ts
index 73d51e6..b2846f2 100644
--- a/src/iteratorutils.ts
+++ b/src/iteratorutils.ts
@@ -1,20 +1,23 @@
-export function forEach<T>(iterator: Iterator<T>, func: (v: T) => void) {
-    while (true) {
-      const { done, value } = iterator.next()
-      if (done) {
-        return;
-      }
-
-      func(value);
+export const forEach = <T>(iterator: Iterator<T>, func: (v: T) => void) => {
+  while (true) {
+    const { done, value } = iterator.next();
+    if (done) {
+      return;
     }
-}
 
-export function map<A, B>(iterator: Iterator<A>, map: (v: A, index: number) => B): B[] {
-    const result: B[] = [];
+    func(value);
+  }
+};
+
+export const map = <A, B>(
+  iterator: Iterator<A>,
+  map: (v: A, index: number) => B
+): B[] => {
+  const result: B[] = [];
 
-    forEach(iterator, value => {
-        result.push(map(value, result.length))
-    })
+  forEach(iterator, value => {
+    result.push(map(value, result.length));
+  });
 
-    return result;
-}
\ No newline at end of file
+  return result;
+};
diff --git a/src/pathstringifier.ts b/src/pathstringifier.ts
index 6d0eebf..7b5dfe5 100644
--- a/src/pathstringifier.ts
+++ b/src/pathstringifier.ts
@@ -5,7 +5,10 @@ const escape = (key: string) => key.replace(/\./g, '\\.');
 const unescape = (k: string) => k.replace(/\\\./g, '.');
 
 export const stringifyPath = (path: Path): StringifiedPath =>
-  path.map(String).map(escape).join('.');
+  path
+    .map(String)
+    .map(escape)
+    .join('.');
 
 export const parsePath = (string: StringifiedPath): Path =>
   string.split(/(?<!\\)\./g).map(unescape);
diff --git a/src/plainer.ts b/src/plainer.ts
index bee7de8..e1c8f53 100644
--- a/src/plainer.ts
+++ b/src/plainer.ts
@@ -1,4 +1,4 @@
-import * as IteratorUtils from "./iteratorutils"
+import * as IteratorUtils from './iteratorutils';
 import { isArray, isMap, isPlainObject, isPrimitive, isSet } from './is';
 
 interface WalkerValue {
@@ -18,7 +18,7 @@ const entries = (object: object | Map<any, any>): Iterator<[any, any]> => {
   }
 
   if (isPlainObject(object)) {
-    return Object.entries(object).values()
+    return Object.entries(object).values();
   }
 
   throw new Error('Illegal Argument: ' + typeof object);
@@ -46,14 +46,16 @@ export const plainer = (
 
   if (isArray(object) || isSet(object)) {
     return IteratorUtils.map(object.values(), (value, index) =>
-    plainer(value, walker, [...path, index], alreadySeenObjects)
-  );
+      plainer(value, walker, [...path, index], alreadySeenObjects)
+    );
   }
 
   if (isPlainObject(object) || isMap(object)) {
-    return Object.fromEntries(IteratorUtils.map(entries(object), ([key, value]) => [
-      key,
-      plainer(value, walker, [...path, key], alreadySeenObjects),
-    ]));
+    return Object.fromEntries(
+      IteratorUtils.map(entries(object), ([key, value]) => [
+        key,
+        plainer(value, walker, [...path, key], alreadySeenObjects),
+      ])
+    );
   }
 };
diff --git a/src/transformer.ts b/src/transformer.ts
index 058a215..a137c0f 100644
--- a/src/transformer.ts
+++ b/src/transformer.ts
@@ -9,6 +9,7 @@ import {
   isRegExp,
   isSet,
   isUndefined,
+  isString,
 } from './is';
 
 export type PrimitiveTypeAnnotation =
@@ -20,7 +21,13 @@ export type PrimitiveTypeAnnotation =
 
 type LeafTypeAnnotation = PrimitiveTypeAnnotation | 'regexp' | 'Date';
 
-type ContainerTypeAnnotation = 'map' | 'set';
+type MapTypeAnnotation =
+  | 'map:number'
+  | 'map:string'
+  | 'map:bigint'
+  | 'map:boolean';
+
+type ContainerTypeAnnotation = MapTypeAnnotation | 'set';
 
 export type TypeAnnotation = LeafTypeAnnotation | ContainerTypeAnnotation;
 
@@ -39,7 +46,15 @@ export const isPrimitiveTypeAnnotation = (
 };
 
 const ALL_TYPE_ANNOTATIONS: TypeAnnotation[] = ALL_PRIMITIVE_TYPE_ANNOTATIONS.concat(
-  ['map', 'regexp', 'set', 'Date']
+  [
+    'map:number',
+    'map:string',
+    'map:bigint',
+    'map:boolean',
+    'regexp',
+    'set',
+    'Date',
+  ]
 );
 
 export const isTypeAnnotation = (value: any): value is TypeAnnotation => {
@@ -85,10 +100,34 @@ export const transformValue = (
       type: 'regexp',
     };
   } else if (isMap(value)) {
-    return {
-      value: value,
-      type: 'map',
-    };
+    const { done: valueIsEmpty, value: firstKey } = value.keys().next();
+    const returnValueDoesntMatter = valueIsEmpty;
+    if (returnValueDoesntMatter || isString(firstKey)) {
+      return { value, type: 'map:string' };
+    }
+
+    if (isNumber(firstKey)) {
+      return {
+        value: value,
+        type: 'map:number',
+      };
+    }
+
+    if (isBigint(firstKey)) {
+      return {
+        value: value,
+        type: 'map:bigint',
+      };
+    }
+
+    if (isBoolean(firstKey)) {
+      return {
+        value: value,
+        type: 'map:boolean',
+      };
+    }
+
+    throw new Error('Key type not supported.');
   }
 
   return undefined;
@@ -108,8 +147,14 @@ export const untransformValue = (json: any, type: TypeAnnotation) => {
       return Number.POSITIVE_INFINITY;
     case '-Infinity':
       return Number.NEGATIVE_INFINITY;
-    case 'map':
+    case 'map:number':
+      return new Map(Object.entries(json).map(([k, v]) => [Number(k), v]));
+    case 'map:string':
       return new Map(Object.entries(json));
+    case 'map:boolean':
+      return new Map(Object.entries(json).map(([k, v]) => [Boolean(k), v]));
+    case 'map:bigint':
+      return new Map(Object.entries(json).map(([k, v]) => [BigInt(k), v]));
     case 'set':
       return new Set(json as unknown[]);
     case 'regexp': {
@@ -122,48 +167,3 @@ export const untransformValue = (json: any, type: TypeAnnotation) => {
       return json;
   }
 };
-
-export type KeyTypeAnnotation = PrimitiveTypeAnnotation | 'number' | 'boolean';
-
-export function isKeyTypeAnnotation(
-  string: unknown
-): string is KeyTypeAnnotation {
-  return (
-    string === 'number' ||
-    string === 'boolean' ||
-    isPrimitiveTypeAnnotation(string)
-  );
-}
-
-export function transformKey(
-  key: any
-): { key: string; type: KeyTypeAnnotation } | undefined {
-  if (isNumber(key)) {
-    return { key: '' + key, type: 'number' };
-  }
-
-  if (isBoolean(key)) {
-    return { key: '' + key, type: 'boolean' };
-  }
-
-  const transformed = transformValue(key)!;
-  if (transformed) {
-    return {
-      key: '' + transformed.value,
-      type: transformed.type as KeyTypeAnnotation,
-    };
-  }
-
-  return undefined;
-}
-
-export function untransformKey(key: any, type: KeyTypeAnnotation): any {
-  switch (type) {
-    case 'number':
-      return Number(key);
-    case 'boolean':
-      return Boolean(key);
-    default:
-      return untransformValue(key, type);
-  }
-}
diff --git a/yarn.lock b/yarn.lock
index e67ab06..1b036b9 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -40,28 +40,6 @@
     semver "^5.4.1"
     source-map "^0.5.0"
 
-"@babel/core@^7.2.2":
-  version "7.10.5"
-  resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.10.5.tgz#1f15e2cca8ad9a1d78a38ddba612f5e7cdbbd330"
-  integrity sha512-O34LQooYVDXPl7QWCdW9p4NR+QlzOr7xShPPJz8GsuCU3/8ua/wqTr7gmnxXv+WBESiGU/G5s16i6tUvHkNb+w==
-  dependencies:
-    "@babel/code-frame" "^7.10.4"
-    "@babel/generator" "^7.10.5"
-    "@babel/helper-module-transforms" "^7.10.5"
-    "@babel/helpers" "^7.10.4"
-    "@babel/parser" "^7.10.5"
-    "@babel/template" "^7.10.4"
-    "@babel/traverse" "^7.10.5"
-    "@babel/types" "^7.10.5"
-    convert-source-map "^1.7.0"
-    debug "^4.1.0"
-    gensync "^1.0.0-beta.1"
-    json5 "^2.1.2"
-    lodash "^4.17.19"
-    resolve "^1.3.2"
-    semver "^5.4.1"
-    source-map "^0.5.0"
-
 "@babel/generator@^7.10.4", "@babel/generator@^7.4.0":
   version "7.10.4"
   resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.10.4.tgz#e49eeed9fe114b62fa5b181856a43a5e32f5f243"
@@ -72,15 +50,6 @@
     lodash "^4.17.13"
     source-map "^0.5.0"
 
-"@babel/generator@^7.10.5":
-  version "7.10.5"
-  resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.10.5.tgz#1b903554bc8c583ee8d25f1e8969732e6b829a69"
-  integrity sha512-3vXxr3FEW7E7lJZiWQ3bM4+v/Vyr9C+hpolQ8BGFr9Y8Ri2tFLWTixmwKBafDujO1WVah4fhZBeU1bieKdghig==
-  dependencies:
-    "@babel/types" "^7.10.5"
-    jsesc "^2.5.1"
-    source-map "^0.5.0"
-
 "@babel/helper-annotate-as-pure@^7.10.4":
   version "7.10.4"
   resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.10.4.tgz#5bf0d495a3f757ac3bda48b5bf3b3ba309c72ba3"
@@ -195,19 +164,6 @@
     "@babel/types" "^7.10.4"
     lodash "^4.17.13"
 
-"@babel/helper-module-transforms@^7.10.5":
-  version "7.10.5"
-  resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.10.5.tgz#120c271c0b3353673fcdfd8c053db3c544a260d6"
-  integrity sha512-4P+CWMJ6/j1W915ITJaUkadLObmCRRSC234uctJfn/vHrsLNxsR8dwlcXv9ZhJWzl77awf+mWXSZEKt5t0OnlA==
-  dependencies:
-    "@babel/helper-module-imports" "^7.10.4"
-    "@babel/helper-replace-supers" "^7.10.4"
-    "@babel/helper-simple-access" "^7.10.4"
-    "@babel/helper-split-export-declaration" "^7.10.4"
-    "@babel/template" "^7.10.4"
-    "@babel/types" "^7.10.5"
-    lodash "^4.17.19"
-
 "@babel/helper-optimise-call-expression@^7.10.4":
   version "7.10.4"
   resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.10.4.tgz#50dc96413d594f995a77905905b05893cd779673"
@@ -296,11 +252,6 @@
     chalk "^2.0.0"
     js-tokens "^4.0.0"
 
-"@babel/parser@^7.0.0-beta.54", "@babel/parser@^7.10.5":
-  version "7.10.5"
-  resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.10.5.tgz#e7c6bf5a7deff957cec9f04b551e2762909d826b"
-  integrity sha512-wfryxy4bE1UivvQKSQDU4/X6dr+i8bctjUjj8Zyt3DQy7NtPizJXT8M52nqpNKL+nq2PW8lxk4ZqLj0fD4B4hQ==
-
 "@babel/parser@^7.1.0", "@babel/parser@^7.10.4", "@babel/parser@^7.4.3", "@babel/parser@^7.7.0":
   version "7.10.4"
   resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.10.4.tgz#9eedf27e1998d87739fb5028a5120557c06a1a64"
@@ -840,21 +791,6 @@
     "@babel/parser" "^7.10.4"
     "@babel/types" "^7.10.4"
 
-"@babel/traverse@^7.0.0-beta.54", "@babel/traverse@^7.10.5":
-  version "7.10.5"
-  resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.10.5.tgz#77ce464f5b258be265af618d8fddf0536f20b564"
-  integrity sha512-yc/fyv2gUjPqzTz0WHeRJH2pv7jA9kA7mBX2tXl/x5iOE81uaVPuGPtaYk7wmkx4b67mQ7NqI8rmT2pF47KYKQ==
-  dependencies:
-    "@babel/code-frame" "^7.10.4"
-    "@babel/generator" "^7.10.5"
-    "@babel/helper-function-name" "^7.10.4"
-    "@babel/helper-split-export-declaration" "^7.10.4"
-    "@babel/parser" "^7.10.5"
-    "@babel/types" "^7.10.5"
-    debug "^4.1.0"
-    globals "^11.1.0"
-    lodash "^4.17.19"
-
 "@babel/traverse@^7.1.0", "@babel/traverse@^7.10.4", "@babel/traverse@^7.4.3", "@babel/traverse@^7.7.0":
   version "7.10.4"
   resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.10.4.tgz#e642e5395a3b09cc95c8e74a27432b484b697818"
@@ -879,15 +815,6 @@
     lodash "^4.17.13"
     to-fast-properties "^2.0.0"
 
-"@babel/types@^7.0.0-beta.54", "@babel/types@^7.10.5":
-  version "7.10.5"
-  resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.10.5.tgz#d88ae7e2fde86bfbfe851d4d81afa70a997b5d15"
-  integrity sha512-ixV66KWfCI6GKoA/2H9v6bQdbfXEwwpOdQ8cRvb4F+eyvhlaHxWFMQB4+3d9QFJXZsiiiqVrewNV0DFEQpyT4Q==
-  dependencies:
-    "@babel/helper-validator-identifier" "^7.10.4"
-    lodash "^4.17.19"
-    to-fast-properties "^2.0.0"
-
 "@cnakazawa/watch@^1.0.3":
   version "1.0.4"
   resolved "https://registry.yarnpkg.com/@cnakazawa/watch/-/watch-1.0.4.tgz#f864ae85004d0fcab6f50be9141c4da368d1656a"
@@ -1742,7 +1669,7 @@ buffer-from@1.x, buffer-from@^1.0.0:
   resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef"
   integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==
 
-builtin-modules@^3.0.0, builtin-modules@^3.1.0:
+builtin-modules@^3.1.0:
   version "3.1.0"
   resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-3.1.0.tgz#aad97c15131eb76b65b50ef208e7584cd76a7484"
   integrity sha512-k0KL0aWZuBt2lrxrcASWDfwOLMnodeQjodT/1SxEQAXsHANgo6ZC/VEaSEHCXt7aSTZ4/4H5LKa+tBXmW7Vtvw==
@@ -1762,25 +1689,6 @@ cache-base@^1.0.1:
     union-value "^1.0.0"
     unset-value "^1.0.0"
 
-caller-callsite@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/caller-callsite/-/caller-callsite-2.0.0.tgz#847e0fce0a223750a9a027c54b33731ad3154134"
-  integrity sha1-hH4PzgoiN1CpoCfFSzNzGtMVQTQ=
-  dependencies:
-    callsites "^2.0.0"
-
-caller-path@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/caller-path/-/caller-path-2.0.0.tgz#468f83044e369ab2010fac5f06ceee15bb2cb1f4"
-  integrity sha1-Ro+DBE42mrIBD6xfBs7uFbsssfQ=
-  dependencies:
-    caller-callsite "^2.0.0"
-
-callsites@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/callsites/-/callsites-2.0.0.tgz#06eb84f00eea413da86affefacbffb36093b3c50"
-  integrity sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA=
-
 callsites@^3.0.0:
   version "3.1.0"
   resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73"
@@ -2026,16 +1934,6 @@ core-util-is@1.0.2:
   resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
   integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=
 
-cosmiconfig@^5.0.5:
-  version "5.2.1"
-  resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-5.2.1.tgz#040f726809c591e77a17c0a3626ca45b4f168b1a"
-  integrity sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA==
-  dependencies:
-    import-fresh "^2.0.0"
-    is-directory "^0.3.1"
-    js-yaml "^3.13.1"
-    parse-json "^4.0.0"
-
 cosmiconfig@^6.0.0:
   version "6.0.0"
   resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-6.0.0.tgz#da4fee853c52f6b1e6935f41c1a2fc50bd4a9982"
@@ -2713,16 +2611,6 @@ find-cache-dir@^3.2.0:
     make-dir "^3.0.2"
     pkg-dir "^4.1.0"
 
-find-line-column@^0.5.2:
-  version "0.5.2"
-  resolved "https://registry.yarnpkg.com/find-line-column/-/find-line-column-0.5.2.tgz#db00238ff868551a182e74a103416d295a98c8ca"
-  integrity sha1-2wAjj/hoVRoYLnShA0FtKVqYyMo=
-
-find-root@^1.0.0:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/find-root/-/find-root-1.1.0.tgz#abcfc8ba76f708c42a97b3d685b7e9450bfb9ce4"
-  integrity sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==
-
 find-up@^2.0.0, find-up@^2.1.0:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/find-up/-/find-up-2.1.0.tgz#45d1b7e506c717ddd482775a2b77920a3c0c57a7"
@@ -3058,14 +2946,6 @@ ignore@^4.0.6:
   resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc"
   integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==
 
-import-fresh@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-2.0.0.tgz#d81355c15612d386c61f9ddd3922d4304822a546"
-  integrity sha1-2BNVwVYS04bGH53dOSLUMEgipUY=
-  dependencies:
-    caller-path "^2.0.0"
-    resolve-from "^3.0.0"
-
 import-fresh@^3.0.0, import-fresh@^3.1.0:
   version "3.2.1"
   resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.2.1.tgz#633ff618506e793af5ac91bf48b72677e15cbe66"
@@ -3082,60 +2962,6 @@ import-local@^2.0.0:
     pkg-dir "^3.0.0"
     resolve-cwd "^2.0.0"
 
-import-sort-config@^6.0.0:
-  version "6.0.0"
-  resolved "https://registry.yarnpkg.com/import-sort-config/-/import-sort-config-6.0.0.tgz#7313775b761eb479ab2d383945ecb15c008763b8"
-  integrity sha512-FJpF2F3+30JXqH1rJKeajxoSCHCueai3/0ntDN4y3GJL5pjnLDt/VjCy5FzjH7u0NHnllL/zVEf1wfmsVxJlPQ==
-  dependencies:
-    cosmiconfig "^5.0.5"
-    find-root "^1.0.0"
-    minimatch "^3.0.4"
-    resolve-from "^4.0.0"
-
-import-sort-parser-babylon@^6.0.0:
-  version "6.0.0"
-  resolved "https://registry.yarnpkg.com/import-sort-parser-babylon/-/import-sort-parser-babylon-6.0.0.tgz#e1a4c28e0794ad7d9ff36cd045559d8ca8c38be7"
-  integrity sha512-NyShTiNhTh4Vy7kJUVe6CuvOaQAzzfSIT72wtp3CzGjz8bHjNj59DCAjncuviicmDOgVAgmLuSh1WMcLYAMWGg==
-  dependencies:
-    "@babel/core" "^7.2.2"
-    "@babel/parser" "^7.0.0-beta.54"
-    "@babel/traverse" "^7.0.0-beta.54"
-    "@babel/types" "^7.0.0-beta.54"
-    find-line-column "^0.5.2"
-
-import-sort-parser-typescript@^6.0.0:
-  version "6.0.0"
-  resolved "https://registry.yarnpkg.com/import-sort-parser-typescript/-/import-sort-parser-typescript-6.0.0.tgz#98e73cef9e077d073e798722ed59e215b51c17e2"
-  integrity sha512-pgxnr3I156DonupQriNsgDb2zJN9TxrqCCIN1rwT/6SDO1rkJb+a0fjqshCjlgacTSA92oPAp1eAwmQUeZi3dw==
-  dependencies:
-    typescript "^3.2.4"
-
-import-sort-parser@^6.0.0:
-  version "6.0.0"
-  resolved "https://registry.yarnpkg.com/import-sort-parser/-/import-sort-parser-6.0.0.tgz#0d901f264d98ed7caaae71f66128a686f828f2f4"
-  integrity sha512-H5L+d6HnqHvThB0GmAA3/43Sv74oCwL0iMk3/ixOv0LRJ69rCyHXeG/+UadMHrD2FefEmgPIWboEPAG7gsQrkA==
-
-import-sort-style-module@^6.0.0:
-  version "6.0.0"
-  resolved "https://registry.yarnpkg.com/import-sort-style-module/-/import-sort-style-module-6.0.0.tgz#3149df4785bae804ed32630634ed49e405fa7cad"
-  integrity sha512-Oxd256EVt6TAgawhIDuKnNHWumzHMHFWhVncBBvlHVnx69B4GP/Gu4Xo+gjxtqSEKEvam5ajUkNvnsXLDMDjKg==
-
-import-sort-style@^6.0.0:
-  version "6.0.0"
-  resolved "https://registry.yarnpkg.com/import-sort-style/-/import-sort-style-6.0.0.tgz#088523f056e5064c34a6426f4733674d81b42e6a"
-  integrity sha512-z0H5PKs7YoDeKxNYXv2AA1mjjZFY07fjeNCXUdTM3ymJtWeeEoTm8CQkFm2l+KPZoMczIvdwzJpWkkOamBnsPw==
-
-import-sort@^6.0.0:
-  version "6.0.0"
-  resolved "https://registry.yarnpkg.com/import-sort/-/import-sort-6.0.0.tgz#48ba2a7b53f2566ca1dd004327ea271321ad64ff"
-  integrity sha512-XUwSQMGAGmcW/wfshFE0gXgb1NPF6ibbQD6wDr3KRDykZf/lZj0jf58Bwa02xNb8EE59oz7etFe9OHnJocUW5Q==
-  dependencies:
-    detect-newline "^2.1.0"
-    import-sort-parser "^6.0.0"
-    import-sort-style "^6.0.0"
-    is-builtin-module "^3.0.0"
-    resolve "^1.8.1"
-
 imurmurhash@^0.1.4:
   version "0.1.4"
   resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea"
@@ -3218,13 +3044,6 @@ is-buffer@^1.1.5:
   resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be"
   integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==
 
-is-builtin-module@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/is-builtin-module/-/is-builtin-module-3.0.0.tgz#137d3d2425023a19a660fb9dd6ddfabe52c03466"
-  integrity sha512-/93sDihsAD652hrMEbJGbMAVBf1qc96kyThHQ0CAOONHaE3aROLpTjDe4WQ5aoC5ITHFxEq1z8XqSU7km+8amw==
-  dependencies:
-    builtin-modules "^3.0.0"
-
 is-callable@^1.1.4, is-callable@^1.2.0:
   version "1.2.0"
   resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.0.tgz#83336560b54a38e35e3a2df7afd0454d691468bb"
@@ -3274,11 +3093,6 @@ is-descriptor@^1.0.0, is-descriptor@^1.0.2:
     is-data-descriptor "^1.0.0"
     kind-of "^6.0.2"
 
-is-directory@^0.3.1:
-  version "0.3.1"
-  resolved "https://registry.yarnpkg.com/is-directory/-/is-directory-0.3.1.tgz#61339b6f2475fc772fd9c9d83f5c8575dc154ae1"
-  integrity sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE=
-
 is-extendable@^0.1.0, is-extendable@^0.1.1:
   version "0.1.1"
   resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89"
@@ -4095,11 +3909,6 @@ lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.4:
   resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548"
   integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==
 
-lodash@^4.17.19:
-  version "4.17.19"
-  resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.19.tgz#e48ddedbe30b3321783c5b4301fbd353bc1e4a4b"
-  integrity sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==
-
 log-symbols@^2.2.0:
   version "2.2.0"
   resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-2.2.0.tgz#5740e1c5d6f0dfda4ad9323b5332107ef6b4c40a"
@@ -4761,26 +4570,11 @@ prettier-linter-helpers@^1.0.0:
   dependencies:
     fast-diff "^1.1.2"
 
-prettier-plugin-import-sort@^0.0.4:
-  version "0.0.4"
-  resolved "https://registry.yarnpkg.com/prettier-plugin-import-sort/-/prettier-plugin-import-sort-0.0.4.tgz#29fb80669a1a7cef5fb841933b7aaf80f5e80cb4"
-  integrity sha512-KyPKnWbFER9NJk+qidbGYSPO82wF1OSNdnZGtVSBG6Te0ZAWOvZ5fDeTaaHczG8UIaePgSNE1H1DoUMAykjsEg==
-  dependencies:
-    import-sort "^6.0.0"
-    import-sort-config "^6.0.0"
-    import-sort-parser-babylon "^6.0.0"
-    import-sort-parser-typescript "^6.0.0"
-
 prettier@^1.19.1:
   version "1.19.1"
   resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.19.1.tgz#f7d7f5ff8a9cd872a7be4ca142095956a60797cb"
   integrity sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew==
 
-prettier@^2.0.5:
-  version "2.0.5"
-  resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.0.5.tgz#d6d56282455243f2f92cc1716692c08aa31522d4"
-  integrity sha512-7PtVymN48hGcO4fGjybyBSIWDsLU4H4XlvOHfq91pz9kkGlonzwTfYkaIEwiRg/dAJF9YlbsduBAgtYLi+8cFg==
-
 pretty-format@^24.9.0:
   version "24.9.0"
   resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-24.9.0.tgz#12fac31b37019a4eea3c11aa9a959eb7628aa7c9"
@@ -5913,11 +5707,6 @@ type-fest@^0.8.1:
   resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d"
   integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==
 
-typescript@^3.2.4:
-  version "3.9.7"
-  resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.7.tgz#98d600a5ebdc38f40cb277522f12dc800e9e25fa"
-  integrity sha512-BLbiRkiBzAwsjut4x/dsibSTB6yWpwT5qWmC2OfuCg3GgVQCSgMs4vEctYPhsaGtd0AeuuHMkjZ2h2WG8MSzRw==
-
 typescript@^3.7.3, typescript@^3.9.6:
   version "3.9.6"
   resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.6.tgz#8f3e0198a34c3ae17091b35571d3afd31999365a"


Test patch:
diff --git a/src/index.test.ts b/src/index.test.ts
index 6582fa3..69b9ba3 100644
--- a/src/index.test.ts
+++ b/src/index.test.ts
@@ -75,22 +75,22 @@ describe('stringify & parse', () => {
 
     'works for Maps': {
       input: {
-        a: new Map([[1, 'a']]),
+        a: new Map([
+          [1, 'a'],
+          [NaN, 'b'],
+        ]),
         b: new Map([['2', 'b']]),
-        c: new Map([[undefined, 5]]),
         d: new Map([[true, 'true key']]),
       },
 
       output: {
         a: {
           1: 'a',
+          NaN: 'b',
         },
         b: {
           2: 'b',
         },
-        c: {
-          undefined: 5,
-        },
         d: {
           true: 'true key',
         },
@@ -98,15 +98,9 @@ describe('stringify & parse', () => {
 
       outputAnnotations: {
         values: {
-          a: 'map',
-          b: 'map',
-          c: 'map',
-          d: 'map',
-        },
-        keys: {
-          'a.1': 'number',
-          'c.undefined': 'undefined',
-          'd.true': 'boolean',
+          a: 'map:number',
+          b: 'map:string',
+          d: 'map:boolean',
         },
       },
     },