Skip to content

Commit d92ca9f

Browse files
committed
v1.0.0 Initial Release
#### Features - Package exports a single function, `deepMapKeys()` #### Development - Added tests written in CoffeeScript - Set up CI and automatic deployment via Travis - Added configuration files, e.g. `.gitignore`, `.travis.yml` #### Documentation - All functionality documented in `README.md` - MIT License added
1 parent a70d686 commit d92ca9f

11 files changed

+392
-0
lines changed

.editorconfig

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
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

.eslintrc.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
extends: eslint:recommended
2+
parserOptions:
3+
ecmaVersion: 5
4+
env:
5+
node: true

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
/coverage/
2+
/node_modules/
3+
/npm-debug.log

.npmignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
/coverage/
2+
/test/
3+
/.editorconfig
4+
/.eslintrc.yml
5+
/.gitignore
6+
/.travis.yml

.travis.yml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
language: node_js
2+
node_js:
3+
- "6"
4+
- "4"
5+
- "0.12"
6+
- "0.10"
7+
after_success:
8+
- npm run coveralls
9+
deploy:
10+
provider: npm
11+
email:
12+
secure: RgE+z8gZdN6PAaEnzPK3EGiv9crsbsVzZuVVM+TsrVwQU1R0/NzbL4nNP85vqS8ZBAIrA9adZIDGNtOpZ8ZGmbgd5CKVtizTF3HFtH2ctAmLerPY+HeniXkzt1+NoVZIzDgE78ubGgT6dRfpwmfyXzE83obdcQUVVBKQIR94295o3pu73oUj8a03IPD4Njbdsd8std+mzRPXXcgV3oVkcsuEVK5HifvzS7LC1owimpTrWvJ28qcUJXp7KZBpdCL3zFApsbhykOfToDHqf1RpXcnD74hkOC8Uc0S4a2ILeCl55OkjiJMzNyM1uqwHaBIUG5JK5+aFUMnkAsHRlrmdJoFeJpLxigUF8QZQYSRBNgzf1V7F4mt6FFd9keksOPJI25KqQdpOSC0He+4IJtpu97iXw2GOhPZ31KUg63wHsPHbqzBDk8xqLqaA+V8Z/4ccMpsQFd+/txzbVhQDZDD/jzLLM4gXsV7fhN5fxX1oZcIiFb+OnG2UYQsjdVPkVjJrDUrSC3HQQUK/XXK5/hwm28emQjBZkRFjE127HiVQi7FCHwJzSoAsypPezL3FJ3kNuSJMOkci8dc0R18kOvig0ofFtogYA/qVsmHl7BB+h0MLkzlV6Cl3DgzaArU4PUp827btZ2bFFF1I1h7FC1yfx3+wi6O1TaMRzt2sxs1hnAA=
13+
api_key:
14+
secure: LM2UzLwKTAuxxXgIZXUb2YV/eWMJm2L5oWbL8OW8z4q5CGmAOgw+iAzqW2zI8daozY3u49oY4aRM1emBjmS3rl12CFHoS1WAO49ypHfwEcWYmHIvEoBanIrVDrapJhv6CYMQKGlUfdTDEpw+cUVY+Vi/ioVBCCgeT0dljtW5JnVFhYeZ2e2P9yazXYMnMbB1Fdb42TY5WyBxAtXqPd0kdgiNuYTETWibKJc1h5ttV0LVGnV78ta+K/+M5DO1/AHE28raIfaH+HMaEcXbA7ygfWedQlcoXZNUe/twQNF3ihfBxBbtVwjCWXwwgkzJG6Exsy6rjmyVTg6p+FDFy1mHgLEJmXHBcBID4kwH+Qg1gnwob+bONUVzVsx946ppxItzshxyUBBayUEovH9Jgr5vf2Jm0ZYHAazZToOvr4NMRdK1MdrsSKKLNluwT/iXbW581SkBtHjTmpCBWS944UDBOYbxXgIT21p98veK99Hiksaqr4VErMMD9UBefyEl5jhC7QmyqH88IF0LlAHOdGGdWHvCbS9MhwsJM6zVLBmLDe237lOoq6Ib2TZGKAT69B1Qd1wuhc5PQtc6S5hRMRaClV3JFHXvdNMZ6B+ISNHzV76xCH3hxfv1/TJjZrsOHgpcqgQ4IWhkPFrgG3Dc5PG/WBwAE2jKmevvK4T5b2G5Cyg=
15+
skip_cleanup: true
16+
on:
17+
repo: akim-mcmath/deep-map-keys
18+
node: "4"
19+
tags: true

LICENSE

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
Copyright (c) 2016 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.

README.md

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
# deep-map-keys
2+
3+
[![Version][version-badge]][npm]
4+
[![License][license-badge]][license]
5+
[![Build][build-badge]][travis]
6+
[![Coverage][coverage-badge]][coveralls]
7+
[![Dependencies][dependencies-badge]][gemnasium]
8+
9+
Recurses through a JSON-like object and transforms its keys, returning a new object.
10+
11+
## Install
12+
13+
Install via [npm][npm].
14+
15+
```sh
16+
npm install --save deep-map-keys
17+
```
18+
19+
## Usage
20+
21+
Suppose we want to change the keys of an object from [snake case][snake-case] to
22+
[camel case][camel-case]. We can do something like this:
23+
24+
```js
25+
const deepMapKeys = require('deep-map-keys');
26+
27+
let comment = {
28+
comment_id: 42,
29+
user_id: 1024,
30+
user_name: 'Mufasa',
31+
content: 'You deliberately disobeyed me.',
32+
viewed_by: [
33+
{ user_id: 3820, user_name: 'Zazu' },
34+
{ user_id: 8391, user_name: 'Rafiki' }
35+
]
36+
};
37+
38+
let result = deepMapKeys(comment, key => {
39+
return key.replace(/_(\w)/g, (match, char) => char.toUpperCase());
40+
});
41+
42+
console.log(result); /*
43+
{
44+
commentId: 42,
45+
userId: 1024,
46+
userName: 'Mufasa',
47+
content: 'You deliberately disobeyed me.',
48+
viewedBy: [
49+
{ userId: 3820, userName: 'Rafiki' },
50+
{ userId: 8391, userName: 'Zazu' }
51+
]
52+
};
53+
*/
54+
```
55+
56+
## API
57+
58+
### `deepMapKeys(object, transformFn, [options])`
59+
60+
Applies `transformFn` to each key in an object. Keys are visited recursively,
61+
so nested keys will be transformed. A new object is always returned; the
62+
original object is unmodified.
63+
64+
##### object
65+
66+
`object`
67+
68+
The object whose keys are to be transformed. This object may be an `Array`.
69+
70+
##### transformFn
71+
72+
`function`
73+
74+
The function to call for each key. The return value of the function
75+
determines the transformed value. The function is called with a single
76+
argument:
77+
78+
* **key**: The key being transformed.
79+
80+
##### options
81+
82+
`object` (optional)
83+
84+
An options object. The following options are accepted:
85+
86+
* **thisArg**: Sets the value of `this` within `transformFn`.
87+
88+
#### Returns
89+
90+
`object`
91+
92+
Returns a new object.
93+
94+
## License
95+
96+
Copyright © 2016 Akim McMath. Licensed under the [MIT License][license].
97+
98+
[version-badge]: https://img.shields.io/npm/v/deep-map-keys.svg?style=flat-square
99+
[license-badge]: https://img.shields.io/npm/l/deep-map-keys.svg?style=flat-square
100+
[build-badge]: https://img.shields.io/travis/akim-mcmath/deep-map-keys/master.svg?style=flat-square
101+
[coverage-badge]: https://img.shields.io/coveralls/akim-mcmath/deep-map-keys/master.svg?style=flat-square&service=github
102+
[dependencies-badge]: https://img.shields.io/gemnasium/akim-mcmath/deep-map-keys.svg?style=flat-square
103+
[npm]: https://www.npmjs.com/package/deep-map-keys
104+
[license]: LICENSE
105+
[travis]: https://travis-ci.org/akim-mcmath/deep-map-keys
106+
[coveralls]: https://coveralls.io/github/akim-mcmath/deep-map-keys?branch=master
107+
[gemnasium]: https://gemnasium.com/akim-mcmath/deep-map-keys
108+
[snake-case]: https://en.wikipedia.org/wiki/Snake_case
109+
[camel-case]: https://en.wikipedia.org/wiki/CamelCase

lib/index.js

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
'use strict';
2+
3+
var isArray = Array.isArray;
4+
5+
function deepMapKeys(obj, fn, opts) {
6+
opts = opts || {};
7+
8+
if (!fn) {
9+
err(Error, 'transformFn must be defined');
10+
} else if (!isFunction(fn)) {
11+
err(TypeError, 'transformFn must be a function');
12+
} else if (!isObject(opts)) {
13+
err(TypeError, 'options must be an object or undefined');
14+
}
15+
16+
return map(obj, fn, opts);
17+
}
18+
19+
function map(value, fn, opts) {
20+
return isArray(value) ? mapArray(value, fn, opts) :
21+
isObject(value) ? mapObject(value, fn, opts) :
22+
value;
23+
}
24+
25+
function mapArray(arr, fn, opts) {
26+
var result = [];
27+
var len = arr.length;
28+
29+
for (var i = 0; i < len; i++) {
30+
result.push(map(arr[i], fn, opts));
31+
}
32+
33+
return result;
34+
}
35+
36+
function mapObject(obj, fn, opts) {
37+
var result = {};
38+
39+
for (var key in obj) {
40+
result[fn.call(opts.thisArg, key)] = map(obj[key], fn, opts);
41+
}
42+
43+
return result;
44+
}
45+
46+
function isFunction(value) {
47+
return typeof value === 'function';
48+
}
49+
50+
function isObject(value) {
51+
return typeof value === 'object' || isFunction(value);
52+
}
53+
54+
function err(ctor, msg) {
55+
var e = new ctor(msg);
56+
Error.captureStackTrace(e, deepMapKeys);
57+
throw e;
58+
}
59+
60+
module.exports = deepMapKeys;

package.json

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
{
2+
"name": "deep-map-keys",
3+
"version": "1.0.0",
4+
"description": "Transforms keys of a JSON-like object",
5+
"main": "lib/index.js",
6+
"scripts": {
7+
"test:lint": "eslint lib",
8+
"test:unit": "istanbul cover _mocha",
9+
"test": "npm run test:lint && npm run test:unit",
10+
"coveralls": "cat coverage/lcov.info | coveralls"
11+
},
12+
"engines": {
13+
"node": ">=0.10"
14+
},
15+
"keywords": [
16+
"map",
17+
"keys",
18+
"deep",
19+
"recursive",
20+
"nested",
21+
"object",
22+
"array",
23+
"json"
24+
],
25+
"author": "Akim McMath <[email protected]>",
26+
"license": "MIT",
27+
"devDependencies": {
28+
"chai": "^3.5.0",
29+
"coffee-script": "^1.10.0",
30+
"coveralls": "^2.11.9",
31+
"eslint": "^2.12.0",
32+
"istanbul": "^0.4.3",
33+
"mocha": "^2.5.3",
34+
"sinon": "^1.17.4",
35+
"sinon-chai": "^2.8.0"
36+
},
37+
"dependencies": {},
38+
"repository": {
39+
"type": "git",
40+
"url": "git+https://github.com/akim-mcmath/deep-map-keys.git"
41+
},
42+
"bugs": {
43+
"url": "https://github.com/akim-mcmath/deep-map-keys/issues"
44+
},
45+
"homepage": "https://github.com/akim-mcmath/deep-map-keys#readme"
46+
}

test/index.coffee

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
chai = require 'chai'
2+
sinonChai = require 'sinon-chai'
3+
sinon = require 'sinon'
4+
deepMapKeys = require '../lib'
5+
6+
before ->
7+
chai.use sinonChai
8+
chai.should()
9+
10+
err = (fn, ctor) ->
11+
fn.should.throw ctor
12+
13+
describe 'deepMapKeys(object, transformFn, [options])', ->
14+
snakeToCamel = null
15+
camelMap = null
16+
17+
beforeEach ->
18+
snakeToCamel = sinon.spy (str) ->
19+
str.replace /_(\w)/g, (match, char) ->
20+
char.toUpperCase()
21+
22+
camelMap = (object, options) ->
23+
deepMapKeys object, snakeToCamel, options
24+
25+
it 'is the main module', ->
26+
deepMapKeys.should.equal require('..')
27+
28+
it 'is a function', ->
29+
deepMapKeys.should.be.a 'function'
30+
31+
context 'simple object is passed', ->
32+
33+
it 'transforms keys', ->
34+
camelMap
35+
user_id: 42
36+
user_name: 'Mufasa'
37+
.should.deep.equal
38+
userId: 42
39+
userName: 'Mufasa'
40+
41+
context 'nested object is passed', ->
42+
43+
it 'transforms keys', ->
44+
camelMap
45+
user_info:
46+
user_id: 42
47+
user_name: 'Mufasa'
48+
.should.deep.equal
49+
userInfo:
50+
userId: 42
51+
userName: 'Mufasa'
52+
53+
context 'complex object is passed', ->
54+
55+
it 'transforms keys', ->
56+
camelMap
57+
user_id: 42
58+
user_name: 'Mufasa'
59+
viewed_by: [
60+
user_id: 100, user_name: 'Rafiki'
61+
,
62+
user_id: 101, user_name: 'Zazu'
63+
]
64+
.should.deep.equal
65+
userId: 42
66+
userName: 'Mufasa'
67+
viewedBy: [
68+
userId: 100, userName: 'Rafiki'
69+
,
70+
userId: 101, userName: 'Zazu'
71+
]
72+
73+
context 'complex array is passed', ->
74+
75+
it 'transforms keys', ->
76+
camelMap [
77+
user_id: 100, user_name: 'Rafiki'
78+
,
79+
user_id: 101, user_name: 'Zazu'
80+
]
81+
.should.deep.equal [
82+
userId: 100, userName: 'Rafiki'
83+
,
84+
userId: 101, userName: 'Zazu'
85+
]
86+
87+
context '@options.thisArg is set', ->
88+
89+
it 'sets context in @transformFn', ->
90+
camelMap({user_id: 41}, {thisArg: 42})
91+
snakeToCamel.should.have.been.calledOn 42
92+
93+
context 'non-object is passed as @options', ->
94+
95+
it 'throws TypeError', ->
96+
err((-> camelMap({user_id: 42}, 42)), TypeError)
97+
98+
context 'non-function is passed as @transformFn', ->
99+
100+
it 'throws TypeError', ->
101+
err((-> deepMapKeys({user_id: 42}, 42)), TypeError)
102+
103+
context 'undefined @transformFn', ->
104+
105+
it 'throws Error', ->
106+
err((-> deepMapKeys({user_id: 42})), Error)
107+
108+
describe 'return value', ->
109+
110+
it 'is a new object', ->
111+
obj = user_id: 42
112+
camelMap(obj).should.not.equal obj
113+
obj.should.deep.equal user_id: 42

0 commit comments

Comments
 (0)