├── .gitignore ├── mocha.opts ├── src ├── index.ts ├── deep-map.ts └── index.test.ts ├── tsconfig.build.json ├── .npmignore ├── .editorconfig ├── tsconfig.json ├── LICENSE ├── .travis.yml ├── package.json ├── tslint.json └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | /coverage/ 2 | /lib/ 3 | /node_modules/ 4 | /npm-debug.* 5 | -------------------------------------------------------------------------------- /mocha.opts: -------------------------------------------------------------------------------- 1 | src/**/*.test.ts 2 | --reporter dot 3 | --require ts-node/register 4 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { deepMapModule } from './deep-map'; 2 | 3 | export = deepMapModule; 4 | -------------------------------------------------------------------------------- /tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": [ 4 | "**/*.test.ts" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | /coverage/ 2 | /src/ 3 | /.editorconfig 4 | /.gitignore 5 | /.travis.yml 6 | /mocha.opts 7 | /tsconfig.build.json 8 | /tsconfig.json 9 | /tslint.json 10 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES5", 4 | "module": "CommonJS", 5 | "moduleResolution": "Node", 6 | "rootDir": "src", 7 | "outDir": "lib", 8 | "newLine": "lf", 9 | "declaration": true, 10 | "sourceMap": true, 11 | "forceConsistentCasingInFileNames": true, 12 | "importHelpers": true, 13 | "noEmitHelpers": true, 14 | "noEmitOnError": true, 15 | "noImplicitAny": true, 16 | "noUnusedLocals": true, 17 | "noUnusedParameters": true, 18 | "preserveConstEnums": true, 19 | "skipLibCheck": true, 20 | "strictNullChecks": true, 21 | "stripInternal": true, 22 | "suppressImplicitAnyIndexErrors": true 23 | }, 24 | "compileOnSave": false, 25 | "include": [ 26 | "src/**/*.ts" 27 | ], 28 | "exclude": [ 29 | "coverage", 30 | "node_modules", 31 | "lib" 32 | ] 33 | } 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016-2019 Akim McMath 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 7 | of the Software, and to permit persons to whom the Software is furnished to do 8 | so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '11' 4 | - '6' 5 | after_success: 6 | - npm run coveralls 7 | before_deploy: 8 | - npm run build 9 | deploy: 10 | provider: npm 11 | email: 12 | secure: LJrKNR1urq+kl4x/rnuRMZZBCVFEItYCH5XKPFlCTuKXeAtfDgXat6EPGTLINf8QYPFcB+LtgCxCOT0uHgjp/JIrBaDXIlh1R/JHwLbcuuErIV0BcSJ6E7vc+TMaDWklX81FNvo4F5S77PhcW6uNeU82jCdXcdRY4xkRew5p4/KOsbuE3JHpvSmK6hFmZQzWFV+7nsrdge/IUMbwCh4aG3BLwLl1JUT/8zPOxfcZdU9VSmsgND0eQre/PC3Q+27qIZU45wenbD5TtSe4Ps7v5bAayMp6Ftv9fhdDYZF0HCzZbJsifQhYm69gb5nRwPEKUpobaKEyShv5kQ1/Pq8Y4iJbaIV+4BLrRqhWhIfbRWO+Wkivqti8lMCgLY+2U9GPMdmqk7cr2IRYFlxGjpWFV4g4cWYBuqt3ppMnYSiOzQdEIyyiRzNc8cWDtWHNH11hDXj4FE11NqsKUljc1gfpE8BLmSTTA9ED+SfZbV1mQnSRw1BN9amvNmRK424IbXtkP/cftdU03nrCD2bB1NSaiDMKuoZx8AxywzUuF5lAZtI8/C8vUTpyKUN7rvttr3Sn+WwA7Gx5+UC/qMZ0jUElR84ujvaiGOMjlzxy83m5+EGX3n23RfWn5eZil35jz6LUeJKMMGl5UTZ4pbTTP4AWkIGeaAJfJb13o1f79LbDN7Y= 13 | api_key: 14 | secure: gQ/GQmA0o/RVL27XrBOF/wxxrOWdSgDF1VdJ6nRb90BUafK3E9BtDF+6Ba8WU2iseJuFpvkUAUYynX0jOl6bKHu/xInbboreHIJNnupfLpSrt6pUes15QPcz3CPBcbGoW1i4N0piEyjTnxA5fZyfdDkTFtuZVCDSXeLqGJYR4OWdLZ/lNP8y57Pns+Fzai3tvLAxQ18Y0DAJ6LFfvL/KEjj0NrB08XrrlHM9L+crO7BJWxZUhZROFho1aUiey1MlPF6D/xBdfTdbfnIdC6cAfE+8O4oHn8/8IPr6Se2d1KZg9+tfyxybCaARnAbisbloz38RM7zYNFZmF1vudtnwUnJJAwmvL0qQDiveIsDj8QMyRyQ7/CS69bb1gem19Eg3dIWQlQTvtdQjjv5ikpYWLkX7ok6e87ljOq/Nbtc8v5HSwoNPJW975HqbyvRDFB6dE+b5IO+yGhFRnWt61Zt+Ns05OPVgfWFbtkjRR7928rWdfJQAmz5KaA6Dgt6/cmeYj5ikNdrLMz0RImQN7lRyt4en8ZHwp0Y7zB7rMGAXWueMBqgEqYfYKdYjT93nkvW9ifoPu/5zsJwYrPOCdvYDivuxv8kirE5cdyFBWtMqJ/zdhsiTUqerG2awnCtkZuLM1zesPxQZAXe30WuQ712rQoWBhKiQWj/RZQDKvcSwBe8= 15 | skip_cleanup: true 16 | on: 17 | repo: mcmath/deep-map 18 | node: '11' 19 | tags: true 20 | after_deploy: 21 | - npm run clean 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "deep-map", 3 | "version": "2.0.0", 4 | "description": "Transforms nested values of complex objects", 5 | "main": "./lib/index.js", 6 | "types": "./lib/index.d.ts", 7 | "scripts": { 8 | "clean": "rimraf lib", 9 | "build": "npm run clean && tsc -p tsconfig.build.json", 10 | "test:lint": "tslint \"src/**/*.ts\"", 11 | "test:unit": "istanbul cover -e .ts -x \"*.test.ts\" _mocha -- --opts mocha.opts", 12 | "test:report": "npm test && open coverage/lcov-report/index.html", 13 | "test": "npm run test:lint && npm run test:unit", 14 | "coveralls": "cat coverage/lcov.info | coveralls" 15 | }, 16 | "engines": { 17 | "node": ">=6" 18 | }, 19 | "repository": { 20 | "type": "git", 21 | "url": "git+https://github.com/mcmath/deep-map.git" 22 | }, 23 | "keywords": [ 24 | "map", 25 | "deep", 26 | "recursive", 27 | "nested", 28 | "object", 29 | "array", 30 | "circular", 31 | "json", 32 | "primitive", 33 | "typescript", 34 | "typings" 35 | ], 36 | "author": "Akim McMath (http://www.mcmath.io/)", 37 | "license": "MIT", 38 | "bugs": { 39 | "url": "https://github.com/mcmath/deep-map/issues" 40 | }, 41 | "homepage": "https://github.com/mcmath/deep-map#readme", 42 | "devDependencies": { 43 | "@types/chai": "^4.1.7", 44 | "@types/es6-weak-map": "^1.2.0", 45 | "@types/lodash": "^4.14.120", 46 | "@types/mocha": "^5.2.5", 47 | "@types/sinon": "^7.0.5", 48 | "@types/sinon-chai": "^3.2.2", 49 | "chai": "^4.2.0", 50 | "coveralls": "^3.0.2", 51 | "es6-weak-map": "^2.0.2", 52 | "istanbul": "1.1.0-alpha.1", 53 | "mocha": "^5.2.0", 54 | "rimraf": "^2.6.1", 55 | "sinon": "^7.2.3", 56 | "sinon-chai": "^3.3.0", 57 | "ts-node": "^8.0.2", 58 | "tslint": "^5.1.0", 59 | "typescript": "^3.2.4" 60 | }, 61 | "dependencies": { 62 | "lodash": "^4.17.11", 63 | "tslib": "^1.6.0" 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/deep-map.ts: -------------------------------------------------------------------------------- 1 | import WeakMap = require('es6-weak-map'); 2 | import { isArray, isObject, isFunction, isNil } from 'lodash'; 3 | 4 | export interface DeepMapModule { 5 | (object: any, mapFn: MapFn, options?: Opts): T; 6 | default(object: any, mapFn: MapFn, options?: Opts): T; 7 | } 8 | 9 | export interface MapFn { 10 | (value: any, key: string | number): any; 11 | } 12 | 13 | export interface Opts { 14 | thisArg?: any; 15 | inPlace?: boolean; 16 | } 17 | 18 | export const deepMapModule: DeepMapModule = function deepMap(object: any, mapFn: MapFn, options?: Opts): any { 19 | options = isNil(options) ? {} : options; 20 | 21 | if (!mapFn) { 22 | throw new Error('mapFn is required'); 23 | } else if (!isFunction(mapFn)) { 24 | throw new TypeError('mapFn must be a function'); 25 | } else if (!isObject(options)) { 26 | throw new TypeError('options must be an object'); 27 | } 28 | 29 | return new DeepMap(mapFn, options).map(object); 30 | } as any; 31 | 32 | deepMapModule.default = deepMapModule; 33 | 34 | class DeepMap { 35 | 36 | private cache = new WeakMap(); 37 | 38 | constructor( 39 | private mapFn: MapFn, 40 | private opts: Opts 41 | ) { } 42 | 43 | public map(value: any, key?: string | number): any { 44 | return isArray(value) ? this.mapArray(value) : 45 | isObject(value) ? this.mapObject(value) : 46 | this.mapFn.call(this.opts.thisArg, value, key); 47 | } 48 | 49 | private mapArray(arr: any[]): any[] { 50 | if (this.cache.has(arr)) { 51 | return this.cache.get(arr); 52 | } 53 | 54 | let length = arr.length; 55 | let result = this.opts.inPlace ? arr : []; 56 | this.cache.set(arr, result); 57 | 58 | for (let i = 0; i < length; i++) { 59 | result[i] = this.map(arr[i], i); 60 | } 61 | 62 | return result; 63 | } 64 | 65 | private mapObject(obj: object): object { 66 | if (this.cache.has(obj)) { 67 | return this.cache.get(obj); 68 | } 69 | 70 | let result = this.opts.inPlace ? obj : {}; 71 | this.cache.set(obj, result); 72 | 73 | for (let key in obj as any) { 74 | if (obj.hasOwnProperty(key)) { 75 | result[key] = this.map(obj[key], key); 76 | } 77 | } 78 | 79 | return result; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "align": [true, "parameters", "statements"], 4 | "class-name": true, 5 | "comment-format": [true, "check-space"], 6 | "eofline": true, 7 | "forin": true, 8 | "indent": [true, "spaces"], 9 | "interface-name": [true, "never-prefix"], 10 | "jsdoc-format": true, 11 | "label-position": true, 12 | "max-line-length": [true, 120], 13 | "member-ordering": [true, {"order": "statics-first"}], 14 | "new-parens": true, 15 | "no-angle-bracket-type-assertion": true, 16 | "no-arg": true, 17 | "no-conditional-assignment": true, 18 | "no-consecutive-blank-lines": true, 19 | "no-construct": true, 20 | "no-default-export": true, 21 | "no-eval": true, 22 | "no-namespace": [true, "allow-declarations"], 23 | "no-reference": true, 24 | "no-shadowed-variable": true, 25 | "no-trailing-whitespace": true, 26 | "no-var-keyword": true, 27 | "no-var-requires": true, 28 | "one-variable-per-declaration": [true, "ignore-for-loop"], 29 | "quotemark": [true, "single", "jsx-double", "avoid-escape"], 30 | "radix": true, 31 | "semicolon": [true, "always"], 32 | "trailing-comma": [true, {"multiline": "never", "singleline": "never"}], 33 | "triple-equals": [true, "allow-null-check"], 34 | "typedef": [true, "call-signature"], 35 | "typedef-whitespace": [true, { 36 | "call-signature": "nospace", 37 | "index-signature": "nospace", 38 | "parameter": "nospace", 39 | "property-declaration": "nospace", 40 | "variable-declaration": "nospace" 41 | }, { 42 | "call-signature": "onespace", 43 | "index-signature": "onespace", 44 | "parameter": "onespace", 45 | "property-declaration": "onespace", 46 | "variable-declaration": "onespace" 47 | }], 48 | "use-isnan": true, 49 | "variable-name": [true, 50 | "check-format", 51 | "allow-pascal-case", 52 | "allow-leading-underscore", 53 | "allow-trailing-underscore" 54 | ], 55 | "whitespace": [true, 56 | "check-branch", 57 | "check-decl", 58 | "check-module", 59 | "check-operator", 60 | "check-separator", 61 | "check-type", 62 | "check-typecast" 63 | ] 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/index.test.ts: -------------------------------------------------------------------------------- 1 | import chai = require('chai'); 2 | import sinonChai = require('sinon-chai'); 3 | import sinon = require('sinon'); 4 | import deepMap from './'; 5 | 6 | declare var require: any; 7 | 8 | before(() => { 9 | chai.use(sinonChai); 10 | chai.should(); 11 | }); 12 | 13 | describe('deepMap(object, mapFn, [options])', () => { 14 | let square: (n: number) => number; 15 | 16 | beforeEach(() => { 17 | square = sinon.spy((n: number) => n * n); 18 | }); 19 | 20 | it('exports a function', () => { 21 | deepMap.should.be.a('function'); 22 | }); 23 | 24 | it('may be imported as a CommonJS module or ES2015 default import', () => { 25 | require('./').should.equal(deepMap); 26 | require('./').default.should.equal(deepMap); 27 | }); 28 | 29 | describe('@object: any', () => { 30 | 31 | it('transforms simple object', () => { 32 | deepMap({two: 2, three: 3}, square).should.deep.equal({two: 4, three: 9}); 33 | }); 34 | 35 | it('transforms simple array', () => { 36 | deepMap([2, 3], square).should.deep.equal([4, 9]); 37 | }); 38 | 39 | it('transforms object with nested objects/arrays', () => { 40 | deepMap({two: 2, obj: {three: 3, four: 4}, arr: [5, 6]}, square) 41 | .should.deep.equal({two: 4, obj: {three: 9, four: 16}, arr: [25, 36]}); 42 | }); 43 | 44 | it('transforms array with nested objects/arrays', () => { 45 | deepMap([2, {three: 3, four: 4}, [5, 6]], square) 46 | .should.deep.equal([4, {three: 9, four: 16}, [25, 36]]); 47 | }); 48 | 49 | it('transforms an object with circular references', () => { 50 | let obj = {two: 2, arr: [3, 4], self: null as any, arr2: null as any}; 51 | obj.self = obj; 52 | obj.arr2 = obj.arr; 53 | 54 | let exp = {two: 4, arr: [9, 16], self: null as any, arr2: null as any}; 55 | exp.self = exp; 56 | exp.arr2 = exp.arr; 57 | 58 | deepMap(obj, square).should.deep.equal(exp); 59 | }); 60 | 61 | }); 62 | 63 | describe('@mapFn(value: any, key: string|number): any', () => { 64 | 65 | it('throws Error if undefined', () => { 66 | deepMap.bind(null, {two: 2}).should.throw(Error); 67 | }); 68 | 69 | it('throws TypeError if not a function', () => { 70 | deepMap.bind(null, {two: 2}, 42).should.throw(TypeError); 71 | }); 72 | 73 | it('is called once per primitive value', () => { 74 | deepMap({two: 2, obj: {three: 3}, arr: [4]}, square); 75 | square.should.have.callCount(3); 76 | }); 77 | 78 | it('is called with @value as first argument', () => { 79 | deepMap({two: 2, arr: [3]}, square); 80 | square.should.have.been.calledWith(2); 81 | square.should.have.been.calledWith(3); 82 | }); 83 | 84 | it('is called with @key as second argument', () => { 85 | let {any} = sinon.match; 86 | deepMap({two: 2, arr: [3]}, square); 87 | square.should.have.been.calledWith(any, 'two'); 88 | square.should.have.been.calledWith(any, 0); 89 | }); 90 | 91 | }); 92 | 93 | describe('@options', () => { 94 | 95 | it('throws TypeError if defined but not an object', () => { 96 | deepMap.bind(null, {two: 2}, square, 42).should.throw(TypeError); 97 | }); 98 | 99 | describe('option: thisArg', () => { 100 | 101 | it('sets the context within @mapFn', () => { 102 | deepMap({two: 2, arr: [3]}, square, {thisArg: 42}); 103 | square.should.have.been.calledOn(42); 104 | }); 105 | 106 | it('defaults to undefined', () => { 107 | deepMap({two: 2, arr: [3]}, square); 108 | square.should.have.been.calledOn(undefined); 109 | }); 110 | 111 | }); 112 | 113 | describe('option: inPlace', () => { 114 | let arr: number[]; 115 | let obj: {two: number, arr: number[]}; 116 | 117 | beforeEach(() => { 118 | arr = [3]; 119 | obj = {two: 2, arr}; 120 | }); 121 | 122 | it('transforms @object in place', () => { 123 | let result = deepMap(obj, square, {inPlace: true}); 124 | result.should.deep.equal({two: 4, arr: [9]}); 125 | result.should.equal(obj); 126 | }); 127 | 128 | it('transforms sub-objects/sub-arrays in place', () => { 129 | (deepMap(obj, square, {inPlace: true}) as any).arr.should.equal(arr); 130 | }); 131 | 132 | it('defaults to false', () => { 133 | deepMap(obj, square).should.not.equal(obj); 134 | }); 135 | 136 | }); 137 | 138 | }); 139 | 140 | }); 141 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Deep Map 2 | 3 | [![Version][version-badge]][npm] 4 | [![License][license-badge]][license] 5 | [![Build][build-badge]][travis] 6 | [![Coverage][coverage-badge]][coveralls] 7 | 8 | [Install](#install) | [Usage](#usage) | [API](#api) | [TypeScript](#typescript) | [License](#license) 9 | 10 | **Deep Map** recurses through an object and transforms its primitive values 11 | – including nested values – according to some function. Essentially, 12 | it's a deep version of [`Array.prototype.map()`][array-prototype-map] that 13 | works on all objects rather than just on Arrays. Circular references are 14 | supported. 15 | 16 | To transform the *keys* of an object rather than its values, use 17 | [Deep Map Keys][deep-map-keys]. 18 | 19 | ## Install 20 | 21 | Install Deep Map via [npm][npm]. 22 | 23 | ```sh 24 | npm install --save deep-map 25 | ``` 26 | 27 | ## Usage 28 | 29 | Let's say we have an object like this: 30 | 31 | ```js 32 | const info = { 33 | name: '<%- name %>', 34 | email: '<%- email %>', 35 | keywords: ['<%- keyword1 %>', '<%- keyword2 %>'], 36 | hobbies: { 37 | primary: '<%- hobby1 %>', 38 | secondary: '<%- hobby2 %>' 39 | } 40 | }; 41 | ``` 42 | 43 | And we want to fill it with this data: 44 | 45 | ```js 46 | const data = { 47 | name: 'Samuel Johnson', 48 | email: 'sam.johnson@dictionary.com', 49 | keyword1: 'dictionary', 50 | keyword2: 'lexicography', 51 | hobby1: 'writing', 52 | hobby2: 'torying', 53 | }; 54 | ``` 55 | 56 | We can use Deep Map like this: 57 | 58 | ```js 59 | const deepMap = require('deep-map'); 60 | const template = require('lodash/template'); 61 | 62 | let result = deepMap(info, value => template(value)(data)); 63 | ``` 64 | 65 | And the result looks like this: 66 | 67 | ```js 68 | { 69 | name: 'Samuel Johnson', 70 | email: 'sam.johnson@dictionary.com', 71 | keywords: ['dictionary', 'lexicography'], 72 | hobbies: { 73 | primary: 'writing', 74 | secondary: 'torying' 75 | } 76 | } 77 | ``` 78 | 79 | ## API 80 | 81 | #### `deepMap(object, mapFn, [options])` 82 | 83 | #### Parameters 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 105 | 106 | 107 | 108 | 109 | 127 | 128 | 129 | 130 | 131 | 147 | 148 | 149 |
ParamTypeDescription
objectany 98 | The object whose values are to be transformed. Typically, this will be 99 | a complex object containing other nested objects. This object may be an 100 | 101 | Array, and may contain nested arrays whose values will 102 | be deeply transformed in the same way. The object may contain circular 103 | references. 104 |
mapFnfunction 110 | The function used to transform each primitive value. The function is 111 | called with two arguments: 112 |
    113 |
  • 114 | value <any> 115 | The value being transformed. 116 |
  • 117 |
  • 118 | key <string | number> 119 | The key or index of the value being transformed. In the case 120 | of plain objects, this will be a string; in the case of arrays, 121 | this will be a number. 122 |
  • 123 |
124 | The return value determines the value at the same position on the 125 | resulting object. 126 |
[options]object 132 | An optional options object. The following options are accepted: 133 |
    134 |
  • 135 | inPlace <boolean=false> 136 | Mutate object rather than constructing a new 137 | object. Nested objects will also be mutated. 138 |
  • 139 |
  • 140 | thisArg <any=undefined> 141 | Sets the value of 142 | this 143 | within mapFn(). 144 |
  • 145 |
146 |
150 | 151 | #### Returns 152 | 153 | Returns a new object with the same keys as `object`. If `options.inPlace` is set 154 | to `true`, the original object is returned, mutated. 155 | 156 | ## TypeScript 157 | 158 | [TypeScript][typescript] declarations are included in the package. Just import 159 | the module, and things will just work. 160 | 161 | By default, the compiler will assume that the return value will have the same 162 | shape as the input object. In most use cases, this is likely to be true. But in 163 | some cases – like the one below – the assumption breaks down. 164 | 165 | ```ts 166 | function isPositive(n: number): boolean { 167 | return n >= 0; 168 | } 169 | 170 | // COMPILER ERROR: number not assignable to boolean :( 171 | let bool: boolean = deepMap({n: 2}, isPositive).n; 172 | ``` 173 | 174 | Pass a type argument to describe the shape of the return value, and everything 175 | will be happy. 176 | 177 | ```ts 178 | let bool: boolean = deepMap<{n: boolean}>({n: 2}, isPositive).n; // :) 179 | ``` 180 | 181 | ## License 182 | 183 | Copyright © 2016–2019 Akim McMath. Licensed under the [MIT License][license]. 184 | 185 | [version-badge]: https://img.shields.io/npm/v/deep-map.svg?style=flat-square 186 | [license-badge]: https://img.shields.io/npm/l/deep-map.svg?style=flat-square 187 | [build-badge]: https://img.shields.io/travis/mcmath/deep-map/master.svg?style=flat-square 188 | [coverage-badge]: https://img.shields.io/coveralls/mcmath/deep-map/master.svg?style=flat-square&service=github 189 | [npm]: https://www.npmjs.com/package/deep-map 190 | [license]: LICENSE 191 | [travis]: https://travis-ci.org/mcmath/deep-map 192 | [coveralls]: https://coveralls.io/github/mcmath/deep-map?branch=master 193 | [deep-map-keys]: https://github.com/mcmath/deep-map-keys 194 | [typescript]: http://www.typescriptlang.org/ 195 | [array-prototype-map]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map 196 | --------------------------------------------------------------------------------