├── .travis.yml ├── .gitignore ├── test ├── perf │ ├── index-test.js │ ├── gen.js │ └── perf-test.js ├── trans │ ├── index-test.js │ ├── basic-test.js │ ├── default-test.js │ ├── flatten-test.js │ ├── array-test.js │ ├── filter-test.js │ ├── pick-test.js │ ├── list-test.js │ ├── remove-test.js │ ├── object-test.js │ ├── sort-test.js │ ├── omit-test.js │ ├── group-test.js │ └── map-test.js └── util.js ├── lib ├── context.js ├── node.js ├── osort.js ├── invoker.js ├── opath.js ├── util.js ├── trav.js ├── ocopy.js └── index.js ├── package.json ├── CHANGELOG.md ├── LICENSE ├── Makefile └── README.md /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | cache: 4 | directories: 5 | - node_modules 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.csv 2 | *.dat 3 | *.gz 4 | *.log 5 | *.out 6 | *.pid 7 | *.rdb 8 | *.seed 9 | 10 | .DS_Store 11 | .stat-test 12 | 13 | NERD_tree_* 14 | lib-cov 15 | logs 16 | node_modules 17 | npm-debug.log 18 | pids 19 | results 20 | tmp 21 | -------------------------------------------------------------------------------- /test/perf/index-test.js: -------------------------------------------------------------------------------- 1 | var util = require('../util') 2 | , path = require('path'); 3 | 4 | describe('trans performance', function () { 5 | [ 'perf-test' ].forEach(function (name) { 6 | require(path.join(process.cwd(), 'test', 'perf', name))(util); 7 | }); 8 | }); 9 | -------------------------------------------------------------------------------- /test/trans/index-test.js: -------------------------------------------------------------------------------- 1 | var util = require('../util') 2 | , path = require('path'); 3 | 4 | describe('trans', function () { 5 | [ 6 | 'basic-test' 7 | , 'map-test' 8 | , 'group-test' 9 | , 'flatten-test' 10 | , 'sort-test' 11 | , 'pick-test' 12 | , 'omit-test' 13 | , 'remove-test' 14 | , 'list-test' 15 | , 'object-test' 16 | , 'array-test' 17 | , 'default-test' 18 | , 'filter-test' 19 | ].forEach(function (name) { 20 | require(path.join(process.cwd(), 'test', 'trans', name))(util); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /lib/context.js: -------------------------------------------------------------------------------- 1 | 2 | function Context (data) { 3 | data = data || {}; 4 | this.indexes = data.indexes || []; 5 | this.index = data.index || 0; 6 | } 7 | 8 | exports.Context = Context; 9 | 10 | Context.prototype.pushIndex = function() { 11 | this.indexes.unshift(this.index); 12 | }; 13 | 14 | Context.prototype.popIndex = function() { 15 | this.index = this.indexes.shift(); 16 | }; 17 | 18 | Context.prototype.setIndex = function(index) { 19 | this.index = index; 20 | }; 21 | 22 | Context.prototype.getIndex = function() { 23 | return this.index; 24 | }; 25 | 26 | Context.prototype.getIndexes = function() { 27 | return [this.index].concat(this.indexes.slice(0, this.indexes.length - 1)); 28 | }; 29 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": { 3 | "email": "gabesoft@gmail.com", 4 | "name": "Gabriel Adomnicai" 5 | }, 6 | "bin": {}, 7 | "dependencies": {}, 8 | "description": "The ultimate object transformer", 9 | "devDependencies": { 10 | "Faker": "^0.7.2", 11 | "eyes": "^0.1.8", 12 | "mocha": "^2.3.4", 13 | "mocha-better-spec-reporter": "^3.0.1", 14 | "range.js": "^1.1.0" 15 | }, 16 | "license": "MIT", 17 | "main": "./lib/index.js", 18 | "name": "trans", 19 | "preferGlobal": false, 20 | "repository": { 21 | "type": "git", 22 | "url": "git@github.com:gabesoft/trans.git" 23 | }, 24 | "scripts": { 25 | "test": "node_modules/.bin/mocha -u tdd --check-leaks -R spec test/trans/index-test.js" 26 | }, 27 | "version": "0.1.2" 28 | } 29 | -------------------------------------------------------------------------------- /test/util.js: -------------------------------------------------------------------------------- 1 | exports.trans = require('../lib'); 2 | exports.eyes = require('eyes'); 3 | exports.assert = require('assert'); 4 | exports.truncate = function (x, len) { return x.substring(0, len || 3); }; 5 | exports.inspect = function (obj) { exports.eyes.inspect(obj); }; 6 | exports.sum = function (xs) { return xs.reduce(function (acc, x) { return x + acc; }, 0); }; 7 | exports.mod = function (x, y) { return x % y; }; 8 | exports.add = function (x, y) { return x + y; }; 9 | exports.gt = function (x, y) { return x > y; }; 10 | exports.lt = function (x, y) { return x < y; }; 11 | exports.square = function (x) { return x * x; }; 12 | exports.stringify = function (obj) { 13 | return JSON.stringify(obj, function (key, val) { 14 | return (typeof val === 'function') ? val + '' : val; 15 | }); 16 | }; 17 | 18 | exports.eyes.defaults.maxLength = 8192; 19 | -------------------------------------------------------------------------------- /test/trans/basic-test.js: -------------------------------------------------------------------------------- 1 | module.exports = function (util) { 2 | var trans = util.trans 3 | , assert = util.assert 4 | , square = util.square 5 | , mod = util.mod 6 | , sum = util.sum 7 | , add = util.add; 8 | 9 | describe('value', function () { 10 | it('should return the transformation state', function () { 11 | var o = { a: 1 }; 12 | assert.strictEqual(trans(o).value(), o); 13 | }); 14 | }); 15 | 16 | describe('get', function () { 17 | it('should get the transformation state', function () { 18 | var o = { a: 1 }; 19 | trans(o).get(function (e) { assert.strictEqual(e, o); }); 20 | }); 21 | 22 | it('should allow further chaining', function () { 23 | var o = { a: 1 } 24 | , e = trans(o).get().value(); 25 | assert.strictEqual(e, o); 26 | }); 27 | }); 28 | }; 29 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 0.1.2 2 | ----- 3 | Clone date objects properly 4 | 5 | 0.1.1 6 | ----- 7 | Fixed tests 8 | 9 | 0.1.0 10 | ----- 11 | Added count method 12 | 13 | 0.0.16 14 | ----- 15 | Better error messages 16 | 17 | 0.0.15 18 | ----- 19 | Added uniq method to filter duplicates 20 | 21 | 0.0.14 22 | ----- 23 | More mapff fixes 24 | 25 | 0.0.13 26 | ----- 27 | Bug fixes in mapff + documentation + unit tests 28 | 29 | 0.0.12 30 | ----- 31 | Documentation fixes 5 32 | 33 | 0.0.11 34 | ----- 35 | Documentation fixes 4 36 | 37 | 0.0.10 38 | ----- 39 | Documentation fixes 3 40 | 41 | 0.0.9 42 | ----- 43 | Documentation fixes 3 44 | 45 | 0.0.8 46 | ----- 47 | Documentation fixes 2 48 | 49 | 0.0.7 50 | ----- 51 | Documentation fixes 52 | 53 | 0.0.6 54 | ----- 55 | Documentation + minor fixes 56 | 57 | 0.0.4 58 | ----- 59 | Load eyes module on demand 60 | 61 | 0.0.3 62 | ----- 63 | Added filter + unit tests + fixes 64 | 65 | 0.0.2 66 | ----- 67 | Added convenience list methods 68 | 69 | -------------------------------------------------------------------------------- /lib/node.js: -------------------------------------------------------------------------------- 1 | var util = require('./util') 2 | , ensure = util.ensure 3 | , fail = util.fail 4 | , isUndefined = util.isUndefined 5 | , isObject = util.isObject 6 | , isString = util.isString; 7 | 8 | function addField (obj, field, value) { 9 | ensure(field, 'No field specified', obj); 10 | ensure(isObject(obj), 'Could not create field ' + field + ' on', obj); 11 | obj[field] = value; 12 | } 13 | 14 | function Node (parent, field) { 15 | this.parent = parent; 16 | this.field = field; 17 | } 18 | 19 | exports.Node = Node; 20 | 21 | Node.prototype.getValue = function() { 22 | var val = this.parent[this.field]; 23 | return isUndefined(val) ? null : val; 24 | }; 25 | 26 | Node.prototype.setValue = function (value) { 27 | addField(this.parent, this.field, value); 28 | }; 29 | 30 | Node.prototype.getValueOrParent = function() { 31 | var val = this.field ? this.parent[this.field] : this.parent; 32 | return isUndefined(val) ? null : val; 33 | }; 34 | -------------------------------------------------------------------------------- /lib/osort.js: -------------------------------------------------------------------------------- 1 | 2 | exports.createComparer = function(fn, descending) { 3 | if (fn) { 4 | return function (x, y) { 5 | var val = fn(x.criteria, y.criteria); 6 | return val === 0 ? x.index - y.index : val; 7 | }; 8 | } else if (descending) { 9 | return function (x, y) { 10 | var cx = x.criteria 11 | , cy = y.criteria; 12 | 13 | if (cx !== cy) { 14 | if (cx > cy || cx === void 0) { return -1; } 15 | if (cx < cy || cy === void 0) { return 1; } 16 | } 17 | 18 | return x.index - y.index; 19 | }; 20 | } else { 21 | return function (x, y) { 22 | var cx = x.criteria 23 | , cy = y.criteria; 24 | 25 | if (cx !== cy) { 26 | if (cx > cy || cx === void 0) { return 1; } 27 | if (cx < cy || cy === void 0) { return -1; } 28 | } 29 | 30 | return x.index - y.index; 31 | }; 32 | } 33 | }; 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This software is released under the MIT license: 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 of 7 | the Software, and to permit persons to whom the Software is furnished to do so, 8 | 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, FITNESS 15 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | default: test 2 | 3 | MODULES = ./node_modules/.bin 4 | MOCHA = $(MODULES)/mocha -u tdd --check-leaks -R mocha-better-spec-reporter 5 | VERSION = $(shell node -pe 'require("./package.json").version') 6 | 7 | all: jshint test 8 | 9 | .PHONY: release test loc clean no_targets__ help 10 | 11 | no_targets__: 12 | help: 13 | @sh -c "$(MAKE) -rpn no_targets__ | awk -F':' '/^[a-zA-Z0-9][^\$$#\/\\t=]*:([^=]|$$)/ {split(\$$1,A,/ /);for(i in A)print A[i]}' | grep -v '__\$$' | grep -v 'Makefile' | grep -v 'make\[1\]' | sort" 14 | 15 | tag: 16 | @git tag -a "v$(VERSION)" -m "Version $(VERSION)" 17 | 18 | tag-push: tag 19 | @git push --tags origin HEAD:master 20 | 21 | test: 22 | @NODE_ENV=test $(MOCHA) test/trans/index-test.js 23 | 24 | test-slow: 25 | @NODE_ENV=test $(MOCHA) test/perf/index-test.js --timeout 10000 26 | 27 | test-all: 28 | @NODE_ENV=test $(MOCHA) test/trans/index-test.js test/perf/index-test.js --timeout 10000 29 | 30 | jshint: 31 | jshint lib/** 32 | jshint test/** 33 | 34 | loc: 35 | @find src/ -name *.js | xargs wc -l 36 | 37 | setup: 38 | @npm install . -d 39 | 40 | clean-dep: 41 | @rm -rf node_modules 42 | 43 | -------------------------------------------------------------------------------- /lib/invoker.js: -------------------------------------------------------------------------------- 1 | var util = require('./util') 2 | , ensure = util.ensure 3 | , fail = util.fail 4 | , isArray = util.isArray 5 | , isFunction = util.isFunction 6 | , isUndefined = util.isUndefined 7 | , isObject = util.isObject 8 | , isString = util.isString 9 | , toArray = util.toArray; 10 | 11 | function Invoker (context) { 12 | this.context = context; 13 | } 14 | 15 | exports.Invoker = Invoker; 16 | 17 | Invoker.prototype.run = function() { 18 | var args = toArray(arguments) 19 | , context = this.context 20 | , target = args[0] 21 | , fn = args[1] 22 | , rest = args.slice(2); 23 | 24 | if (isFunction(fn)) { 25 | return fn.apply(context, [target].concat(rest)); 26 | } 27 | if (isArray(fn)) { 28 | return this.run.apply(this, [target].concat(fn)); 29 | } 30 | if (isObject(fn)) { 31 | return fn[target]; 32 | } 33 | if (!isString(fn)) { 34 | fail('Could not apply transformer', fn || null); 35 | } 36 | 37 | fn = target[fn]; 38 | 39 | return isFunction(fn) ? fn.apply(target, rest) : fn; 40 | }; 41 | -------------------------------------------------------------------------------- /test/trans/default-test.js: -------------------------------------------------------------------------------- 1 | module.exports = function (util) { 2 | var trans = util.trans 3 | , assert = util.assert 4 | , add = util.add 5 | , sum = util.sum 6 | , square = util.square; 7 | 8 | describe('default', function () { 9 | it('should set default values for null or undefined values', function () { 10 | var o = { a: { b: null, c: { }, d: { e: { g: 4 } } } } 11 | , t = trans(o).default('a.b', 1, 'a.c.f', 2, 'a.d.e.f', 5).value(); 12 | assert.deepEqual(t, { a: { b: 1, c: { f: 2 }, d: { e: { f: 5, g: 4 } } } }); 13 | }); 14 | 15 | it('should handle arrays', function () { 16 | var o = { a: [ { b: {} }, { b: { c: null } }, { b: {} } ], e: { d: {}, f: 3 } } 17 | , t = trans(o).default('a.b.c', 2, 'e.d.g', 4).value(); 18 | assert.deepEqual(t, { 19 | a: [ { b: { c: 2 } }, { b: { c: 2 } }, { b: { c: 2 } } ] 20 | , e: { d: { g: 4 }, f: 3 } 21 | }); 22 | }); 23 | 24 | it('should throw if there isn\'t an even number of arguments', function () { 25 | assert.throws(function () { 26 | trans({ a: 1 }).default('a', 1, 'b'); 27 | }, /an even number of arguments was expected/i); 28 | }); 29 | 30 | it('should throw if asked to set defaults on a primitive value', function () { 31 | assert.throws(function () { 32 | trans({ a: { b: 1, c: null }, d: 1 }).default('d.e', 3); 33 | }, /could not create field/i); 34 | }); 35 | }); 36 | }; 37 | -------------------------------------------------------------------------------- /lib/opath.js: -------------------------------------------------------------------------------- 1 | var END_DOT = /\.$/ 2 | , DOT = '.' 3 | , COL = ':' 4 | , util = require('./util') 5 | , isArray = util.isArray; 6 | 7 | function Path (name, iter) { 8 | var parts = null; 9 | 10 | this.full = ''; 11 | this.path = []; 12 | this.iter = !!iter; 13 | this.meta = []; 14 | 15 | if (isArray(name)) { 16 | this.full = name.join(DOT); 17 | this.path = name; 18 | } else if (name) { 19 | parts = name.split(COL); 20 | this.full = parts[0].replace(END_DOT, ''); 21 | this.path = this.full.split(DOT).filter(Boolean); 22 | this.iter = END_DOT.test(parts[0]); 23 | this.meta = parts.slice(1); 24 | } 25 | 26 | this.nil = this.path.length === 0; 27 | this.exists = !this.nil; 28 | this._isPath = true; 29 | } 30 | 31 | exports.ITER = DOT; 32 | 33 | function parse (name) { 34 | return (name || {})._isPath ? name : new Path(name); 35 | } 36 | 37 | exports.parse = parse; 38 | 39 | exports.join = function (p1, p2) { 40 | p1 = parse(p1); 41 | p2 = parse(p2); 42 | return new Path(p1.path.concat(p2.path), p2.iter); 43 | }; 44 | 45 | exports.root = function (src, dst) { 46 | src = parse(src); 47 | dst = parse(dst); 48 | 49 | var len = Math.min(src.path.length, dst.path.length) 50 | , i = 0 51 | , r = []; 52 | 53 | for (i = 0; i < len; i++) { 54 | if (src.path[i] === dst.path[i]) { 55 | r.push(src.path[i]); 56 | } else { 57 | break; 58 | } 59 | } 60 | 61 | return { 62 | root : new Path(r) 63 | , src : new Path(src.path.slice(i)) 64 | , dst : new Path(dst.path.slice(i)) 65 | }; 66 | }; 67 | -------------------------------------------------------------------------------- /lib/util.js: -------------------------------------------------------------------------------- 1 | var slice = Array.prototype.slice 2 | , util = require('util') 3 | , isArray = util.isArray 4 | , isFunction = function (x) { return typeof x === 'function'; } 5 | , isUndefined = function (x) { return x === void 0; } 6 | , isObject = function (x) { return Object(x) === x && !isArray(x) && !isFunction(x); } 7 | , isString = function (x) { return typeof x === 'string'; } 8 | , isDate = function (x) { return Object.prototype.toString.call(x) === '[object Date]'; } 9 | , toArray = function (x) { return slice.call(x); }; 10 | 11 | function ensure (cond, msg, obj) { 12 | if (!cond) { 13 | fail(msg, obj); 14 | } 15 | } 16 | 17 | function fail (msg, obj) { 18 | if (!isUndefined(obj)) { 19 | if (obj === null) { 20 | msg += ' null'; 21 | } else if (isFunction(obj)) { 22 | msg += ' ' + obj; 23 | } else { 24 | try { 25 | msg += ' '; 26 | msg += JSON.stringify(obj); 27 | } catch (e) { 28 | msg += ' '; 29 | msg += obj; 30 | } 31 | } 32 | } 33 | throw new Error(msg); 34 | } 35 | 36 | exports.ensure = ensure; 37 | exports.fail = fail; 38 | exports.isArray = util.isArray; 39 | exports.isFunction = isFunction; 40 | exports.isUndefined = isUndefined; 41 | exports.isObject = isObject; 42 | exports.isDate = isDate; 43 | exports.isString = isString; 44 | exports.toArray = toArray; 45 | exports.default = function (obj, val) { return isUndefined(obj) ? val : obj; }; 46 | exports.trueFn = function () { return true; }; 47 | exports.idFn = function (o) { return o; }; 48 | -------------------------------------------------------------------------------- /test/trans/flatten-test.js: -------------------------------------------------------------------------------- 1 | module.exports = function (util) { 2 | var trans = util.trans 3 | , assert = util.assert; 4 | 5 | describe('flatten', function () { 6 | it('should flatten an array - shallow', function () { 7 | var o = [ [ [ 1 ], [ 2 ] ], [ [ 1, 2, 3 ], [ 4 ] ] ] 8 | , t = trans(o).flatten().value(); 9 | assert.deepEqual(t, [ [ 1 ], [ 2 ], [ 1, 2, 3 ], [ 4 ] ]); 10 | }); 11 | 12 | it('should flatten an array - deep', function () { 13 | var o = [ [ [ 1 ], [ 2 ] ], [ [ 1, 2, 3 ], [ 4 ] ] ] 14 | , t = trans(o).flatten(true).value(); 15 | assert.deepEqual(t, [ 1, 2, 1, 2, 3, 4 ]); 16 | }); 17 | 18 | it('should leave the object unchanged if not an array', function () { 19 | var o = { a: 1 } 20 | , t = trans(o).flatten().value(); 21 | assert.strictEqual(t, o); 22 | }); 23 | }); 24 | 25 | describe('flattenf', function () { 26 | it('should flatten the array at the given field', function () { 27 | var o = { a: { b: 1 }, c: { d: [ [ 'a', 'b' ], [ 'c', 'd', 'e' ], [ 'f' ] ] } } 28 | , t = trans(o).flattenf('c.d').value(); 29 | assert.deepEqual(t, { a: { b: 1 }, c: { d: [ 'a', 'b', 'c', 'd', 'e', 'f' ] } }); 30 | }); 31 | }); 32 | 33 | describe('flattenff', function () { 34 | it('should flatten the target value and set it on the destination field', function () { 35 | var o = { a: { b: [ [ 1 ], [ [ 2 ], 3 ], [ [ [ 4 ] ], [ 5 ] ] ] } } 36 | , t = trans(o).flattenff('a.b', 'c', true).value(); 37 | assert.deepEqual(t, { 38 | a: { b: [ [ 1 ], [ [ 2 ], 3 ], [ [ [ 4 ] ], [ 5 ] ] ] } 39 | , c: [ 1, 2, 3, 4, 5 ] 40 | }); 41 | }); 42 | }); 43 | }; 44 | -------------------------------------------------------------------------------- /test/perf/gen.js: -------------------------------------------------------------------------------- 1 | var faker = require('faker') 2 | , data = null 3 | , addrCount = 12 4 | , contactCount = 12 5 | , phoneCount = 12 6 | , emailCount = 12; 7 | 8 | function rand (min, max) { 9 | return min + Math.floor(Math.random() * (max - min + 1)); 10 | } 11 | 12 | function genlist (count, gen) { 13 | var list = [] 14 | , i = 0; 15 | 16 | for (i = 0; i < count; i++) { 17 | list.push(gen()); 18 | } 19 | 20 | return list; 21 | } 22 | 23 | function genAddress () { 24 | var addr = faker.Address; 25 | return { 26 | zip : addr.zipCode() 27 | , city : addr.city() 28 | , street : addr.streetAddress() 29 | , suite : addr.secondaryAddress() 30 | , state : addr.usState() 31 | , geo : { lat: addr.latitude(), lng: addr.longitude() } 32 | }; 33 | } 34 | 35 | function genContact () { 36 | var name = faker.Name 37 | , phone = faker.PhoneNumber; 38 | 39 | return { 40 | first : name.firstName() 41 | , last : name.lastName() 42 | , phones : genlist(phoneCount, phone.phoneNumber) 43 | , addresses : genlist(addrCount, genAddress) 44 | , emails : genlist(emailCount, function () { return faker.Internet.email(); }) 45 | }; 46 | } 47 | 48 | function genCompany () { 49 | var co = faker.Company; 50 | return { 51 | name : co.companyName() 52 | , website : faker.Internet.domainName() 53 | , phrase : co.catchPhrase() 54 | , tagLine : co.bs() 55 | , mission : faker.Lorem.paragraph() 56 | , locations : genlist(addrCount, genAddress) 57 | , contacts : genlist(contactCount, genContact) 58 | }; 59 | } 60 | 61 | exports.createCompanies = function (count) { 62 | return genlist(count, genCompany); 63 | }; 64 | -------------------------------------------------------------------------------- /lib/trav.js: -------------------------------------------------------------------------------- 1 | var util = require('./util') 2 | , opath = require('./opath') 3 | , Invoker = require('./invoker').Invoker 4 | , Node = require('./node').Node 5 | , ensure = util.ensure 6 | , fail = util.fail 7 | , isArray = util.isArray 8 | , isFunction = util.isFunction 9 | , isUndefined = util.isUndefined 10 | , isObject = util.isObject 11 | , isString = util.isString 12 | , toArray = util.toArray 13 | , trueFn = util.trueFn 14 | , idFn = util.idFn; 15 | 16 | function addField (obj, field, value) { 17 | ensure(field, 'No field specified', obj); 18 | ensure(isObject(obj), 'Could not create field ' + field + ' on', obj); 19 | obj[field] = value; 20 | } 21 | 22 | function Trav (context) { 23 | this.context = context; 24 | this.invoker = new Invoker(context); 25 | } 26 | 27 | exports.Trav = Trav; 28 | 29 | Trav.prototype.loop = function(obj, fnAdd, fnMap) { 30 | var context = this.context 31 | , objects = Object(obj) 32 | , self = this 33 | , result = [] 34 | , i = 0 35 | , len = obj.length >>> 0; 36 | 37 | ensure(isArray(obj), 'Array expected but got', obj || null); 38 | 39 | context.pushIndex(); 40 | for (i = 0; i < len; i++) { 41 | if (i in objects) { 42 | context.setIndex(i); 43 | 44 | if (fnAdd(objects[i], i)) { 45 | result.push(fnMap(objects[i], i)); 46 | } 47 | } 48 | } 49 | context.popIndex(); 50 | 51 | return result; 52 | }; 53 | 54 | Trav.prototype.map = function(obj, fn) { 55 | return this.loop(obj, trueFn, fn); 56 | }; 57 | 58 | Trav.prototype.filter = function(obj, fn) { 59 | return this.loop(obj, fn, idFn); 60 | }; 61 | 62 | Trav.prototype.walkValue = function(obj, fields, create) { 63 | return this.walk(obj, fields, create, function (node) { 64 | return node.getValue(); 65 | }); 66 | }; 67 | 68 | Trav.prototype.walk = function(obj, fields, create, fn) { 69 | if (!obj) { return null; } 70 | 71 | var self = this; 72 | 73 | if (isArray(obj)) { 74 | return self.map(obj, function (o) { return self.walk(o, fields, create, fn); }); 75 | } 76 | if (!fields || fields.length === 0) { 77 | return fn(new Node(obj, null)); 78 | } 79 | if (fields.length === 1) { 80 | return fn(new Node(obj, fields[0])); 81 | } 82 | 83 | if (create && !obj.hasOwnProperty(fields[0])) { 84 | addField(obj, fields[0], {}); 85 | } 86 | 87 | if (obj.hasOwnProperty(fields[0])) { 88 | return self.walk(obj[fields[0]], fields.slice(1), create, fn); 89 | } 90 | 91 | return null; 92 | }; 93 | 94 | Trav.prototype.transform = function(obj, funs) { 95 | if (!funs || funs.length === 0) { return obj; } 96 | 97 | var next = funs[0] 98 | , rest = funs.slice(1) 99 | , self = this 100 | , invoker = this.invoker 101 | , result = null; 102 | 103 | if (next === opath.ITER) { 104 | result = self.map(obj, function (o) { 105 | return self.transform(o, rest); 106 | }); 107 | } else { 108 | obj = invoker.run(obj, next); 109 | result = self.transform(obj, rest); 110 | } 111 | 112 | return result; 113 | }; 114 | -------------------------------------------------------------------------------- /lib/ocopy.js: -------------------------------------------------------------------------------- 1 | var util = require('./util') 2 | , opath = require('./opath') 3 | , ensure = util.ensure 4 | , fail = util.fail 5 | , isArray = util.isArray 6 | , isFunction = util.isFunction 7 | , isDate = util.isDate 8 | , isUndefined = util.isUndefined 9 | , isObject = util.isObject 10 | , isString = util.isString 11 | , toArray = util.toArray; 12 | 13 | function fieldTree (fields) { 14 | var root = {}; 15 | 16 | fields.forEach(function (field) { 17 | var r = root 18 | , p = opath.parse(field); 19 | p.path.forEach(function (part) { 20 | if (!r[part]) { 21 | r[part] = { _end_: true }; 22 | } 23 | if (r._end_) { 24 | r._end_ = false; 25 | } 26 | r = r[part]; 27 | }); 28 | }); 29 | 30 | return root; 31 | } 32 | 33 | function removeFields (obj, removelist) { 34 | var keys = Object.keys(removelist) 35 | , i = 0 36 | , key = null 37 | , len = keys.length; 38 | 39 | for (i = 0; i < len; i++) { 40 | key = keys[i]; 41 | 42 | if (!(key in obj)) { 43 | continue; 44 | } 45 | 46 | if (removelist[key]._end_) { 47 | delete obj[key]; 48 | } else if (removelist[key]) { 49 | copyObject(removeFields, obj[key], removelist[key]); 50 | } 51 | } 52 | } 53 | 54 | function copyBlack (obj, blacklist, parents) { 55 | blacklist = blacklist || {}; 56 | 57 | var clone = {} 58 | , keys = Object.keys(obj) 59 | , i = 0 60 | , key = null 61 | , end = null 62 | , len = keys.length; 63 | 64 | parents.push(obj); 65 | for (i = 0; i < len; i++) { 66 | key = keys[i]; 67 | end = blacklist[key] && blacklist[key]._end_; 68 | 69 | if (end || parents.indexOf(obj[key]) !== -1) { 70 | continue; 71 | } 72 | 73 | clone[key] = copyObject(copyBlack, obj[key], blacklist[key], parents); 74 | } 75 | parents.pop(obj); 76 | 77 | return clone; 78 | } 79 | 80 | function copyWhite (obj, whitelist) { 81 | var keys = Object.keys(whitelist) 82 | , clone = {} 83 | , i = 0 84 | , key = null 85 | , len = keys.length; 86 | 87 | for (i = 0; i < len; i++) { 88 | key = keys[i]; 89 | 90 | if(!(key in obj)) { 91 | continue; 92 | } 93 | 94 | clone[key] = copyObject(copyWhite, obj[key], whitelist[key]); 95 | } 96 | 97 | return clone; 98 | } 99 | 100 | function copyObject (copyFn, obj, fields, parents) { 101 | if (isArray(obj)) { 102 | return obj.map(function (o) { return copyObject(copyFn, o, fields, parents); }); 103 | } else if (isDate(obj)) { 104 | return new Date(obj.getTime()); 105 | } else if (isObject(obj)) { 106 | return copyFn(obj, fields, parents); 107 | } else { 108 | return obj; 109 | } 110 | } 111 | 112 | exports.copyWhite = function (obj, fields) { 113 | return copyObject(copyWhite, obj, fieldTree(fields)); 114 | }; 115 | 116 | exports.copyBlack = function (obj, fields) { 117 | return copyObject(copyBlack, obj, fieldTree(fields), []); 118 | }; 119 | 120 | exports.removeFields = function (obj, fields) { 121 | return copyObject(removeFields, obj, fieldTree(fields)); 122 | }; 123 | -------------------------------------------------------------------------------- /test/trans/array-test.js: -------------------------------------------------------------------------------- 1 | module.exports = function (util) { 2 | var trans = util.trans 3 | , assert = util.assert 4 | , add = util.add 5 | , sum = util.sum 6 | , square = util.square; 7 | 8 | describe('array', function () { 9 | it('should turn an object into an array', function () { 10 | var o = { a: 1, b: 2, c: 3 } 11 | , t = trans(o).array().value(); 12 | assert.deepEqual(t, [ 13 | { key: 'a', value: 1 } 14 | , { key: 'b', value: 2 } 15 | , { key: 'c', value: 3 } 16 | ]); 17 | }); 18 | 19 | it('should be able to specify the key and value names', function () { 20 | var o = { a: { b: 'A' }, c: { b: 'C' }, d: { b: 'D' } } 21 | , t = trans(o).array('letter', 'obj').value(); 22 | assert.deepEqual(t, [ 23 | { letter: 'a', obj: { b: 'A' } } 24 | , { letter: 'c', obj: { b: 'C' } } 25 | , { letter: 'd', obj: { b: 'D' } } 26 | ]); 27 | }); 28 | 29 | it('should be able to get only keys', function () { 30 | var o = { a: 1, b: 2, c: 3 } 31 | , t = trans(o).array().map('.', 'key').value(); 32 | assert.deepEqual(t, [ 'a', 'b', 'c' ]); 33 | }); 34 | 35 | it('should be able to get only values', function () { 36 | var o = { a: 1, b: 2, c: 3 } 37 | , t = trans(o).array().map('.', 'value').value(); 38 | assert.deepEqual(t, [ 1, 2, 3 ]); 39 | }); 40 | 41 | it('should arrayify every object in an array', function () { 42 | var o = [ { a: 1, b: 2 }, { a: 3, b: 4, c: 5 }, { d: 6 } ] 43 | , t = trans(o).array().value(); 44 | assert.deepEqual(t, [ 45 | [ { key: 'a', value: 1 }, { key: 'b', value: 2 } ] 46 | , [ { key: 'a', value: 3 }, { key: 'b', value: 4 }, { key: 'c', value: 5 } ] 47 | , [ { key: 'd', value: 6 } ] 48 | ]); 49 | }); 50 | 51 | it('should work with nested arrays', function () { 52 | var o = [ [ { a: 1, c: 3 }, { a: 1 } ], [ { a: 1, b: 2 }, { c: 3 } ] ] 53 | , t = trans(o).array('k', 'v').value(); 54 | assert.deepEqual(t, [ 55 | [ [ { k: 'a', v: 1 }, { k: 'c', v: 3 } ], [ { k: 'a', v: 1 } ] ] 56 | , [ [ { k: 'a', v: 1 }, { k: 'b', v: 2 } ], [ { k: 'c', v: 3 } ] ] 57 | ]); 58 | }); 59 | 60 | it('should be reversible with object', function () { 61 | var o = { a: 1, b: 2, c: 3 } 62 | , t = trans(o).array().object('key', 'value').value(); 63 | assert.deepEqual(t, o); 64 | }); 65 | 66 | it('should throw if the target is not an object 1', function () { 67 | assert.throws(function () { 68 | trans('bad').array(); 69 | }, /object expected/i); 70 | }); 71 | 72 | it('should throw if the target is not an object 2', function () { 73 | assert.throws(function () { 74 | trans([ 'abc', 'def' ]).array(); 75 | }, /object expected/i); 76 | }); 77 | }); 78 | 79 | describe('arrayf', function () { 80 | it('should arrayify the object at the given field', function () { 81 | var o = { a: { d: { b: 1, c: 2 } } } 82 | , t = trans(o).arrayf('a.d', 'k', 'v').value(); 83 | assert.deepEqual(t, { 84 | a: { d: [ { k: 'b', v: 1 }, { k: 'c', v: 2 } ] } 85 | }); 86 | }); 87 | }); 88 | 89 | describe('arrayff', function () { 90 | it('should arrayify the source object and set it on the destination', function () { 91 | var o = { a: { b: { c: 1, d: 2 } }, c: 'ready' } 92 | , t = trans(o).arrayff('a.b', 'c', 'k', 'v').value(); 93 | assert.deepEqual(t, { 94 | a: { b: { c: 1, d: 2 } }, c: [ { k: 'c', v: 1 }, { k: 'd', v: 2 } ] 95 | }); 96 | }); 97 | }); 98 | }; 99 | -------------------------------------------------------------------------------- /test/trans/filter-test.js: -------------------------------------------------------------------------------- 1 | module.exports = function (util) { 2 | var trans = util.trans 3 | , assert = util.assert 4 | , square = util.square 5 | , gt = util.gt 6 | , lt = util.lt 7 | , mod = util.mod 8 | , sum = util.sum 9 | , add = util.add; 10 | 11 | describe('filter', function () { 12 | it('should filter by the specified field', function () { 13 | var o = [ { a: { b: 1 } }, { a: { b: 0 } }, { a: { b: 3 } } ] 14 | , t = trans(o).filter('a.b').value(); 15 | assert.deepEqual(t, [ { a: { b: 1 } }, { a: { b: 3 } } ]); 16 | }); 17 | 18 | it('should filter by the specified field inverted', function () { 19 | var o = [ { a: { b: 1 } }, { a: { b: 0 } }, { a: { b: 3 } } ] 20 | , t = trans(o).filter('a.b:invert').value(); 21 | assert.deepEqual(t, [ { a: { b: 0 } }]); 22 | }); 23 | 24 | it('should filter by the specified field and predicate', function () { 25 | var o = [ { a: [ 1, 2 ] }, { a: [ 3, 6 ] }, { a: [ 1, 4 ] }, { a: [ 1, 5 ] } ] 26 | , t = trans(o).filter('a', sum, [mod, 3], Boolean).value(); 27 | assert.deepEqual(t, [ { a: [ 1, 4 ] } ]); }); 28 | 29 | it('should handle nested arrays 1', function () { 30 | var o = [ 31 | { a: { b: [ { c: 1 }, { c: 2 }, { c: 3 } ] } } 32 | , { a: { b: [ { c: 5 }, { c: 10 }, { c: 15 } ] } } 33 | , { a: { b: [ { c: 100 }, { c: 200 }, { c: 300 } ] } } 34 | , { a: { b: [ { c: 7 }, { c: 13 }, { c: 19 } ] } } ] 35 | , t = trans(o).filter('a.b.c', sum, [gt, 35]).value(); 36 | assert.deepEqual(t, [ 37 | { a: { b: [ { c: 100 }, { c: 200 }, { c: 300 } ] } } 38 | , { a: { b: [ { c: 7 }, { c: 13 }, { c: 19 } ] } } ]); 39 | }); 40 | 41 | it('should handle nested arrays 2', function () { 42 | var o = [ 43 | [ { a: [ { b: 'a' }, { b: 'c' } ] }, { a: [ { b: 'b' } ] }, { a: [ { b: 'c1' }, { b: 'c2' }, { b: 'c3' } ] } ] 44 | , [ { a: [ { b: 'u' } ] }, { a: [ { b: 's' }, { b: 'q' } ] }, { a: [ { b: 't' } ] } ] 45 | , [ { a: [ { b: 'p' } ] }, { a: [ { b: 'q' } ] }, { a: [ { b: 'r' } ] } ] 46 | , [ { a: [ { b: 'x' } ] }, { a: [ { b: 'y' }, { b: 'yy' }, { b: 'yyy' } ] }, { a: [ { b: 'z' } ] } ] 47 | ] 48 | , t = trans(o).filter('a.b', trans 49 | , 'flatten', 'value', ['join', ''], ['match', /cbc1|yyyz/]).value(); 50 | assert.deepEqual(t, [ 51 | [ { a: [ { b: 'a' }, { b: 'c' } ] }, { a: [ { b: 'b' } ] }, { a: [ { b: 'c1' }, { b: 'c2' }, { b: 'c3' } ] } ] 52 | , [ { a: [ { b: 'x' } ] }, { a: [ { b: 'y' }, { b: 'yy' }, { b: 'yyy' } ] }, { a: [ { b: 'z' } ] } ] 53 | ]); 54 | }); 55 | 56 | it('should handle nested arrays 3', function () { 57 | var o = [ 58 | [ { a: [ { b: [ 1 ] }, { b: [ 2, 3 ] }, ] }, { a: [ { b: [ 5 ] } ] } ] 59 | , [ { a: [ { b: [ 10, 11, 23, 35 ] }, { b: [ 25, 3 ] }, ] }, { a: [ { b: [ 13 ] } ] } ] 60 | , [ { a: [ { b: [ 36 ] }, { b: [ 2, 3 ] }, ] }, { a: [ { b: [ 18, 19, 20 ] } ] } ] 61 | , [ { a: [ { b: [ 100, 99, 98 ] }, { b: [ 2, 3 ] }, ] }, { a: [ { b: [ 28, 19, 20 ] } ] } ] 62 | ] 63 | , t = trans(o).filter('a.b', trans, ['flatten', true], 'value', sum, [lt, 100]).value(); 64 | assert.deepEqual(t, [ 65 | [ { a: [ { b: [ 1 ] }, { b: [ 2, 3 ] }, ] }, { a: [ { b: [ 5 ] } ] } ] 66 | , [ { a: [ { b: [ 36 ] }, { b: [ 2, 3 ] }, ] }, { a: [ { b: [ 18, 19, 20 ] } ] } ] 67 | ]); 68 | }); 69 | 70 | it('should make the array index available to the predicate', function () { 71 | var o = [ 1, 2, 3, 4, 5, 6, 7, 7 ] 72 | , t = trans(o).filter(null, function (x) { return this.index % 2 === 1; }).value(); 73 | assert.deepEqual(t, [ 2, 4, 6, 7 ]); 74 | }); 75 | 76 | it('should throw if the filter target is not an array', function () { 77 | assert.throws(function() { 78 | trans({ a: 1 }).filter('a'); 79 | }, /the filter target is not an array/i); 80 | }); 81 | }); 82 | 83 | describe('filterf', function () { 84 | it('should filter the array at the given field', function () { 85 | var o = { a: [ 1, 2, 1, 1, 4, 5 ] } 86 | , t = trans(o).filterf('a', null, [mod, 2]).value(); 87 | assert.deepEqual(t, { a: [ 1, 1, 1, 5 ] }); 88 | }); 89 | }); 90 | 91 | describe('filterff', function () { 92 | it('should filter the array at the source field and set it on the destination', function () { 93 | var o = { a: [ 1, 2, 1, 1, 4, 5 ] } 94 | , t = trans(o).filterff('a', 'b', null, [mod, 2]).value(); 95 | assert.deepEqual(t, { a: [ 1, 2, 1, 1, 4, 5 ], b: [ 1, 1, 1, 5 ] }); 96 | }); 97 | }); 98 | }; 99 | -------------------------------------------------------------------------------- /test/perf/perf-test.js: -------------------------------------------------------------------------------- 1 | module.exports = function (util) { 2 | var trans = util.trans 3 | , faker = require('faker') 4 | , gen = require('./gen') 5 | , assert = util.assert 6 | , square = util.square 7 | , mod = util.mod 8 | , sum = util.sum 9 | , add = util.add 10 | , data = null; 11 | 12 | function inspectGroup (group, full) { 13 | if (full) { 14 | trans(group) 15 | .map('.', function (g) { return { key: g.key, count: g.value.length }; }) 16 | .inspect(); 17 | } 18 | util.inspect(group.length); 19 | } 20 | 21 | beforeEach(function () { 22 | data = gen.createCompanies(500); 23 | }); 24 | 25 | describe('group', function () { 26 | it('should group companies by contact state', function () { 27 | var t = trans(data) 28 | .group('locations.state.') 29 | .value(); 30 | inspectGroup(t); 31 | }); 32 | 33 | it('should group companies by contacts emails', function () { 34 | var t = trans(data) 35 | .mapff('contacts.emails.', 'emails') 36 | .flattenf('emails') 37 | .group('emails.') 38 | .value(); 39 | inspectGroup(t); 40 | }); 41 | 42 | it('should group companies by contacts phone numbers', function () { 43 | var t = trans(data) 44 | .mapff('contacts.phones', 'phones') 45 | .flattenf('phones') 46 | .group('phones.') 47 | .value(); 48 | inspectGroup(t); 49 | }); 50 | 51 | it('should group companies by contacts latitude', function () { 52 | var t = trans(data) 53 | .mapff('contacts.addresses.geo.lat', 'lat') 54 | .flattenf('lat', true) 55 | .group('lat.') 56 | .value(); 57 | inspectGroup(t); 58 | }); 59 | 60 | it('should group by the contacts geo locations', function () { 61 | var t = trans(data) 62 | .mapff('contacts.addresses.geo', 'geo', '.', function (geo) { 63 | return Math.floor(parseFloat(geo.lat) + parseFloat(geo.lng)); 64 | }) 65 | .flattenf('geo', true) 66 | .group('geo.') 67 | .value(); 68 | inspectGroup(t); 69 | }); 70 | }); 71 | 72 | describe('map', function () { 73 | it('should extract contacts latitude', function () { 74 | var t = trans(data) 75 | .map('.', 'contacts', '.', 'addresses', '.', 'geo', 'lat') 76 | .flatten(true) 77 | .value(); 78 | util.inspect(t.length); 79 | }); 80 | }); 81 | 82 | describe('mapf', function () { 83 | it('should replace the geo field with a location field on each contact address', function () { 84 | var t = trans(data) 85 | .mapf('contacts.addresses.geo', function (geo) { return geo.lat + ':' + geo.lng; }) 86 | .value(); 87 | util.inspect(t.length); 88 | }); 89 | }); 90 | 91 | describe('mapff', function () { 92 | it('should create a location field on each contact', function () { 93 | var t = trans(data) 94 | .mapff('contacts.addresses.geo', 'contacts.loc', '.', function (geo) { return geo.lat + ':' + geo.lng; }) 95 | .mapf('contacts.loc', ['join', ', ']) 96 | .pluck('contacts.loc') 97 | .flatten() 98 | .value(); 99 | util.inspect(t.length); 100 | }); 101 | 102 | it('should map the geo field to the sum of lat, long', function () { 103 | trans(data) 104 | .mapff('contacts.addresses.geo', 'contacts.addresses.sum', function (geo) { 105 | return this.indexes + ':' + geo.lat + geo.lng; 106 | }) 107 | .value(); 108 | }); 109 | 110 | it('should map the city field to a city slug field on every contact address', function () { 111 | var t = trans(data) 112 | .mapff('contacts.addresses.city', 'contacts.addresses.citySlug', faker.Helpers.slugify) 113 | .pluck('contacts.addresses.citySlug') 114 | .flatten(true) 115 | .value(); 116 | util.inspect(t.length); 117 | }); 118 | }); 119 | 120 | describe('remove', function () { 121 | it('should remove items from the contacts addresses', function () { 122 | trans(data) 123 | .remove('contacts.addresses.geo.lat', 'contacts.addresses.suite') 124 | .value(); 125 | }); 126 | }); 127 | 128 | describe('omit', function () { 129 | it('should remove items from the contacts addresses', function () { 130 | trans(data) 131 | .omit('contacts.addresses.geo.lat', 'contacts.addresses.suite') 132 | .value(); 133 | }); 134 | }); 135 | 136 | describe('copy', function () { 137 | it('should deep copy the data object', function () { 138 | trans(data).copy(); 139 | }); 140 | }); 141 | 142 | describe('sort', function () { 143 | it('should sort the companies by first location latitude', function () { 144 | trans(data) 145 | .sortf('locations', 'geo.lat') 146 | .firstf('locations') 147 | .sort('locations.geo.lat'); 148 | }); 149 | }); 150 | 151 | describe('object', function () { 152 | it('should create an object indexed by the contact names', function () { 153 | var t = trans(data) 154 | .pluck('contacts') 155 | .flatten() 156 | .object(null, null, function (c) { 157 | return c.first + '-' + c.last + '-' + this.index; 158 | }) 159 | .value(); 160 | 161 | util.inspect(trans(t).array().value().length); 162 | }); 163 | 164 | it('should create an object indexed by the contact latitude', function () { 165 | var t = trans(data) 166 | .mapff('contacts.addresses.geo.lat', 'lat') 167 | .flattenf('lat', true) 168 | .object('lat.') 169 | .value(); 170 | util.inspect(trans(t).array().value().length); 171 | }); 172 | }); 173 | 174 | describe('filter', function () { 175 | it('should filter companies that have contacts in california', function () { 176 | var t = trans(data) 177 | .filter('contacts.addresses.state', trans, ['flatten', true], 'value', 'join', ['match', /cali/i]) 178 | .value(); 179 | util.inspect(t.length); 180 | }); 181 | 182 | it('should filter companies that have websites ending in .com', function () { 183 | var t = trans(data) 184 | .filter('website', ['match', /\.com$/i]) 185 | .value(); 186 | util.inspect(t.length); 187 | }); 188 | }); 189 | 190 | describe('default', function () { 191 | it('should set the geo defaults', function () { 192 | trans(data) 193 | .default('contacts.addresses.geo.lat', 1, 'contacts.addresses.geo.lng', 2) 194 | .value(); 195 | }); 196 | }); 197 | 198 | describe('uniq', function () { 199 | it('should get all contacts states distinct', function () { 200 | var before = 0, after = 0; 201 | trans(data) 202 | .pluck('contacts.addresses.state') 203 | .flatten(true) 204 | .get(function (states) { before = states.length; }) 205 | .uniq() 206 | .get(function (states) { after = states.length; }) 207 | .value(); 208 | util.inspect([ before, after ]); 209 | }); 210 | }); 211 | }; 212 | -------------------------------------------------------------------------------- /test/trans/pick-test.js: -------------------------------------------------------------------------------- 1 | module.exports = function (util) { 2 | var trans = util.trans 3 | , assert = util.assert 4 | , square = util.square 5 | , mod = util.mod 6 | , sum = util.sum 7 | , add = util.add; 8 | 9 | describe('pick', function () { 10 | it('should keep only the specified fields', function () { 11 | var o = { 12 | a: { 13 | b: 1 14 | , c: 2 15 | , p: { d: 3, e: 4 } 16 | , q: { g: 5, h: 6, f: { k: 8, l: 10, r: 22 } } 17 | } 18 | , m: { n: 11 } 19 | } 20 | , t = trans(o).pick('a.c', 'a.p.d', 'a.q.h', 'a.q.f.k', 'a.q.f.l').value(); 21 | assert.deepEqual(t, { 22 | a: { 23 | c: 2 24 | , p: { d: 3 } 25 | , q: { h: 6, f: { k: 8, l: 10 } } 26 | } 27 | }); 28 | }); 29 | 30 | it('should keep only the specified field on all objects in an array', function () { 31 | var o = [ 32 | { a: { b: 'a', c: 'b' }, d: { e: 1, f: 2 } } 33 | , { a: { b: 'b', c: 'e' }, d: { e: 2, f: 3 } } 34 | , { a: { b: 'c', c: 'f' }, d: { e: 3, f: 4 } } 35 | , { a: { b: 'd', c: 'g' }, d: { e: 4, f: 5 } } 36 | ] 37 | , t = trans(o).pick('d.e').value(); 38 | assert.deepEqual(t, [ 39 | { d: { e: 1 } } 40 | , { d: { e: 2 } } 41 | , { d: { e: 3 } } 42 | , { d: { e: 4 } } 43 | ]); 44 | }); 45 | 46 | it('should handle missing keys', function () { 47 | var o = [ { a: { b: 1, c: 2 } }, { a: { c: 3 } }, {} ] 48 | , t = trans(o).pick('a.b').value(); 49 | assert.deepEqual(t, [ { a: { b: 1 } }, { a: {} }, {} ]); 50 | }); 51 | 52 | it('should handle functions', function () { 53 | var o = { a: { b: 1, c: function () { return 1; } }, e: 2, f: 3 } 54 | , t = trans(o).pick('a.c', 'f').value(); 55 | assert.strictEqual( 56 | util.stringify(t) 57 | , util.stringify({ a: { c: function () { return 1; } }, f: 3 })); 58 | }); 59 | 60 | it('should work with nested arrays 1', function () { 61 | var o = [ 62 | { a: { b: [ { c: 10, d: 10 }, { c: 20, d: 20 } ], d: 'a', e: [ { f: 2 } ] } } 63 | , { a: { b: [ { c: 11, d: 11 }, { c: 12, d: 12 } ], d: 'b', e: [ { f: 2 } ] } } 64 | , { a: { b: [ { c: 21, d: 21 }, { c: 22, d: 22 } ], d: 'c', e: [ { f: 2 } ] } } 65 | ] 66 | , t = trans(o).pick('a.b.d', 'a.d').value(); 67 | assert.deepEqual(t, [ 68 | { a: { b: [ { d: 10 }, { d: 20 } ], d: 'a' } } 69 | , { a: { b: [ { d: 11 }, { d: 12 } ], d: 'b' } } 70 | , { a: { b: [ { d: 21 }, { d: 22 } ], d: 'c' } } 71 | ]); 72 | }); 73 | 74 | it('should handle primitives', function () { 75 | var o = { a: { b: 1 } } 76 | , t = trans(o).pick('a.b.c').value(); 77 | assert.deepEqual(t, o); 78 | }); 79 | 80 | it('should work with nested arrays 2', function () { 81 | var o = [ [ 82 | { a: [ 83 | { b: 1, c: [ 84 | { d: 1, e: 2, f: { g: 1, h: 2 } }, { d: 1, e: 2, f: { g: 1, h: 2 } } ] 85 | } 86 | , { b: 2, c: [ 87 | { d: 2, e: 3, f: { g: 2, h: 3 } }, { d: 2, e: 3, f: { g: 2, h: 3 } } ] 88 | } 89 | , { b: 2, c: [ 90 | { d: 2, e: 3, f: { g: 2, h: 3 } }, { d: 2, e: 3, f: { g: 2, h: 3 } } ] 91 | } ] 92 | , b: 4 93 | } 94 | , { a: [ 95 | { b: 1, c: [ 96 | { d: 1, e: 2, f: { g: 1, h: 2 } }, { d: 1, e: 2, f: { g: 1, h: 2 } } ] 97 | } 98 | , { b: 2, c: [ 99 | { d: 2, e: 3, f: { g: 2, h: 3 } }, { d: 2, e: 3, f: { g: 2, h: 3 } } ] 100 | } 101 | , { b: 2, c: [ 102 | { d: 2, e: 3, f: { g: 2, h: 3 } }, { d: 2, e: 3, f: { g: 2, h: 3 } } ] 103 | } ] 104 | , b: 4 105 | }] 106 | , [ 107 | { a: [ 108 | { b: 1, c: [ 109 | { d: 1, e: 2, f: { g: 1, h: 2 } }, { d: 1, e: 2, f: { g: 1, h: 2 } } ] 110 | } 111 | , { b: 2, c: [ 112 | { d: 2, e: 3, f: { g: 2, h: 3 } }, { d: 2, e: 3, f: { g: 2, h: 3 } } ] 113 | } 114 | , { b: 2, c: [ 115 | { d: 2, e: 3, f: { g: 2, h: 3 } }, { d: 2, e: 3, f: { g: 2, h: 3 } } ] 116 | } ] 117 | , b: 4 118 | } 119 | , { a: [ 120 | { b: 1, c: [ 121 | { d: 1, e: 2, f: { g: 1, h: 2 } }, { d: 1, e: 2, f: { g: 1, h: 2 } } ] 122 | } 123 | , { b: 2, c: [ 124 | { d: 2, e: 3, f: { g: 2, h: 3 } }, { d: 2, e: 3, f: { g: 2, h: 3 } } ] 125 | } 126 | , { b: 2, c: [ 127 | { d: 2, e: 3, f: { g: 2, h: 3 } }, { d: 2, e: 3, f: { g: 2, h: 3 } } ] 128 | } ] 129 | , b: 4 130 | }]] 131 | , t = trans(o).pick('a.c.e', 'a.c.f.h').value(); 132 | assert.deepEqual(t, [ 133 | [ 134 | { a: [ 135 | { c: [ { e: 2, f: { h: 2 } }, { e: 2, f: { h: 2 } } ] } 136 | , { c: [ { e: 3, f: { h: 3 } }, { e: 3, f: { h: 3 } } ] } 137 | , { c: [ { e: 3, f: { h: 3 } }, { e: 3, f: { h: 3 } } ] } ] 138 | } 139 | , { a: [ 140 | { c: [ { e: 2, f: { h: 2 } }, { e: 2, f: { h: 2 } } ] } 141 | , { c: [ { e: 3, f: { h: 3 } }, { e: 3, f: { h: 3 } } ] } 142 | , { c: [ { e: 3, f: { h: 3 } }, { e: 3, f: { h: 3 } } ] } ] 143 | } 144 | ] 145 | , [ 146 | { a: [ 147 | { c: [ { e: 2, f: { h: 2 } }, { e: 2, f: { h: 2 } } ] } 148 | , { c: [ { e: 3, f: { h: 3 } }, { e: 3, f: { h: 3 } } ] } 149 | , { c: [ { e: 3, f: { h: 3 } }, { e: 3, f: { h: 3 } } ] } ] 150 | } 151 | , { a: [ 152 | { c: [ { e: 2, f: { h: 2 } }, { e: 2, f: { h: 2 } } ] } 153 | , { c: [ { e: 3, f: { h: 3 } }, { e: 3, f: { h: 3 } } ] } 154 | , { c: [ { e: 3, f: { h: 3 } }, { e: 3, f: { h: 3 } } ] } ] 155 | } 156 | ] ]); 157 | }); 158 | }); 159 | 160 | describe('pickf', function () { 161 | it('should apply pick at the given field 1', function () { 162 | var o = { a: 1, b: { c: [ { d: 1, e: 2 }, { d: 3, e: 4 } ], f: 'b' }, g: 'c' } 163 | , t = trans(o).pickf('b.c', 'd').value(); 164 | assert.deepEqual(t, { a: 1, b: { c: [ { d: 1 }, { d: 3 } ], f: 'b' }, g: 'c' }); 165 | }); 166 | 167 | it('should apply pick at the given field 2', function () { 168 | var o = { a: 1, b: { c: [ { d: 1, e: 2 }, { d: 3, e: 4 } ], f: 'b' }, g: 'c' } 169 | , t = trans(o).pickf('b', 'c.d').value(); 170 | assert.deepEqual(t, { a: 1, b: { c: [ { d: 1 }, { d: 3 } ] }, g: 'c' }); 171 | }); 172 | }); 173 | 174 | describe('pickff', function () { 175 | it('should apply pick on the target and set it on the destination', function () { 176 | var o = { a: 1, b: { c: [ { d: 1, e: 2 }, { d: 3, e: 4 } ], f: 'b' }, g: 'c' } 177 | , t = trans(o).pickff('b.c', 'b.p', 'd').value(); 178 | assert.deepEqual(t, { 179 | a: 1 180 | , b: { 181 | c: [ { d: 1, e: 2 }, { d: 3, e: 4 } ] 182 | , p: [ { d: 1 }, { d: 3 } ] 183 | , f: 'b' 184 | } 185 | , g: 'c' 186 | }); 187 | }); 188 | }); 189 | }; 190 | -------------------------------------------------------------------------------- /test/trans/list-test.js: -------------------------------------------------------------------------------- 1 | module.exports = function (util) { 2 | var trans = util.trans 3 | , assert = util.assert 4 | , add = util.add 5 | , mod = util.mod; 6 | 7 | describe('take', function () { 8 | it('should keep only the specified number of items from an array', function () { 9 | var o = [ 1, 2, 3, 4, 5, 6 ] 10 | , t = trans(o).take(4).value(); 11 | assert.deepEqual(t, [1, 2, 3, 4]); 12 | }); 13 | }); 14 | 15 | describe('takef', function () { 16 | it('should keep only the specified number of items from the target array', function () { 17 | var o = { a: { b: [ { c: 1 }, { c: 2 }, { c: 3 }, { c: 4 }, { c: 5 } ] } } 18 | , t = trans(o).takef('a.b', 3).value(); 19 | assert.deepEqual(t, { a: { b: [ { c: 1 }, { c: 2 }, { c: 3 } ] } }); 20 | }); 21 | }); 22 | 23 | describe('skip', function () { 24 | it('should remove the first specified items from an array', function () { 25 | var o = [ 1, 2, 3, 4, 5, 6, 7, 8 ] 26 | , t = trans(o).skip(3).value(); 27 | assert.deepEqual(t, [ 4, 5, 6, 7, 8 ]); 28 | }); 29 | }); 30 | 31 | describe('pluck', function () { 32 | it('should pluck the specified field from all objects in an array', function () { 33 | var o = [ { a: { b: 1, c: 1 } }, { a: { b: 2, c: 'b' } }, { a: { b: 3 } }, { a: { b: 4, d: 'F' } }, { a: { b: 5 } } ] 34 | , t = trans(o).pluck('a.b').value(); 35 | assert.deepEqual(t, [ 1, 2, 3, 4, 5 ]); 36 | }); 37 | 38 | it('should pluck the specified field and apply transformations', function () { 39 | var o = [ { a: { b: 'aab', c: 1 } }, { a: { b: 'aac', c: 'b' } }, { a: { b: 'foo' } }, { a: { b: 'bar', d: 'F' } }, { a: { b: 'GFK' } } ] 40 | , t = trans(o).pluck('a.b', [ 'charAt', 0 ], 'toUpperCase').value(); 41 | assert.deepEqual(t, [ 'A', 'A', 'F', 'B', 'G' ]); 42 | }); 43 | 44 | it('should pluck the specified field from a single object', function () { 45 | var o = { a: { b: 1, c: { d: { e: 'ABC', f: 3 } }, g: { h: 10 } }, k: 2 } 46 | , t = trans(o).pluck('a.c.d.e', 'toLowerCase').value(); 47 | assert.strictEqual(t, 'abc'); 48 | }); 49 | 50 | it('should handle multiple arrays', function () { 51 | var o = [ 52 | [ { a: [ { b: 1 }, { b: 2 } ] }, { a: [ { b: 3 } ] } ] 53 | , [ { a: [ { b: 10 } ] }, { a: [ { b: 11 }, { b: 12 }, { b: 13 } ] } ] ] 54 | , t = trans(o).pluck('a.b', '.', [add, 5]).value(); 55 | assert.deepEqual(t, [ [ [ 6, 7 ], [ 8 ] ], [ [ 15 ], [ 16, 17, 18 ] ] ]); 56 | }); 57 | }); 58 | 59 | describe('pluckf', function () { 60 | it('should replace the field value with the specified pluck values 1', function () { 61 | var o = { a: [ { b: { c: { d: 1 } } }, { b: { c: { d: 2 } } }, { b: { c: { d: 1 } } } ] } 62 | , t = trans(o).pluckf('a.b', 'c.d').value(); 63 | assert.deepEqual(t, { a: [ { b: 1 }, { b: 2 }, { b: 1 } ] }); 64 | }); 65 | 66 | it('should replace the field value with the specified pluck values 2', function () { 67 | var o = { a: [ { b: { c: { d: 1 } } }, { b: { c: { d: 2 } } }, { b: { c: { d: 1 } } } ] } 68 | , t = trans(o).pluckf('a', 'b.c.d').value(); 69 | assert.deepEqual(t, { a: [ 1, 2, 1 ] }); 70 | }); 71 | }); 72 | 73 | describe('first', function () { 74 | it('should keep only the first element in the list', function () { 75 | var o = [ 1, 2, 3, 4 ] 76 | , t = trans(o).first().value(); 77 | assert.deepEqual(t, 1); 78 | }); 79 | 80 | it('should not modify the original array', function () { 81 | var o = [ 1, 2, 3, 4, 5 ] 82 | , t = trans(o).first().value(); 83 | assert.deepEqual(o, [ 1, 2, 3, 4, 5 ]); 84 | }); 85 | 86 | it('should handle empty arrays', function () { 87 | var t = trans([]).first().value(); 88 | assert.strictEqual(t, null); 89 | }); 90 | }); 91 | 92 | describe('firstf', function () { 93 | it('should keep only the first element from the target array', function () { 94 | var o = { a: { b: [ { c: 1 }, { c: 2 }, { c: 3 }, { c: 4 }, { c: 5 } ] } } 95 | , t = trans(o).firstf('a.b').value(); 96 | assert.deepEqual(t, { a: { b: { c: 1 } } }); 97 | }); 98 | }); 99 | 100 | describe('firstff', function () { 101 | it('should keep only the first element from the target array and set it on the destination', function () { 102 | var o = { a: { b: [ { c: 1 }, { c: 2 }, { c: 3 }, { c: 4 }, { c: 5 } ] } } 103 | , t = trans(o).firstff('a.b', 'a.e').value(); 104 | assert.deepEqual(t, { a: { 105 | b: [ { c: 1 }, { c: 2 }, { c: 3 }, { c: 4 }, { c: 5 } ] 106 | , e: { c: 1 } 107 | } }); 108 | }); 109 | }); 110 | 111 | describe('last', function () { 112 | it('should keep only the last element in the list', function () { 113 | var o = [ 1, 2, 3, 4 ] 114 | , t = trans(o).last().value(); 115 | assert.deepEqual(t, 4); 116 | }); 117 | 118 | it('should not modify the original array', function () { 119 | var o = [ 1, 2, 3, 4, 5 ] 120 | , t = trans(o).last().value(); 121 | assert.deepEqual(o, [ 1, 2, 3, 4, 5 ]); 122 | }); 123 | 124 | it('should handle empty arrays', function () { 125 | var t = trans([]).last().value(); 126 | assert.strictEqual(t, null); 127 | }); 128 | }); 129 | 130 | describe('lastf', function () { 131 | it('should keep only the last element from the target array', function () { 132 | var o = { a: { b: [ { c: 1 }, { c: 2 }, { c: 3 }, { c: 4 }, { c: 5 } ] } } 133 | , t = trans(o).lastf('a.b').value(); 134 | assert.deepEqual(t, { a: { b: { c: 5 } } }); 135 | }); 136 | }); 137 | 138 | describe('firstff', function () { 139 | it('should keep only the last element from the target array and set it on the destination', function () { 140 | var o = { a: { b: [ { c: 1 }, { c: 2 }, { c: 3 }, { c: 4 }, { c: 5 } ] } } 141 | , t = trans(o).lastff('a.b', 'a.e').value(); 142 | assert.deepEqual(t, { a: { 143 | b: [ { c: 1 }, { c: 2 }, { c: 3 }, { c: 4 }, { c: 5 } ] 144 | , e: { c: 5 } 145 | } }); 146 | }); 147 | }); 148 | 149 | describe('uniq', function () { 150 | it('should remove all duplicates according to the specified field', function () { 151 | var o = [ { a: { b: 1 } }, { a: { b: '1' } }, { a: { b: 2 } }, { a: { b: 1 } }, { a: { b: 10 } } ] 152 | , t = trans(o).uniq('a.b').value(); 153 | assert.deepEqual(t, [ 154 | { a: { b: 1 } }, { a: { b: '1' } }, { a: { b: 2 } }, { a: { b: 10 } } 155 | ]); 156 | }); 157 | 158 | it('should apply transformers and remove all duplicates', function () { 159 | var o = [ { a: { b: 1 } }, { a: { b: '1' } }, { a: { b: 2 } }, { a: { b: 1 } }, { a: { b: 10 } } ] 160 | , t = trans(o).uniq('a.b', parseInt).value(); 161 | assert.deepEqual(t, [ 162 | { a: { b: 1 } }, { a: { b: 2 } }, { a: { b: 10 } } 163 | ]); 164 | }); 165 | 166 | it('should not remove if the value is null or undefined', function () { 167 | var o = [ { a: { b: 1 } }, { a: {} }, { a: { b: 2 } }, { a: {} }, { a: { b: 1 } } ] 168 | , t = trans(o).uniq('a.b').value(); 169 | assert.deepEqual(t, [ 170 | { a: { b: 1 } }, { a: {} }, { a: { b: 2 } }, { a: {} } 171 | ]); 172 | }); 173 | 174 | it('should remove duplicates in an array of strings', function () { 175 | var o = [ 'a', 'b', 'a', 'a', 'b', 'c', 'd', 'a', 'e' ] 176 | , t = trans(o).uniq().value(); 177 | assert.deepEqual(t, [ 'a', 'b', 'c', 'd', 'e' ]); 178 | }); 179 | }); 180 | 181 | describe('uniqf', function () { 182 | it('should remove duplicates from the target array', function () { 183 | var o = [ 184 | { a: { b: [ 1, 1, 3, 4, 4, 11, 12, 99, 9, 3, 4 ] } } 185 | , { a: { b: [ 1, 1, 14, 4 ] } } ] 186 | , t = trans(o).uniqf('a.b', null, [mod, 10]).value(); 187 | assert.deepEqual(t, [ 188 | { a: { b: [ 1, 3, 4, 12, 99 ] } } 189 | , { a: { b: [ 1, 14 ] } } ]); 190 | }); 191 | }); 192 | 193 | describe('uniqff', function () { 194 | it('should remove duplicates from the target array and set it on the destination', function () { 195 | var o = { a: [ 1, 1, 3 ] } 196 | , t = trans(o).uniqff('a', 'b').value(); 197 | assert.deepEqual(t, { a: [ 1, 1, 3 ], b: [ 1, 3 ] }); 198 | }); 199 | }); 200 | }; 201 | -------------------------------------------------------------------------------- /test/trans/remove-test.js: -------------------------------------------------------------------------------- 1 | module.exports = function (util) { 2 | var trans = util.trans 3 | , assert = util.assert 4 | , square = util.square 5 | , mod = util.mod 6 | , sum = util.sum 7 | , add = util.add; 8 | 9 | describe('remove', function () { 10 | it('should remove the specified fields', function () { 11 | var o = { 12 | a: { 13 | b: 1 14 | , c: 2 15 | , p: { d: 3, e: 4 } 16 | , q: { g: 5, h: 6, f: { k: 8, l: 10, r: 22 } } 17 | } 18 | , m: { n: 11 } 19 | } 20 | , t = trans(o).remove('a.b', 'a.p.e', 'a.q.g', 'a.q.f.r', 'm').value(); 21 | assert.deepEqual(t, { 22 | a: { 23 | c: 2 24 | , p: { d: 3 } 25 | , q: { h: 6, f: { k: 8, l: 10 } } 26 | } 27 | }); 28 | }); 29 | 30 | it('should not remove null fields if not specified', function () { 31 | var o = { a: 1, b: { c: 'a', d: null, e: 2 } } 32 | , t = trans(o).remove('b.c').value(); 33 | assert.deepEqual(t, { a: 1, b: { d: null, e: 2 } }); 34 | }); 35 | 36 | it('should not change the object if no fields are specified', function () { 37 | var o = { 38 | a: { 39 | b: 1 40 | , c: 2 41 | , p: { d: 3, e: 4 } 42 | , q: { g: 5, h: 6, f: { k: 8, l: 10, r: 22 } } 43 | , r: [ { s: { t: [ 1 ] } }, { s: { t: [ 2, 3, 4 ] } } ] 44 | } 45 | , m: { n: 11 } 46 | } 47 | , t = trans(o).remove().value(); 48 | assert.deepEqual(t, o); 49 | assert.strictEqual(t, o); 50 | }); 51 | 52 | it('should remove all the specified fields in place', function () { 53 | var o = { 54 | a: { 55 | b: 1 56 | , c: 2 57 | , p: { d: 3, e: 4 } 58 | , q: { g: 5, h: 6, f: { k: 8, l: 10, r: 22 } } 59 | , r: [ { s: { t: [ 1 ] } }, { s: { t: [ 2, 3, 4 ] } } ] 60 | } 61 | , m: { n: 11 } 62 | } 63 | , t = trans(o).remove('a.c', 'a.p.e', 'a.q.f.k', 'a.r.s.t').value(); 64 | assert.deepEqual(o, { 65 | a: { 66 | b: 1 67 | , p: { d: 3 } 68 | , q: { g: 5, h: 6, f: { l: 10, r: 22 } } 69 | , r: [ { s: {} }, { s: {} } ] 70 | } 71 | , m: { n: 11 } 72 | }); 73 | assert.deepEqual(t, o); 74 | assert.strictEqual(t, o); 75 | }); 76 | 77 | it('should remove the specified fields on all objects in an array', function () { 78 | var o = [ 79 | { a: { b: 'a', c: 'b' }, d: { e: 1, f: 2 } } 80 | , { a: { b: 'b', c: 'e' }, d: { e: 2, f: 3 } } 81 | , { a: { b: 'c', c: 'f' }, d: { e: 3, f: 4 } } 82 | , { a: { b: 'd', c: 'g' }, d: { e: 4, f: 5 } } 83 | ] 84 | , t = trans(o).remove('a', 'd.f').value(); 85 | assert.deepEqual(t, [ 86 | { d: { e: 1 } } 87 | , { d: { e: 2 } } 88 | , { d: { e: 3 } } 89 | , { d: { e: 4 } } 90 | ]); 91 | }); 92 | 93 | it('should handle missing keys', function () { 94 | var o = [ { a: { b: 1, c: 2 } }, { a: { c: 3 } }, {} ] 95 | , t = trans(o).remove('a.c').value(); 96 | assert.deepEqual(t, [ { a: { b: 1 } }, { a: {} }, {} ]); 97 | }); 98 | 99 | it('should handle functions', function () { 100 | var o = { a: { b: 1, c: function () { return 1; } }, e: 2, f: 3 } 101 | , t = trans(o).remove('a.b', 'e').value(); 102 | assert.strictEqual( 103 | util.stringify(t) 104 | , util.stringify({ a: { c: function () { return 1; } }, f: 3 })); 105 | }); 106 | 107 | it('should remove an array field', function () { 108 | var o = { a: 1, b: [ 1, 2, 4 ] } 109 | , t = trans(o).remove('b').value(); 110 | assert.deepEqual(t, { a: 1 }); 111 | }); 112 | 113 | it('should work with nested arrays 1', function () { 114 | var o = [ 115 | { a: { b: [ { c: 10, d: 10 }, { c: 20, d: 20 } ], d: 'a', e: [ { f: 2 } ] } } 116 | , { a: { b: [ { c: 11, d: 11 }, { c: 12, d: 12 } ], d: 'b', e: [ { f: 2 } ] } } 117 | , { a: { b: [ { c: 21, d: 21 }, { c: 22, d: 22 } ], d: 'c', e: [ { f: 2 } ] } } 118 | ] 119 | , t = trans(o).remove('a.b.c', 'a.e').value(); 120 | assert.deepEqual(t, [ 121 | { a: { b: [ { d: 10 }, { d: 20 } ], d: 'a' } } 122 | , { a: { b: [ { d: 11 }, { d: 12 } ], d: 'b' } } 123 | , { a: { b: [ { d: 21 }, { d: 22 } ], d: 'c' } } 124 | ]); 125 | }); 126 | 127 | it('should handle primitives', function () { 128 | var o = { a: { b: 1 } } 129 | , t = trans(o).remove('a.b.c').value(); 130 | assert.deepEqual(t, o); 131 | }); 132 | 133 | it('should work with nested arrays 2', function () { 134 | var o = [ [ 135 | { a: [ 136 | { b: 1, c: [ 137 | { d: 1, e: 2, f: { g: 1, h: 2 } }, { d: 1, e: 2, f: { g: 1, h: 2 } } ] 138 | } 139 | , { b: 2, c: [ 140 | { d: 2, e: 3, f: { g: 2, h: 3 } }, { d: 2, e: 3, f: { g: 2, h: 3 } } ] 141 | } 142 | , { b: 2, c: [ 143 | { d: 2, e: 3, f: { g: 2, h: 3 } }, { d: 2, e: 3, f: { g: 2, h: 3 } } ] 144 | } ] 145 | , b: 4 146 | } 147 | , { a: [ 148 | { b: 1, c: [ 149 | { d: 1, e: 2, f: { g: 1, h: 2 } }, { d: 1, e: 2, f: { g: 1, h: 2 } } ] 150 | } 151 | , { b: 2, c: [ 152 | { d: 2, e: 3, f: { g: 2, h: 3 } }, { d: 2, e: 3, f: { g: 2, h: 3 } } ] 153 | } 154 | , { b: 2, c: [ 155 | { d: 2, e: 3, f: { g: 2, h: 3 } }, { d: 2, e: 3, f: { g: 2, h: 3 } } ] 156 | } ] 157 | , b: 4 158 | }] 159 | , [ 160 | { a: [ 161 | { b: 1, c: [ 162 | { d: 1, e: 2, f: { g: 1, h: 2 } }, { d: 1, e: 2, f: { g: 1, h: 2 } } ] 163 | } 164 | , { b: 2, c: [ 165 | { d: 2, e: 3, f: { g: 2, h: 3 } }, { d: 2, e: 3, f: { g: 2, h: 3 } } ] 166 | } 167 | , { b: 2, c: [ 168 | { d: 2, e: 3, f: { g: 2, h: 3 } }, { d: 2, e: 3, f: { g: 2, h: 3 } } ] 169 | } ] 170 | , b: 4 171 | } 172 | , { a: [ 173 | { b: 1, c: [ 174 | { d: 1, e: 2, f: { g: 1, h: 2 } }, { d: 1, e: 2, f: { g: 1, h: 2 } } ] 175 | } 176 | , { b: 2, c: [ 177 | { d: 2, e: 3, f: { g: 2, h: 3 } }, { d: 2, e: 3, f: { g: 2, h: 3 } } ] 178 | } 179 | , { b: 2, c: [ 180 | { d: 2, e: 3, f: { g: 2, h: 3 } }, { d: 2, e: 3, f: { g: 2, h: 3 } } ] 181 | } ] 182 | , b: 4 183 | }]] 184 | , t = trans(o).remove('b', 'a.b', 'a.c.d', 'a.c.f.g').value(); 185 | assert.deepEqual(t, [ 186 | [ 187 | { a: [ 188 | { c: [ { e: 2, f: { h: 2 } }, { e: 2, f: { h: 2 } } ] } 189 | , { c: [ { e: 3, f: { h: 3 } }, { e: 3, f: { h: 3 } } ] } 190 | , { c: [ { e: 3, f: { h: 3 } }, { e: 3, f: { h: 3 } } ] } ] 191 | } 192 | , { a: [ 193 | { c: [ { e: 2, f: { h: 2 } }, { e: 2, f: { h: 2 } } ] } 194 | , { c: [ { e: 3, f: { h: 3 } }, { e: 3, f: { h: 3 } } ] } 195 | , { c: [ { e: 3, f: { h: 3 } }, { e: 3, f: { h: 3 } } ] } ] 196 | } 197 | ] 198 | , [ 199 | { a: [ 200 | { c: [ { e: 2, f: { h: 2 } }, { e: 2, f: { h: 2 } } ] } 201 | , { c: [ { e: 3, f: { h: 3 } }, { e: 3, f: { h: 3 } } ] } 202 | , { c: [ { e: 3, f: { h: 3 } }, { e: 3, f: { h: 3 } } ] } ] 203 | } 204 | , { a: [ 205 | { c: [ { e: 2, f: { h: 2 } }, { e: 2, f: { h: 2 } } ] } 206 | , { c: [ { e: 3, f: { h: 3 } }, { e: 3, f: { h: 3 } } ] } 207 | , { c: [ { e: 3, f: { h: 3 } }, { e: 3, f: { h: 3 } } ] } ] 208 | } 209 | ] ]); 210 | }); 211 | }); 212 | }; 213 | -------------------------------------------------------------------------------- /test/trans/object-test.js: -------------------------------------------------------------------------------- 1 | module.exports = function (util) { 2 | var trans = util.trans 3 | , assert = util.assert 4 | , add = util.add 5 | , sum = util.sum 6 | , square = util.square; 7 | 8 | describe('object', function () { 9 | it('should objectify an array by the given key', function () { 10 | var o = [ { a: 1, b: 'foo' }, { a: 2, b: 'wow' }, { a: 3, b: 'pow' } ] 11 | , t1 = trans(o).object('a').value() 12 | , t2 = trans(o).object('b').value(); 13 | 14 | assert.deepEqual(t1, { 15 | 1: { a: 1, b: 'foo' } 16 | , 2: { a: 2, b: 'wow' } 17 | , 3: { a: 3, b: 'pow' } 18 | }); 19 | 20 | assert.deepEqual(t2, { 21 | foo: { a: 1, b: 'foo' } 22 | , wow: { a: 2, b: 'wow' } 23 | , pow: { a: 3, b: 'pow' } 24 | }); 25 | }); 26 | 27 | it('should objectify an array by the given key and value', function () { 28 | var o = [ { a: 1, b: 'foo' }, { a: 2, b: 'wow' }, { a: 3, b: 'pow' } ] 29 | , t = trans(o).object('b', 'a').value(); 30 | assert.deepEqual(t, { foo: 1, wow: 2, pow: 3 }); 31 | }); 32 | 33 | it('should handle nested keys', function () { 34 | var o = [ { a: { b: { c: 2 } } }, { a: { b: { c: 3 } } }, { a: { b: { c: 4 } } } ] 35 | , t = trans(o).object('a.b.c').value(); 36 | assert.deepEqual(t, { 37 | 2: { a: { b: { c: 2 } } } 38 | , 3: { a: { b: { c: 3 } } } 39 | , 4: { a: { b: { c: 4 } } } 40 | }); 41 | }); 42 | 43 | it('should handle nested values 1', function () { 44 | var o = [ { a: { b: { c: 2, d: 'A' } } }, { a: { b: { c: 3, d: 'B' } } }, { a: { b: { c: 4, d: 'C' } } } ] 45 | , t = trans(o).object('a.b.c', 'a.b.d').value(); 46 | assert.deepEqual(t, { 47 | 2: 'A' 48 | , 3: 'B' 49 | , 4: 'C' 50 | }); 51 | }); 52 | 53 | it('should handle nested values 2', function () { 54 | var o = [ 55 | { a: { b: { c: 1 } }, d: { e: 'A' } } 56 | , { a: { b: { c: 2 } }, d: { e: 'B' } } 57 | , { a: { b: { c: 3 } }, d: { e: 'C' } } 58 | , { a: { b: { c: 4 } }, d: { e: 'D' } } ] 59 | , t = trans(o).object('a.b.c', 'd.e').value(); 60 | assert.deepEqual(t, { 61 | 1: 'A' 62 | , 2: 'B' 63 | , 3: 'C' 64 | , 4: 'D' 65 | }); 66 | }); 67 | 68 | it('should allow any key and any value on each source object', function () { 69 | var o = [ 70 | { a: { b: 'A' }, c: [ { d: 10 }, { d: 20 }, { d: 30 } ] } 71 | , { a: { b: 'B' }, c: [ { d: 11 }, { d: 21 }, { d: 31 } ] } 72 | , { a: { b: 'C' }, c: [ { d: 12 }, { d: 22 }, { d: 32 } ] } ] 73 | , t = trans(o).object('a.b', 'c.d', ['concat', '_FOO']).value(); 74 | assert.deepEqual(t, { 75 | A_FOO: [ 10, 20, 30 ] 76 | , B_FOO: [ 11, 21, 31 ] 77 | , C_FOO: [ 12, 22, 32 ] 78 | }); 79 | }); 80 | 81 | it('should allow keys that are arrays', function () { 82 | var o = [ 83 | { a: [ 8, 2, 3 ], b: '823' } 84 | , { a: [ 2, 3, 5 ], b: '235' } 85 | , { a: [ 2, 3, 5, 9 ], b: '2359' } ] 86 | , t = trans(o).object('a', 'b').value(); 87 | assert.deepEqual(t, { '8,2,3': '823' , '2,3,5': '235' , '2,3,5,9': '2359' }); 88 | }); 89 | 90 | it('should allow keys that are arrays and apply transformations', function () { 91 | var o = [ 92 | { a: [ 8, 2, 3 ], b: '823' } 93 | , { a: [ 2, 3, 5 ], b: '235' } 94 | , { a: [ 2, 3, 5, 9 ], b: '2359' } ] 95 | , t = trans(o).object('a', 'b', sum).value(); 96 | assert.deepEqual(t, { 13: '823' , 10: '235' , 19: '2359' }); 97 | }); 98 | 99 | it('should allow keys that are items of an array', function () { 100 | var o = [ 101 | { a: [ 8, 2, 4 ], b: '824' } 102 | , { a: [ 1, 3, 5 ], b: '135' } 103 | , { a: [ 7, 6 ], b: '76' } ] 104 | , t = trans(o).object('a.', 'b').value(); 105 | assert.deepEqual(t, { 106 | 1: '135' 107 | , 2: '824' 108 | , 3: '135' 109 | , 4: '824' 110 | , 5: '135' 111 | , 6: '76' 112 | , 7: '76' 113 | , 8: '824' 114 | }); 115 | }); 116 | 117 | it('should let the last value win in case of collisions', function () { 118 | var o = [ { a: 1, b: 'A' }, { a: 2, b: 'B' }, { a: 1, b: 'C' } ] 119 | , t = trans(o).object('a', 'b').value(); 120 | assert.deepEqual(t, { 1: 'C', 2: 'B' }); 121 | }); 122 | 123 | it('should make the array index available to the key field transformer', function () { 124 | var o = [ { a: 1, b: 'A' }, { a: 2, b: 'B' }, { a: 1, b: 'C' } ] 125 | , t = trans(o) 126 | .object('a', 'b', function (x) { return x + ':' + this.getIndex(); }) 127 | .value(); 128 | assert.deepEqual(t, { '1:0': 'A', '2:1': 'B', '1:2': 'C' }); 129 | }); 130 | 131 | it('should apply key transformations before creating the object', function () { 132 | var o = [ { a: 'abc', b: 1 }, { a: 'cde', b: 2 }, { a: 'efg', b: 3 } ] 133 | , t = trans(o).object('a', null, 'toUpperCase').value(); 134 | assert.deepEqual(t, { 135 | ABC: { a: 'abc', b: 1 } 136 | , CDE: { a: 'cde', b: 2 } 137 | , EFG: { a: 'efg', b: 3 } 138 | }); 139 | }); 140 | 141 | it('should work with nested arrays 1', function () { 142 | var o = [ 143 | { a: [ { b: 1 }, { b: 2 } ] } 144 | , { a: [ { b: 2 }, { b: 2 } ] } 145 | , { a: [ { b: 3 }, { b: 2 } ] } ] 146 | , t = trans(o).object('a.b', 'a', sum).value(); 147 | assert.deepEqual(t, { 148 | 3: [ { b: 1 }, { b: 2 } ] 149 | , 4: [ { b: 2 }, { b: 2 } ] 150 | , 5: [ { b: 3 }, { b: 2 } ] 151 | }); 152 | }); 153 | 154 | it('should work with nested arrays 2', function () { 155 | var o = [ 156 | { a: [ { b: 1 }, { b: 2 } ] } 157 | , { a: [ { b: 3 }, { b: 4 } ] } 158 | , { a: [ { b: 5 }, { b: 6 } ] } ] 159 | , t = trans(o) 160 | .object('a.b.', 'a.b', function (x) { return x + ':' + this.getIndexes(); }) 161 | .value(); 162 | assert.deepEqual(t, { 163 | '1:0,0': [ 1, 2 ] 164 | , '2:1,0': [ 1, 2 ] 165 | , '3:0,1': [ 3, 4 ] 166 | , '4:1,1': [ 3, 4 ] 167 | , '5:0,2': [ 5, 6 ] 168 | , '6:1,2': [ 5, 6 ] 169 | }); 170 | }); 171 | 172 | it('should objectify an array of strings', function () { 173 | var o = [ 'abc', 'def', 'wow', 'dew', 'pow', 'wow', 'dew' ] 174 | , t = trans(o).object().value(); 175 | assert.deepEqual(t, { abc: 'abc', def: 'def', wow: 'wow', dew: 'dew', pow: 'pow' }); 176 | }); 177 | 178 | it('should objectify an array of numbers', function () { 179 | var o = [ 1, 2, 1, 1, 3, 0, 0 ] 180 | , t = trans(o).object(null, null, [add, 1]).value(); 181 | assert.deepEqual(t, { 2: 1, 3: 2, 4: 3, 1: 0 }); 182 | }); 183 | 184 | it('should objectify an array of objects', function () { 185 | var o = [ 1, 2, 1, 1, 3, 0, 0 ] 186 | , t = trans(o) 187 | .map('.', function(x) { return { k: x, v: Boolean(x) }; }) 188 | .object('k', 'v') 189 | .value(); 190 | assert.deepEqual(t, { 1: true, 2: true, 3: true, 0: false }); 191 | }); 192 | 193 | it('should handle missing values', function () { 194 | var o = [ { a: { b: 1 } }, { a: {} }, { a: { b: 2 } } ] 195 | , t = trans(o).object('a.b', 'a').value(); 196 | assert.deepEqual(t, { 1: { b: 1 }, null: {}, 2: { b: 2 } }); 197 | }); 198 | 199 | it('should throw if the target is not an array', function () { 200 | assert.throws(function () { 201 | trans({ a: 1 }).object('a'); 202 | }, /the object target is not an array/i); 203 | }); 204 | }); 205 | 206 | describe('objectf', function () { 207 | it('should objectify the array at the given field', function () { 208 | var o = { a: { b: [ { c: 1, d: 'one' }, { c: 2, d: 'two' } ] } } 209 | , t = trans(o).objectf('a.b', 'c', 'd', square).value(); 210 | assert.deepEqual(t, { a: { b: { 1: 'one', 4: 'two' } } }); 211 | }); 212 | 213 | it('should objectify all target arrays 1', function () { 214 | var o = [ { a: [ 1, 2 ] }, { a: [ 2, 4 ] }, { a: [ 5 ] } ] 215 | , t = trans(o).objectf('a').value(); 216 | assert.deepEqual(t, [ 217 | { a: { 1: 1, 2: 2 } } 218 | , { a: { 2: 2, 4: 4 } } 219 | , { a: { 5: 5 } } 220 | ]); 221 | }); 222 | 223 | it('should objectify all target arrays 2', function () { 224 | var o = [ { a: [ 'ab', 'cd' ] }, { a: [ 'foo', 'bar' ] }, { a: [ 'aaa' ] } ] 225 | , t = trans(o).objectf('a', null, null, ['charAt', 0], 'toUpperCase').value(); 226 | assert.deepEqual(t, [ 227 | { a: { A: 'ab', C: 'cd' } } 228 | , { a: { F: 'foo', B: 'bar' } } 229 | , { a: { A: 'aaa' } } 230 | ]); 231 | }); 232 | }); 233 | 234 | describe('objectff', function () { 235 | it('should objectify the source array and set it on the destination', function () { 236 | var o = { a: { b: [ { c: 1, d: 'one' }, { c: 2, d: 'two' } ] }, e: 'ready' } 237 | , t = trans(o).objectff('a.b', 'e', 'c', 'd').value(); 238 | assert.deepEqual(t, { 239 | a: { b: [ { c: 1, d: 'one' }, { c: 2, d: 'two' } ] } 240 | , e: { 1: 'one', 2: 'two' } 241 | }); 242 | }); 243 | }); 244 | }; 245 | -------------------------------------------------------------------------------- /test/trans/sort-test.js: -------------------------------------------------------------------------------- 1 | module.exports = function (util) { 2 | var trans = util.trans 3 | , assert = util.assert 4 | , square = util.square 5 | , mod = util.mod 6 | , sum = util.sum 7 | , add = util.add; 8 | 9 | describe('sort', function () { 10 | it('should sort an array of numbers', function () { 11 | var o = [ 1, 1, 3, 4, 1, 1 ] 12 | , t = trans(o).sort().value(); 13 | assert.deepEqual(t, [ 1, 1, 1, 1, 3, 4 ]); 14 | }); 15 | 16 | it('should sort an array of numbers in descending order', function () { 17 | var o = [ 1, 1, 3, 4, 1, 1 ] 18 | , t = trans(o).sort(':desc').value(); 19 | assert.deepEqual(t, [ 4, 3, 1, 1, 1, 1 ]); 20 | }); 21 | 22 | it('should sort an array of strings', function () { 23 | var o = [ 'Ash', 'bar', 'Baz', 'baz', 'bak', 'Foo', 'ash' ] 24 | , t = trans(o).sort(null, 'toLowerCase').value(); 25 | assert.deepEqual(t, [ 'Ash', 'ash', 'bak', 'bar', 'Baz', 'baz', 'Foo' ]); 26 | }); 27 | 28 | it('should allow specifying fields as part of the transformers', function () { 29 | var o = [ 30 | { a: { b: [ 1, 2 ] } } 31 | , { a: { b: [ 1 ] } } 32 | , { a: { b: [ 2, 2, 1 ] } } 33 | , { a: { b: [] } } ] 34 | , t = trans(o).sort('a', 'b', sum, [mod, 4]).value(); 35 | assert.deepEqual(t, [ 36 | { a: { b: [] } } 37 | , { a: { b: [ 1 ] } } 38 | , { a: { b: [ 2, 2, 1 ] } } 39 | , { a: { b: [ 1, 2 ] } } 40 | ]); 41 | }); 42 | 43 | it('should allow specifying the sort field with dot notation', function () { 44 | var o = [ 45 | { a: { b: [ 1, 2 ] } } 46 | , { a: { b: [ 1 ] } } 47 | , { a: { b: [ 2, 2, 2 ] } } 48 | , { a: { b: [] } } ] 49 | , t = trans(o).sort('a.b', 'length').value(); 50 | assert.deepEqual(t, [ 51 | { a: { b: [] } } 52 | , { a: { b: [ 1 ] } } 53 | , { a: { b: [ 1, 2 ] } } 54 | , { a: { b: [ 2, 2, 2 ] } } 55 | ]); 56 | }); 57 | 58 | it('should work with nested arrays 1', function () { 59 | var o = [ 60 | { a: { b: [ { c: 1 }, { c: 2 } ] } } 61 | , { a: { b: [ { c: 2 }, { c: 2 } ] } } 62 | , { a: { b: [ { c: 2 }, { c: 1 } ] } } 63 | , { a: { b: [ { c: 3 }, { c: 1 } ] } } 64 | , { a: { b: [ { c: 1 }, { c: 1 } ] } } 65 | ] 66 | , e = [ 67 | { a: { b: [ { c: 1 }, { c: 1 } ] } } 68 | , { a: { b: [ { c: 1 }, { c: 2 } ] } } 69 | , { a: { b: [ { c: 2 }, { c: 1 } ] } } 70 | , { a: { b: [ { c: 2 }, { c: 2 } ] } } 71 | , { a: { b: [ { c: 3 }, { c: 1 } ] } } 72 | ] 73 | , t = trans(o).sort('a.b.c', sum).value(); 74 | assert.deepEqual(t, e); 75 | }); 76 | 77 | it('should work with nested arrays 2', function () { 78 | var o = [ 79 | [ { a: 1 }, { a: 2 } ] 80 | , [ { a: 5 } ] 81 | , [ { a: 0 }, { a: 1 }, { a: 1 } ] 82 | , [ { a: 2 }, { a: 2 } ] ] 83 | , t = trans(o).sort('a', sum).value(); 84 | assert.deepEqual(t, [ 85 | [ { a: 0 }, { a: 1 }, { a: 1 } ] 86 | , [ { a: 1 }, { a: 2 } ] 87 | , [ { a: 2 }, { a: 2 } ] 88 | , [ { a: 5 } ] 89 | ]); 90 | }); 91 | 92 | it('should sort with a specified comparer', function () { 93 | var o = [ { a: 1, b: 1 }, { a: 2, b: 1 }, { a: 1, b: 2 }, { a: 1, b: 0 } ] 94 | , t = trans(o).sort(null, function (x, y) { 95 | return x.a === y.a ? x.b - y.b : x.a - y.a; 96 | }).value(); 97 | assert.deepEqual(t, [ 98 | { a: 1, b: 0 } 99 | , { a: 1, b: 1 } 100 | , { a: 1, b: 2 } 101 | , { a: 2, b: 1 } 102 | ]); 103 | }); 104 | 105 | it('should sort by a given field with a specified comparer', function () { 106 | var o = [ 107 | { a: 'z', b: 1 } 108 | , { a: 'a', b: 2 } 109 | , { a: 'aa', b: 3 } 110 | , { a: 'zaa', b: 4 } 111 | , { a: 'ba', b: 5 } 112 | , { a: 'ca', b: 6 } 113 | , { a: 'ccc', b: 7 } ] 114 | , t = trans(o).sort('a', function (x, y) { 115 | return x.length === y.length ? x.localeCompare(y) : x.length - y.length; 116 | }).value(); 117 | assert.deepEqual(t, [ 118 | { a: 'a', b: 2 } 119 | , { a: 'z', b: 1 } 120 | , { a: 'aa', b: 3 } 121 | , { a: 'ba', b: 5 } 122 | , { a: 'ca', b: 6 } 123 | , { a: 'ccc', b: 7 } 124 | , { a: 'zaa', b: 4 } 125 | ]); 126 | }); 127 | 128 | it('should work with missing fields 1', function () { 129 | var o = [ { a: { b: 1 } }, { a: { b: 5 } }, { a: {} }, { a: { b: 2 } }, { a: {} } ] 130 | , t = trans(o).sort('a.b').value(); 131 | assert.deepEqual(t, [ 132 | { a: {} }, { a: {} }, { a: { b: 1 } }, { a: { b: 2 } }, { a: { b: 5 } } 133 | ]); 134 | }); 135 | 136 | it('should work with missing fields 2', function () { 137 | var o = [ { a: { b: 0 } }, { a: { b: 5 } }, { a: {} }, { a: { b: 2 } }, { a: {} } ] 138 | , t = trans(o).sort('a.b', [add, 1]).value(); 139 | assert.deepEqual(t, [ 140 | { a: { b: 0 } }, { a: {} }, { a: {} }, { a: { b: 2 } }, { a: { b: 5 } } 141 | ]); 142 | }); 143 | 144 | it('should use a stable sort 1', function () { 145 | var o = [ { a: 1, b: 1 }, { a: 0, b: 2 }, { a: 0, b: 3 }, { a: 1, b: 4 }, { a: 2, b: 5 }, { a: 3, b: 6 } ] 146 | , t = trans(o).sort('a').value(); 147 | assert.deepEqual(t, [ 148 | { a: 0, b: 2 } 149 | , { a: 0, b: 3 } 150 | , { a: 1, b: 1 } 151 | , { a: 1, b: 4 } 152 | , { a: 2, b: 5 } 153 | , { a: 3, b: 6 } 154 | ]); 155 | }); 156 | 157 | it('should use a stable sort 2', function () { 158 | var o = [ { a: 1, b: 1 }, { a: 0, b: 2 }, { a: 0, b: 3 }, { a: 1, b: 4 }, { a: 2, b: 5 }, { a: 3, b: 6 } ] 159 | , t = trans(o).sort('a:descending').value(); 160 | assert.deepEqual(t, [ 161 | { a: 3, b: 6 } 162 | , { a: 2, b: 5 } 163 | , { a: 1, b: 1 } 164 | , { a: 1, b: 4 } 165 | , { a: 0, b: 2 } 166 | , { a: 0, b: 3 } 167 | ]); 168 | }); 169 | 170 | it('should throw if the sort target is not an array', function () { 171 | assert.throws(function () { 172 | trans({ a: 1 }).sort('a'); 173 | }, /the sort target is not an array/i); 174 | }); 175 | }); 176 | 177 | describe('sortf', function () { 178 | it('should sort the target array', function () { 179 | var o = { 180 | a: { b: 1 } 181 | , c: { d: [ 1, 1, 5, 3, 3, 4, 0, ] } 182 | } 183 | , t = trans(o).sortf('c.d', null, [mod, 3]).value(); 184 | assert.deepEqual(t, { 185 | a: { b: 1 } 186 | , c: { d: [ 3, 3, 0, 1, 1, 4, 5, ] } 187 | }); 188 | }); 189 | 190 | it('should sort the target array by the given field', function () { 191 | var o = { a: { b: [ { c: 1 }, { c: 7 }, { c: 3 }, { c: 2 } ] } } 192 | , t = trans(o).sortf('a.b', 'c').value(); 193 | assert.deepEqual(t, { 194 | a: { b: [ { c: 1 }, { c: 2 }, { c: 3 }, { c: 7 } ] } 195 | }); 196 | }); 197 | 198 | it('should sort the target array by the given field and transformer', function () { 199 | var o = { a: { b: [ { c: [ 1 ] }, { c: [] }, { c: [ 3, 4, 5 ] }, { c: [ 2 ] } ] } } 200 | , t = trans(o).sortf('a.b', 'c', 'length').value(); 201 | assert.deepEqual(t, { 202 | a: { b: [ { c: [] }, { c: [ 1 ] }, { c: [ 2 ] }, { c: [ 3, 4, 5 ] } ] } 203 | }); 204 | }); 205 | 206 | it('should sort all targets', function () { 207 | var o = [ { a: [ 1, 4, 2, 1 ] }, { a: [ 2, 2, 1, 4 ] }, { a: [ 3, 1, 2 ] } ] 208 | , t = trans(o).sortf('a').value(); 209 | assert.deepEqual(t, [ 210 | { a: [ 1, 1, 2, 4 ] }, { a: [ 1, 2, 2, 4 ] }, { a: [ 1, 2, 3 ] } 211 | ]); 212 | }); 213 | }); 214 | 215 | describe('sortff', function () { 216 | it('should sort the target array and set it on the destination field', function () { 217 | var o = { a: [ 1, 2, 1, 1, 3, 2 ] } 218 | , t = trans(o).sortff('a', 'c').value(); 219 | assert.deepEqual(t, { a: [ 1, 2, 1, 1, 3, 2 ], c: [ 1, 1, 1, 2, 2, 3 ] }); 220 | }); 221 | 222 | it('should sort multiple times', function () { 223 | var o = [ 224 | { a: { b: [ { c: 5 }, { c: 2 }, { c: 3 }, { c: 4 } ] } } 225 | , { a: { 226 | b: [ { c: 199 }, { c: 290 }, { c: 112 } ] 227 | , d: [ { c: 'five' }, { c: 'two' }, { c: 'three' }, { c: 'four' } ] 228 | } } 229 | ] 230 | , t = trans(o) 231 | .sortff('a.b', 'a.numeric', 'c') 232 | .sortff('a.d', 'a.alpha', 'c') 233 | .sortff('a.d', 'a.size', 'c', 'length') 234 | .value(); 235 | assert.deepEqual(t, [ 236 | { a: { 237 | b : [ { c: 5 }, { c: 2 }, { c: 3 }, { c: 4 } ] 238 | , numeric : [ { c: 2 }, { c: 3 }, { c: 4 }, { c: 5 } ] 239 | , alpha : null 240 | , size : null 241 | } } 242 | , { a: { 243 | b : [ { c: 199 }, { c: 290 }, { c: 112 } ] 244 | , d : [ { c: 'five' }, { c: 'two' }, { c: 'three' }, { c: 'four' } ] 245 | , numeric : [ { c: 112 }, { c: 199 }, { c: 290 } ] 246 | , alpha : [ { c: 'five' }, { c: 'four' }, { c: 'three' }, { c: 'two' } ] 247 | , size : [ { c: 'two' }, { c: 'five' }, { c: 'four' }, { c: 'three' } ] 248 | } } 249 | ]); 250 | }); 251 | }); 252 | }; 253 | -------------------------------------------------------------------------------- /test/trans/omit-test.js: -------------------------------------------------------------------------------- 1 | module.exports = function (util) { 2 | var trans = util.trans 3 | , assert = util.assert 4 | , square = util.square 5 | , mod = util.mod 6 | , sum = util.sum 7 | , add = util.add; 8 | 9 | describe('omit', function () { 10 | it('should remove the specified fields', function () { 11 | var o = { 12 | a: { 13 | b: 1 14 | , c: 2 15 | , p: { d: 3, e: 4 } 16 | , q: { g: 5, h: 6, f: { k: 8, l: 10, r: 22 } } 17 | } 18 | , m: { n: 11 } 19 | } 20 | , t = trans(o).omit('a.b', 'a.p.e', 'a.q.g', 'a.q.f.r', 'm').value(); 21 | assert.deepEqual(t, { 22 | a: { 23 | c: 2 24 | , p: { d: 3 } 25 | , q: { h: 6, f: { k: 8, l: 10 } } 26 | } 27 | }); 28 | }); 29 | 30 | it('should not remove null fields if not specified', function () { 31 | var o = { a: 1, b: { c: 'a', d: null, e: 2 } } 32 | , t = trans(o).omit('b.c').value(); 33 | assert.deepEqual(t, { a: 1, b: { d: null, e: 2 } }); 34 | }); 35 | 36 | it('should create a deep copy if no fields are specified', function () { 37 | var o = { 38 | a: { 39 | b: 1 40 | , c: 2 41 | , p: { d: 3, e: 4 } 42 | , q: { g: 5, h: 6, f: { k: 8, l: 10, r: 22 } } 43 | , r: [ { s: { t: [ 1 ] } }, { s: { t: [ 2, 3, 4 ] } } ] 44 | } 45 | , m: { n: 11 } 46 | } 47 | , t = trans(o).omit().value(); 48 | assert.deepEqual(t, o); 49 | 50 | delete t.a.r[0].s; 51 | delete t.a.p.q; 52 | t.a.r[1].s.t.push(100); 53 | 54 | assert.deepEqual(o, { 55 | a: { 56 | b: 1 57 | , c: 2 58 | , p: { d: 3, e: 4 } 59 | , q: { g: 5, h: 6, f: { k: 8, l: 10, r: 22 } } 60 | , r: [ { s: { t: [ 1 ] } }, { s: { t: [ 2, 3, 4 ] } } ] 61 | } 62 | , m: { n: 11 } 63 | }); 64 | }); 65 | 66 | it('should handle circular references', function () { 67 | var o = { a: { b: [ { c: 1, d: 2 }, { c: 3, d: 4 } ] } } 68 | , t = null; 69 | 70 | o = trans(o).mapf('a.b.c', function () { return o; }).value(); 71 | o.e = o; 72 | t = trans(o).omit().value(); 73 | }); 74 | 75 | it('should not modify the original object', function () { 76 | var o = { 77 | a: { 78 | b: 1 79 | , c: 2 80 | , p: { d: 3, e: 4 } 81 | , q: { g: 5, h: 6, f: { k: 8, l: 10, r: 22 } } 82 | , r: [ { s: { t: [ 1 ] } }, { s: { t: [ 2, 3, 4 ] } } ] 83 | } 84 | , m: { n: 11 } 85 | } 86 | , t = trans(o).omit('a.b', 'a.p.d', 'a.r.s.t').value(); 87 | assert.deepEqual(o, { 88 | a: { 89 | b: 1 90 | , c: 2 91 | , p: { d: 3, e: 4 } 92 | , q: { g: 5, h: 6, f: { k: 8, l: 10, r: 22 } } 93 | , r: [ { s: { t: [ 1 ] } }, { s: { t: [ 2, 3, 4 ] } } ] 94 | } 95 | , m: { n: 11 } 96 | }); 97 | }); 98 | 99 | it('should remove the specified fields on all objects in an array', function () { 100 | var o = [ 101 | { a: { b: 'a', c: 'b' }, d: { e: 1, f: 2 } } 102 | , { a: { b: 'b', c: 'e' }, d: { e: 2, f: 3 } } 103 | , { a: { b: 'c', c: 'f' }, d: { e: 3, f: 4 } } 104 | , { a: { b: 'd', c: 'g' }, d: { e: 4, f: 5 } } 105 | ] 106 | , t = trans(o).omit('a', 'd.f').value(); 107 | assert.deepEqual(t, [ 108 | { d: { e: 1 } } 109 | , { d: { e: 2 } } 110 | , { d: { e: 3 } } 111 | , { d: { e: 4 } } 112 | ]); 113 | }); 114 | 115 | it('should handle missing keys', function () { 116 | var o = [ { a: { b: 1, c: 2 } }, { a: { c: 3 } }, {} ] 117 | , t = trans(o).omit('a.c').value(); 118 | assert.deepEqual(t, [ { a: { b: 1 } }, { a: {} }, {} ]); 119 | }); 120 | 121 | it('should handle functions', function () { 122 | var o = { a: { b: 1, c: function () { return 1; } }, e: 2, f: 3 } 123 | , t = trans(o).omit('a.b', 'e').value(); 124 | assert.strictEqual( 125 | util.stringify(t) 126 | , util.stringify({ a: { c: function () { return 1; } }, f: 3 })); 127 | }); 128 | 129 | it('should work with nested arrays 1', function () { 130 | var o = [ 131 | { a: { b: [ { c: 10, d: 10 }, { c: 20, d: 20 } ], d: 'a', e: [ { f: 2 } ] } } 132 | , { a: { b: [ { c: 11, d: 11 }, { c: 12, d: 12 } ], d: 'b', e: [ { f: 2 } ] } } 133 | , { a: { b: [ { c: 21, d: 21 }, { c: 22, d: 22 } ], d: 'c', e: [ { f: 2 } ] } } 134 | ] 135 | , t = trans(o).omit('a.b.c', 'a.e').value(); 136 | assert.deepEqual(t, [ 137 | { a: { b: [ { d: 10 }, { d: 20 } ], d: 'a' } } 138 | , { a: { b: [ { d: 11 }, { d: 12 } ], d: 'b' } } 139 | , { a: { b: [ { d: 21 }, { d: 22 } ], d: 'c' } } 140 | ]); 141 | }); 142 | 143 | it('should handle primitives', function () { 144 | var o = { a: { b: 1 } } 145 | , t = trans(o).omit('a.b.c').value(); 146 | assert.deepEqual(t, o); 147 | }); 148 | 149 | it('should work with nested arrays 2', function () { 150 | var o = [ [ 151 | { a: [ 152 | { b: 1, c: [ 153 | { d: 1, e: 2, f: { g: 1, h: 2 } }, { d: 1, e: 2, f: { g: 1, h: 2 } } ] 154 | } 155 | , { b: 2, c: [ 156 | { d: 2, e: 3, f: { g: 2, h: 3 } }, { d: 2, e: 3, f: { g: 2, h: 3 } } ] 157 | } 158 | , { b: 2, c: [ 159 | { d: 2, e: 3, f: { g: 2, h: 3 } }, { d: 2, e: 3, f: { g: 2, h: 3 } } ] 160 | } ] 161 | , b: 4 162 | } 163 | , { a: [ 164 | { b: 1, c: [ 165 | { d: 1, e: 2, f: { g: 1, h: 2 } }, { d: 1, e: 2, f: { g: 1, h: 2 } } ] 166 | } 167 | , { b: 2, c: [ 168 | { d: 2, e: 3, f: { g: 2, h: 3 } }, { d: 2, e: 3, f: { g: 2, h: 3 } } ] 169 | } 170 | , { b: 2, c: [ 171 | { d: 2, e: 3, f: { g: 2, h: 3 } }, { d: 2, e: 3, f: { g: 2, h: 3 } } ] 172 | } ] 173 | , b: 4 174 | }] 175 | , [ 176 | { a: [ 177 | { b: 1, c: [ 178 | { d: 1, e: 2, f: { g: 1, h: 2 } }, { d: 1, e: 2, f: { g: 1, h: 2 } } ] 179 | } 180 | , { b: 2, c: [ 181 | { d: 2, e: 3, f: { g: 2, h: 3 } }, { d: 2, e: 3, f: { g: 2, h: 3 } } ] 182 | } 183 | , { b: 2, c: [ 184 | { d: 2, e: 3, f: { g: 2, h: 3 } }, { d: 2, e: 3, f: { g: 2, h: 3 } } ] 185 | } ] 186 | , b: 4 187 | } 188 | , { a: [ 189 | { b: 1, c: [ 190 | { d: 1, e: 2, f: { g: 1, h: 2 } }, { d: 1, e: 2, f: { g: 1, h: 2 } } ] 191 | } 192 | , { b: 2, c: [ 193 | { d: 2, e: 3, f: { g: 2, h: 3 } }, { d: 2, e: 3, f: { g: 2, h: 3 } } ] 194 | } 195 | , { b: 2, c: [ 196 | { d: 2, e: 3, f: { g: 2, h: 3 } }, { d: 2, e: 3, f: { g: 2, h: 3 } } ] 197 | } ] 198 | , b: 4 199 | }]] 200 | , t = trans(o).omit('b', 'a.b', 'a.c.d', 'a.c.f.g').value(); 201 | assert.deepEqual(t, [ 202 | [ 203 | { a: [ 204 | { c: [ { e: 2, f: { h: 2 } }, { e: 2, f: { h: 2 } } ] } 205 | , { c: [ { e: 3, f: { h: 3 } }, { e: 3, f: { h: 3 } } ] } 206 | , { c: [ { e: 3, f: { h: 3 } }, { e: 3, f: { h: 3 } } ] } ] 207 | } 208 | , { a: [ 209 | { c: [ { e: 2, f: { h: 2 } }, { e: 2, f: { h: 2 } } ] } 210 | , { c: [ { e: 3, f: { h: 3 } }, { e: 3, f: { h: 3 } } ] } 211 | , { c: [ { e: 3, f: { h: 3 } }, { e: 3, f: { h: 3 } } ] } ] 212 | } 213 | ] 214 | , [ 215 | { a: [ 216 | { c: [ { e: 2, f: { h: 2 } }, { e: 2, f: { h: 2 } } ] } 217 | , { c: [ { e: 3, f: { h: 3 } }, { e: 3, f: { h: 3 } } ] } 218 | , { c: [ { e: 3, f: { h: 3 } }, { e: 3, f: { h: 3 } } ] } ] 219 | } 220 | , { a: [ 221 | { c: [ { e: 2, f: { h: 2 } }, { e: 2, f: { h: 2 } } ] } 222 | , { c: [ { e: 3, f: { h: 3 } }, { e: 3, f: { h: 3 } } ] } 223 | , { c: [ { e: 3, f: { h: 3 } }, { e: 3, f: { h: 3 } } ] } ] 224 | } 225 | ] ]); 226 | }); 227 | 228 | it('should clone dates', function () { 229 | var date1 = new Date() 230 | , date2 = new Date() 231 | , o = { a: { b: 1, c: date1 }, e: 2, f: date2 } 232 | , t = trans(o).omit('a.b', 'e').value(); 233 | assert.strictEqual( 234 | util.stringify(t) 235 | , util.stringify({ a: { c: date1 }, f: date2 })); 236 | }); 237 | 238 | }); 239 | 240 | describe('omitf', function () { 241 | it('should apply omit at the given field 1', function () { 242 | var o = { a: 1, b: { c: [ { d: 1, e: 2 }, { d: 3, e: 4 } ], f: 'b' }, g: 'c' } 243 | , t = trans(o).omitf('b.c', 'e').value(); 244 | assert.deepEqual(t, { a: 1, b: { c: [ { d: 1 }, { d: 3 } ], f: 'b' }, g: 'c' }); 245 | }); 246 | 247 | it('should apply omit at the given field 2', function () { 248 | var o = { a: 1, b: { c: [ { d: 1, e: 2 }, { d: 3, e: 4 } ], f: 'b' }, g: 'c' } 249 | , t = trans(o).omitf('b', 'c.e', 'f').value(); 250 | assert.deepEqual(t, { a: 1, b: { c: [ { d: 1 }, { d: 3 } ] }, g: 'c' }); 251 | }); 252 | }); 253 | 254 | describe('omitff', function () { 255 | it('should apply omit on the target and set it on the destination', function () { 256 | var o = { a: 1, b: { c: [ { d: 1, e: 2 }, { d: 3, e: 4 } ], f: 'b' }, g: 'c' } 257 | , t = trans(o).omitff('b.c', 'b.p', 'e').value(); 258 | assert.deepEqual(t, { 259 | a: 1 260 | , b: { 261 | c: [ { d: 1, e: 2 }, { d: 3, e: 4 } ] 262 | , p: [ { d: 1 }, { d: 3 } ] 263 | , f: 'b' 264 | } 265 | , g: 'c' 266 | }); 267 | }); 268 | }); 269 | }; 270 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | var slice = Array.prototype.slice 2 | , concat = Array.prototype.concat 3 | , all = Array.prototype.every 4 | , any = Array.prototype.some 5 | , Context = require('./context').Context 6 | , Trav = require('./trav').Trav 7 | , util = require('./util') 8 | , opath = require('./opath') 9 | , ocopy = require('./ocopy') 10 | , osort = require('./osort') 11 | , ensure = util.ensure 12 | , fail = util.fail 13 | , isArray = util.isArray 14 | , isFunction = util.isFunction 15 | , isUndefined = util.isUndefined 16 | , isObject = util.isObject 17 | , isString = util.isString 18 | , toArray = util.toArray; 19 | 20 | function Trans (obj, options, context) { 21 | this._state_ = obj; 22 | this._context_ = new Context(context); 23 | this._trav_ = new Trav(this._context_); 24 | } 25 | 26 | function flatten (list, shallow) { 27 | if (!isArray(list)) { return list; } 28 | if (shallow && all.call(list, isArray)) { return concat.apply([], list); } 29 | 30 | var i = 0 31 | , len = list.length 32 | , out = [] 33 | , item = null; 34 | 35 | for (i = 0; i < len; i++) { 36 | item = list[i]; 37 | if (!isArray(item)) { 38 | out.push(item); 39 | } else if (shallow) { 40 | out = out.concat(item); 41 | } else { 42 | out = out.concat(flatten(item, false)); 43 | } 44 | } 45 | 46 | return out; 47 | } 48 | 49 | function objToArray (obj, k, v) { 50 | return Object.keys(obj).map(function (key) { 51 | var pair = {}; 52 | pair[k] = key; 53 | pair[v] = obj[key]; 54 | return pair; 55 | }); 56 | } 57 | 58 | module.exports = function (obj) { 59 | return new Trans(obj); 60 | }; 61 | 62 | Trans.prototype.value = function() { 63 | return this._state_; 64 | }; 65 | 66 | Trans.prototype.count = function () { 67 | return isArray(this._state_) ? this._state_.length : 1; 68 | }; 69 | 70 | Trans.prototype.get = function(cb) { 71 | if (isFunction(cb)) { 72 | cb(this._state_); 73 | } 74 | return this; 75 | }; 76 | 77 | Trans.prototype._create_ = function(obj) { 78 | return new Trans(obj, null, this._context_); 79 | }; 80 | 81 | Trans.prototype.skip = function(n) { 82 | return this.map(['slice', n]); 83 | }; 84 | 85 | Trans.prototype.take = function(n) { 86 | return this.map(['slice', 0, n]); 87 | }; 88 | 89 | Trans.prototype.first = function() { 90 | return this.map(['slice', 0, 1], 'shift', [util.default, null]); 91 | }; 92 | 93 | Trans.prototype.last = function() { 94 | return this.map(['slice', -1], 'pop', [util.default, null]); 95 | }; 96 | 97 | Trans.prototype.uniq = function() { 98 | var seen = {} 99 | , args = toArray(arguments); 100 | 101 | if (args.length === 0) { 102 | args.push(null); 103 | } 104 | 105 | args.push(function (value) { 106 | var key = value + typeof value; 107 | 108 | if (value === null || isUndefined(value)) { 109 | return true; 110 | } else if (seen[key]) { 111 | return false; 112 | } else { 113 | seen[key] = true; 114 | return true; 115 | } 116 | }); 117 | 118 | return this.filter.apply(this, args); 119 | }; 120 | 121 | Trans.prototype.copy = function() { 122 | return this.omit(); 123 | }; 124 | 125 | Trans.prototype.remove = function() { 126 | var fields = toArray(arguments); 127 | if (fields.length > 0) { 128 | ocopy.removeFields(this._state_, fields); 129 | } 130 | return this; 131 | }; 132 | 133 | Trans.prototype.omit = function() { 134 | this._state_ = ocopy.copyBlack(this._state_, toArray(arguments)); 135 | return this; 136 | }; 137 | 138 | Trans.prototype.pick = function() { 139 | var fields = toArray(arguments); 140 | if (fields.length > 0) { 141 | this._state_ = ocopy.copyWhite(this._state_, fields); 142 | } 143 | return this; 144 | }; 145 | 146 | Trans.prototype.sort = function() { 147 | var args = toArray(arguments) 148 | , self = this 149 | , state = this._state_ 150 | , trav = this._trav_ 151 | , field = opath.parse(args[0]) 152 | , desc = /desc|dsc/i.test(field.meta[0]) 153 | , funs = args.slice(1) 154 | , last = funs[funs.length - 1] 155 | , comp = null; 156 | 157 | if (!state) { return this; } 158 | 159 | ensure(isArray(state), 'The sort target is not an array'); 160 | 161 | if (last && isFunction(last) && last.length === 2) { 162 | comp = osort.createComparer(last); 163 | funs.pop(); 164 | } 165 | if (comp === null) { 166 | comp = osort.createComparer(null, desc); 167 | } 168 | 169 | state = trav.map(state, function (val, index) { 170 | var criteria = val, t = null; 171 | 172 | if (field.exists) { 173 | criteria = trav.walkValue(val, field.path, false); 174 | } 175 | if (funs.length > 0) { 176 | t = self._create_(criteria); 177 | criteria = t.map.apply(t, funs).value(); 178 | } 179 | 180 | return { 181 | index : index 182 | , value : val 183 | , criteria : criteria 184 | }; 185 | }); 186 | 187 | state.sort(comp); 188 | 189 | this._state_ = state.map(function (n) { return n.value; }); 190 | 191 | return this; 192 | }; 193 | 194 | Trans.prototype.flatten = function(deep) { 195 | return this.map([flatten, !deep]); 196 | }; 197 | 198 | Trans.prototype.default = function() { 199 | var args = toArray(arguments) 200 | , key = null 201 | , val = null 202 | , i = 0 203 | , len = args.length 204 | , set = function (obj, val) { 205 | return isUndefined(obj) || obj === null ? val : obj; 206 | }; 207 | 208 | ensure(len % 2 === 0, 'An even number of arguments was expected'); 209 | 210 | for (i = 0; i < len - 1; i += 2) { 211 | key = args[i]; 212 | val = args[i + 1]; 213 | this.mapf(key, [set, val]); 214 | } 215 | 216 | return this; 217 | }; 218 | 219 | Trans.prototype.array = function(keyName, valName) { 220 | var trav = this._trav_ 221 | , state = this._state_ 222 | , result = []; 223 | 224 | keyName = keyName || 'key'; 225 | valName = valName || 'value'; 226 | 227 | if (isUndefined(state) || state === null) { 228 | result = []; 229 | } else if (isArray(state)) { 230 | result = trav.walk(state, null, false, function (node) { 231 | var obj = node.parent; 232 | ensure(isObject(obj), 'Object expected but got', obj); 233 | return objToArray(obj, keyName, valName); 234 | }); 235 | } else if (isObject(state)) { 236 | result = objToArray(state, keyName, valName); 237 | } else { 238 | fail('Object expected but got', state); 239 | } 240 | 241 | this._state_ = result; 242 | return this; 243 | }; 244 | 245 | Trans.prototype.object = function() { 246 | var args = toArray(arguments) 247 | , state = this._state_ 248 | , self = this 249 | , trav = this._trav_ 250 | , keyField = opath.parse(args[0]) 251 | , valField = opath.parse(args[1]) 252 | , funs = args.slice(2) 253 | , res = {}; 254 | 255 | var addPair = function (key, val) { 256 | var t = null; 257 | 258 | if (funs.length > 0) { 259 | t = self._create_(key); 260 | key = t.map.apply(t, funs).value(); 261 | } 262 | 263 | key = isUndefined(key) ? null : key; 264 | res[key] = val; 265 | }; 266 | 267 | if (!state) { return this; } 268 | 269 | ensure(isArray(state), 'The object target is not an array'); 270 | 271 | trav.map(state, function (obj) { 272 | var key = obj 273 | , val = obj; 274 | 275 | if (keyField.exists) { 276 | key = trav.walkValue(obj, keyField.path, false); 277 | } 278 | if (valField.exists) { 279 | val = trav.walkValue(obj, valField.path, false); 280 | } 281 | 282 | if (keyField.iter) { 283 | trav.map(key, function (k) { addPair(k, val); }); 284 | } else { 285 | addPair(key, val); 286 | } 287 | }); 288 | 289 | this._state_ = res; 290 | return this; 291 | }; 292 | 293 | Trans.prototype.filter = function() { 294 | var args = toArray(arguments) 295 | , self = this 296 | , trav = this._trav_ 297 | , field = opath.parse(args[0]) 298 | , invert = field.meta[0] === 'invert' 299 | , funs = args.slice(1); 300 | 301 | if (!this._state_) { return this; } 302 | 303 | ensure(isArray(this._state_), 'The filter target is not an array'); 304 | 305 | this._state_ = trav.filter(this._state_, function (obj) { 306 | var val = obj 307 | , bval = null 308 | , t = null; 309 | 310 | if (field.exists) { 311 | val = trav.walkValue(val, field.path, false); 312 | } 313 | 314 | if (funs.length > 0) { 315 | t = self._create_(val); 316 | val = t.map.apply(t, funs).value(); 317 | } 318 | 319 | bval = Boolean(val); 320 | return invert ? !bval : bval; 321 | }); 322 | 323 | return this; 324 | }; 325 | 326 | Trans.prototype.group = function() { 327 | var args = toArray(arguments) 328 | , self = this 329 | , trav = this._trav_ 330 | , state = this._state_ 331 | , keyField = opath.parse(args[0]) 332 | , valField = opath.parse(args[1]) 333 | , keyName = keyField.meta[0] || 'key' 334 | , valName = keyField.meta[1] || 'value' 335 | , funs = args.slice(2) 336 | , map = {} 337 | , res = []; 338 | 339 | var addPair = function (key, val, seen) { 340 | var t = null, obj = null; 341 | 342 | if (funs.length > 0) { 343 | t = self._create_(key); 344 | key = t.map.apply(t, funs).value(); 345 | } 346 | 347 | key = isUndefined(key) ? null : key; 348 | 349 | if (!seen[key]) { 350 | seen[key] = true; 351 | 352 | if (!map[key]) { 353 | map[key] = []; 354 | obj = {}; 355 | obj[keyName] = key; 356 | obj[valName] = map[key]; 357 | res.push(obj); 358 | } 359 | 360 | map[key].push(val); 361 | } 362 | }; 363 | 364 | if (!state) { return this; } 365 | 366 | ensure(isArray(state), 'The group target is not an array'); 367 | 368 | trav.map(state, function (val) { 369 | var key = val 370 | , seen = {}; 371 | 372 | if (keyField.exists) { 373 | key = trav.walkValue(val, keyField.path, false); 374 | } 375 | if (valField.exists) { 376 | val = trav.walkValue(val, valField.path, false); 377 | } 378 | 379 | if (keyField.iter) { 380 | trav.map(key, function (k) { addPair(k, val, seen); }); 381 | } else { 382 | addPair(key, val, seen); 383 | } 384 | }); 385 | 386 | this._state_ = res; 387 | return this; 388 | }; 389 | 390 | Trans.prototype.pluck = function() { 391 | var args = toArray(arguments); 392 | args.splice(1, 0, null); 393 | return this.mapff.apply(this, args); 394 | }; 395 | 396 | Trans.prototype.mapff = function() { 397 | var args = toArray(arguments) 398 | , self = this 399 | , funs = args.slice(2) 400 | , srcPath = opath.parse(args[0]) 401 | , dstPath = opath.parse(args[1]) 402 | , paths = opath.root(srcPath, dstPath) 403 | , trav = self._trav_ 404 | , result = null 405 | , walk = function (obj, fn) { 406 | return trav.walk(obj, paths.root.path, false, function (node) { 407 | if (paths.dst.exists) { 408 | return trav.walk(node.getValueOrParent(), null, false, fn); 409 | } else { 410 | return fn(node); 411 | } 412 | }); 413 | }; 414 | 415 | if (srcPath.iter) { funs.unshift(opath.ITER); } 416 | 417 | if (paths.root.nil && paths.src.nil && paths.dst.nil) { 418 | this.map.apply(this, funs); 419 | } else { 420 | result = walk(self._state_, function (node) { 421 | var srcField = opath.join(node.field, paths.src) 422 | , dstField = opath.join(node.field, paths.dst) 423 | , t = null 424 | , val = node.getValueOrParent(); 425 | 426 | if (srcField.exists) { 427 | val = trav.walk(node.parent, srcField.path, false, function (srcNode) { 428 | return srcNode.getValueOrParent(); 429 | }); 430 | } 431 | 432 | t = self._create_(val); 433 | val = t.map.apply(t, funs).value(); 434 | 435 | if (dstField.exists) { 436 | trav.walk(node.parent, dstField.path, true, function (dstNode) { 437 | dstNode.setValue(val); 438 | }); 439 | } 440 | 441 | return val; 442 | }); 443 | 444 | if (dstPath.nil) { 445 | this._state_ = result; 446 | } 447 | } 448 | 449 | return this; 450 | }; 451 | 452 | Trans.prototype.mapf = function() { 453 | var args = toArray(arguments) 454 | , self = this 455 | , field = opath.parse(args[0]) 456 | , funs = args.slice(1); 457 | 458 | if (field.iter) { 459 | funs.unshift(opath.ITER); 460 | } 461 | if (field.nil) { 462 | return this.map.apply(this, funs); 463 | } 464 | 465 | self._trav_.walk(self._state_, field.path, true, function (node) { 466 | var t = null, val = node.getValue(); 467 | 468 | t = self._create_(val); 469 | t.map.apply(t, funs); 470 | node.setValue(t.value()); 471 | }); 472 | 473 | return this; 474 | }; 475 | 476 | Trans.prototype.map = function() { 477 | this._state_ = this._trav_.transform(this._state_, toArray(arguments)); 478 | return this; 479 | }; 480 | 481 | [ 482 | 'group' 483 | , 'flatten' 484 | , 'filter' 485 | , 'sort' 486 | , 'pick' 487 | , 'omit' 488 | , 'uniq' 489 | , 'take' 490 | , 'skip' 491 | , 'first' 492 | , 'last' 493 | , 'copy' 494 | , 'pluck' 495 | , 'object' 496 | , 'array' 497 | , 'default' ].forEach(function (name) { 498 | Trans.prototype[name + 'f'] = function () { 499 | var args = toArray(arguments) 500 | , self = this 501 | , field = args[0] 502 | , rest = args.slice(1); 503 | 504 | return this.mapf(field, function (target) { 505 | var t = self._create_(target); 506 | return t[name].apply(t, rest).value(); 507 | }); 508 | }; 509 | 510 | Trans.prototype[name + 'ff'] = function () { 511 | var args = toArray(arguments) 512 | , self = this 513 | , src = args[0] 514 | , dst = args[1] 515 | , rest = args.slice(2); 516 | 517 | return this.mapff(src, dst, function (target) { 518 | var t = self._create_(target); 519 | return t[name].apply(t, rest).value(); 520 | }); 521 | }; 522 | }); 523 | -------------------------------------------------------------------------------- /test/trans/group-test.js: -------------------------------------------------------------------------------- 1 | module.exports = function (util) { 2 | var trans = util.trans 3 | , assert = util.assert 4 | , square = util.square 5 | , mod = util.mod 6 | , sum = util.sum 7 | , add = util.add; 8 | 9 | describe('group', function () { 10 | it('should group an array by the given key - object', function () { 11 | var o = [ 12 | { a: { b: 1 } } 13 | , { a: { b: 2 } } 14 | , { a: { b: 2 } } 15 | , { a: { b: 3 } } 16 | , { a: { b: 2 } } 17 | , { a: {} } ] 18 | , t = trans(o).group('a.b').value(); 19 | assert.deepEqual(t, [ 20 | { key: 1, value: [ { a: { b: 1 } } ] } 21 | , { key: 2, value: [ { a: { b: 2 } }, { a: { b: 2 } }, { a: { b: 2 } } ] } 22 | , { key: 3, value: [ { a: { b: 3 } } ] } 23 | , { key: null, value: [ { a: {} } ] } 24 | ]); 25 | }); 26 | 27 | it('should group an array of primitives 1', function () { 28 | var o = [ 1, 1, 3, 3, 2, 1 ] 29 | , t = trans(o).group(null).value(); 30 | assert.deepEqual(t, [ 31 | { key: 1, value: [ 1, 1, 1 ] } 32 | , { key: 3, value: [ 3, 3 ] } 33 | , { key: 2, value: [ 2 ] } 34 | ]); 35 | }); 36 | 37 | it('should group an array of primitives 2', function () { 38 | var o = [ 1, 1, 3, 3, 2, 1 ] 39 | , t = trans(o).group(null, null, square).value(); 40 | assert.deepEqual(t, [ 41 | { key: 1, value: [ 1, 1, 1 ] } 42 | , { key: 9, value: [ 3, 3 ] } 43 | , { key: 4, value: [ 2 ] } 44 | ]); 45 | }); 46 | 47 | it('should group an array of strings', function () { 48 | var o = [ 'abc', 'abc', 'def', 'foo', 'bar', 'foo', 'abc' ] 49 | , t = trans(o).group().value(); 50 | assert.deepEqual(t, [ 51 | { key: 'abc', value: [ 'abc', 'abc', 'abc' ] } 52 | , { key: 'def', value: [ 'def' ] } 53 | , { key: 'foo', value: [ 'foo', 'foo' ] } 54 | , { key: 'bar', value: [ 'bar' ] } 55 | ]); 56 | }); 57 | 58 | it('should make available the array index to the key transformer', function () { 59 | var o = [ 'abc', 'abc', 'def', 'foo', 'bar', 'foo', 'abc' ] 60 | , t = trans(o).group(null, null, function (x) { return x + this.getIndex(); }).value(); 61 | assert.deepEqual(t, [ 62 | { key: 'abc0', value: [ 'abc' ] } 63 | , { key: 'abc1', value: [ 'abc' ] } 64 | , { key: 'def2', value: [ 'def' ] } 65 | , { key: 'foo3', value: [ 'foo' ] } 66 | , { key: 'bar4', value: [ 'bar' ] } 67 | , { key: 'foo5', value: [ 'foo' ] } 68 | , { key: 'abc6', value: [ 'abc' ] } 69 | ]); 70 | }); 71 | 72 | it('should be able to specify the group key and value names', function () { 73 | var o = [ 1, 1, 2, 2, 4 ] 74 | , t = trans(o).group(':num:data').value(); 75 | assert.deepEqual(t, [ 76 | { num: 1, data: [ 1, 1 ] } 77 | , { num: 2, data: [ 2, 2 ] } 78 | , { num: 4, data: [ 4 ] } 79 | ]); 80 | }); 81 | 82 | it('should apply key transformations before grouping', function () { 83 | var o = [ { a: 'foo' }, { a: 'bar' }, { a: 'baz' } ] 84 | , t = trans(o).group('a:letter:obj', null, ['charAt', 1], 'toUpperCase').value(); 85 | assert.deepEqual(t, [ 86 | { letter: 'O', obj: [ { a: 'foo' } ] } 87 | , { letter: 'A', obj: [ { a: 'bar' }, { a: 'baz' } ] } 88 | ]); 89 | }); 90 | 91 | it('should work with nested arrays 1', function () { 92 | var o = [ 93 | { a: [ { b: 1 }, { b: 2 } ] } 94 | , { a: [ { b: 2 }, { b: 2 } ] } 95 | , { a: [ { b: 1 }, { b: 3 } ] } 96 | , { a: [ { b: 3 }, { b: 4 } ] } ] 97 | , t = trans(o).group('a.b', null, sum).value(); 98 | assert.deepEqual(t, [ 99 | { key: 3, value: [ { a: [ { b: 1 }, { b: 2 } ] } ] } 100 | , { key: 4, value: [ { a: [ { b: 2 }, { b: 2 } ] }, { a: [ { b: 1 }, { b: 3 } ] }] } 101 | , { key: 7, value: [ { a: [ { b: 3 }, { b: 4 } ] } ] } 102 | ]); 103 | }); 104 | 105 | it('should work with nested arrays 2', function () { 106 | var o = [ 107 | [ { a: { b: 1 } }, { a: { b: 2 } } ] 108 | , [ { a: { b: 3 } } ] 109 | , [ { a: { b: 4 } }, { a: { b: 5 } } ] ] 110 | , t = trans(o).group('a.b', null, sum).value(); 111 | assert.deepEqual(t, [ 112 | { key: 3, value: [ 113 | [ { a: { b: 1 } }, { a: { b: 2 } } ], [ { a: { b: 3 } } ] ] 114 | } 115 | , { key: 9, value: [ 116 | [ { a: { b: 4 } }, { a: { b: 5 } } ] ] 117 | } 118 | ]); 119 | }); 120 | 121 | it('should group an array by the given key - array2', function () { 122 | var o = [ { a: [ 1, 2 ] }, { a: [ 1 ] }, { a: [ 3 ] } ] 123 | , t = trans(o).group('a', null, sum).value(); 124 | assert.deepEqual(t, [ 125 | { key: 3, value: [ { a: [ 1, 2 ] }, { a: [ 3 ] } ] } 126 | , { key: 1, value: [ { a: [ 1 ] } ] } 127 | ]); 128 | }); 129 | 130 | it('should group an array by the given key - array3', function () { 131 | var o = [ { a: [ 1, 2 ] }, { a: [ 1 ] }, { a: [ 2 ] } ] 132 | , t = trans(o).group('a', null, 'length').value(); 133 | assert.deepEqual(t, [ 134 | { key: 2, value: [ { a: [ 1, 2 ] } ] } 135 | , { key: 1, value: [ { a: [ 1 ] }, { a: [ 2 ] } ] } 136 | ]); 137 | }); 138 | 139 | it('should pass missing keys as null through transformers', function () { 140 | var o = [ { a: { b: 1 } }, { a: { b: 2 } }, { a: {} }, { a: { b: 1 } } ] 141 | , t = trans(o).group('a.b', null, [add, 1]).value(); 142 | assert.deepEqual(t, [ 143 | { key: 2, value: [ { a: { b: 1 } }, { a: { b: 1 } } ] } 144 | , { key: 3, value: [ { a: { b: 2 } } ] } 145 | , { key: 1, value: [ { a: {} } ] } 146 | ]); 147 | }); 148 | 149 | it('should group by the items in an array', function () { 150 | var o = [ 151 | { a: 1, c: { b: [ 'a', 'b' ] } } 152 | , { a: 2, c: { b: [ 'a' ] } } 153 | , { a: 3, c: { b: [ 'b' ] } } 154 | , { a: 4, c: { b: [ 'c' ] } } 155 | , { a: 5, c: { b: [ 'd', 'e' ] } } ] 156 | , t = trans(o).group('c.b.').value(); 157 | assert.deepEqual(t, [ 158 | { key: 'a', value: [ 159 | { a: 1, c: { b: [ 'a', 'b' ] } } 160 | , { a: 2, c: { b: [ 'a' ] } } ] 161 | } 162 | , { key: 'b', value: [ 163 | { a: 1, c: { b: [ 'a', 'b' ] } } 164 | , { a: 3, c: { b: [ 'b' ] } } ] 165 | } 166 | , { key: 'c', value: [ { a: 4, c: { b: [ 'c' ] } } ] } 167 | , { key: 'd', value: [ { a: 5, c: { b: [ 'd', 'e' ] } } ] } 168 | , { key: 'e', value: [ { a: 5, c: { b: [ 'd', 'e' ] } } ] } 169 | ]); 170 | }); 171 | 172 | it('should make the array index available when grouping by the items in an array', function () { 173 | var o = [ 174 | { a: 1, c: { b: [ 'a', 'b' ] } } 175 | , { a: 2, c: { b: [ 'a' ] } } 176 | , { a: 3, c: { b: [ 'b' ] } } 177 | , { a: 4, c: { b: [ 'c' ] } } 178 | , { a: 5, c: { b: [ 'd', 'e' ] } } ] 179 | , t = trans(o) 180 | .group('c.b.', 'a', function (x) { return x + ':' + this.getIndexes(); }) 181 | .value(); 182 | assert.deepEqual(t, [ 183 | { key: 'a:0,0', value: [ 1 ] } 184 | , { key: 'b:1,0', value: [ 1 ] } 185 | , { key: 'a:0,1', value: [ 2 ] } 186 | , { key: 'b:0,2', value: [ 3 ] } 187 | , { key: 'c:0,3', value: [ 4 ] } 188 | , { key: 'd:0,4', value: [ 5 ] } 189 | , { key: 'e:1,4', value: [ 5 ] } 190 | ]); 191 | }); 192 | 193 | it('should group by the items in an array and apply key transformations', function () { 194 | var o = [ 195 | { a: [ { b: 1 }, { b: 2 } ] } 196 | , { a: [ { b: 2 }, { b: 2 } ] } 197 | , { a: [ { b: 3 }, { b: 2 } ] } ] 198 | ,t = trans(o).group('a.', null, 'b').value(); 199 | assert.deepEqual(t, [ 200 | { key: 1, value: [ { a: [ { b: 1 }, { b: 2 } ] } ] } 201 | , { key: 2, value: [ 202 | { a: [ { b: 1 }, { b: 2 } ] } 203 | , { a: [ { b: 2 }, { b: 2 } ] } 204 | , { a: [ { b: 3 }, { b: 2 } ] } ] 205 | } 206 | , { key: 3, value: [ { a: [ { b: 3 }, { b: 2 } ] } ] } 207 | ]); 208 | }); 209 | 210 | 211 | it('should group by the items in an array with custom names', function () { 212 | var o = [ { a: 1, b: [ 1, 2 ] }, { a: 2, b: [ 1 ] }, { a: 3, b: [ 3 ] } ] 213 | , t = trans(o).group('b.:k:v').value(); 214 | assert.deepEqual(t, [ 215 | { k: 1, v: [ { a: 1, b: [ 1, 2 ] }, { a: 2, b: [ 1 ] } ] } 216 | , { k: 2, v: [ { a: 1, b: [ 1, 2 ] } ] } 217 | , { k: 3, v: [ { a: 3, b: [ 3 ] } ] } 218 | ]); 219 | }); 220 | 221 | it('should be able to specify the group value 1', function () { 222 | var o = [ { a: 1, b: [ 1, 2 ] }, { a: 2, b: [ 1 ] }, { a: 3, b: [ 3 ] } ] 223 | , t = trans(o).group('b.:k:v', 'b').value(); 224 | assert.deepEqual(t, [ 225 | { k: 1, v: [ [ 1, 2 ], [ 1 ] ] } 226 | , { k: 2, v: [ [ 1, 2 ] ] } 227 | , { k: 3, v: [ [ 3 ] ] } 228 | ]); 229 | }); 230 | 231 | it('should be able to specify the group value 2', function () { 232 | var o = [ { a: 1, b: 'A' }, { a: 1, b: 'B' }, { a: 3, b: 'C' } ] 233 | , t = trans(o).group('a:num:letters', 'b').value(); 234 | assert.deepEqual(t, [ 235 | { num: 1, letters: [ 'A', 'B' ] } 236 | , { num: 3, letters: [ 'C' ] } 237 | ]); 238 | }); 239 | 240 | it('should be able to specify the group value 3', function () { 241 | var o = [ 242 | { a: 1, b: { c: 'one' } } 243 | , { a: 2, b: { c: 'two' } }, 244 | , { a: 3, b: { c: 'three' } }, 245 | , { a: 10, b: { c: 'ten' } }, 246 | , { a: 11, b: { c: 'eleven' } }, 247 | , { a: 20, b: { c: 'twenty' } } ] 248 | , t = trans(o).group('a', 'b.c', [mod, 10]).value(); 249 | assert.deepEqual(t, [ 250 | { key: 1, value: [ 'one', 'eleven' ] } 251 | , { key: 2, value: [ 'two' ] } 252 | , { key: 3, value: [ 'three' ] } 253 | , { key: 0, value: [ 'ten', 'twenty' ] } 254 | ]); 255 | }); 256 | 257 | it('should objectify an array of objects', function () { 258 | var o = [ 1, 2, 1, 1, 3, 0, 0 ] 259 | , t = trans(o) 260 | .map('.', function(x) { return { k: x, v: Boolean(x) }; }) 261 | .group('k', 'v') 262 | .value(); 263 | assert.deepEqual(t, [ 264 | { key: 1, value: [ true, true, true ] } 265 | , { key: 2, value: [ true ] } 266 | , { key: 3, value: [ true ] } 267 | , { key: 0, value: [ false, false ] } 268 | ]); 269 | }); 270 | 271 | it('should throw if the group target is not an array', function () { 272 | assert.throws(function() { 273 | trans({ a: 1 }).group('a'); 274 | }, /the group target is not an array/i); 275 | }); 276 | }); 277 | 278 | describe('groupf', function () { 279 | it('should group the object at the given field - object', function () { 280 | var o = { a: { b: [ 1, 1, 3, 3, 1, 2 ] } } 281 | , e = { a: { b: [ 282 | { key: 1, value: [ 1, 1, 1 ] } 283 | , { key: 9, value: [ 3, 3 ] } 284 | , { key: 4, value: [ 2 ] } 285 | ] } } 286 | , t = trans(o).groupf('a.b', null, null, square).value(); 287 | assert.deepEqual(t, e); 288 | }); 289 | 290 | it('should group the object at the given field - array1', function () { 291 | var o = { a: [ { b: [ 1, 1, 2 ] }, { b: [ 3, 3 ] }, { b: [ 1, 2, 3 ] } ] } 292 | , e = { a: [ 293 | { b: [ { key: 1, value: [ 1, 1 ] }, { key: 2, value: [ 2 ] } ] } 294 | , { b: [ { key: 3, value: [ 3, 3 ] } ] } 295 | , { b: [ { key: 1, value: [ 1 ] }, { key: 2, value: [ 2 ] }, { key: 3, value: [ 3 ] } ] } 296 | ] } 297 | , t = trans(o).groupf('a.b').value(); 298 | assert.deepEqual(t, e); 299 | }); 300 | 301 | it('should group the object at the given field - array2', function () { 302 | var o = { a: [ { b: [ 1, 1, 2 ] }, { b: [ 3, 3 ] }, { b: [ 1, 2, 3 ] } ] } 303 | , e = { a: [ 304 | { key: 3, value: [ { b: [ 1, 1, 2 ] }, { b: [ 1, 2, 3 ] } ] } 305 | , { key: 2, value: [ { b: [ 3, 3 ] } ] } 306 | ]} 307 | , t = trans(o).groupf('a', 'b', null, 'length').value(); 308 | assert.deepEqual(t, e); 309 | }); 310 | 311 | it('should handle nested arrays', function () { 312 | var o = [ 313 | { a: [ { b: 1 }, { b: 2 }, { b: 3 } ] } 314 | , { a: [ { b: 3 }, { b: 3 }, { b: 3 } ] } 315 | , { a: [ { b: 4 } ] } 316 | ] 317 | , t = trans(o).groupf('a', 'b:k:v', null, [mod, 2]).value(); 318 | assert.deepEqual(t, [ 319 | { a: [ { k: 1, v: [ { b: 1 }, { b: 3 } ] }, { k: 0, v: [ { b: 2 } ] } ] } 320 | , { a: [ { k: 1, v: [ { b: 3 }, { b: 3 }, { b: 3 } ] } ] } 321 | , { a: [ { k: 0, v: [ { b: 4 } ] } ] } 322 | ]); 323 | }); 324 | }); 325 | 326 | describe('groupff', function () { 327 | it('should group the target value and set it on the destination field', function () { 328 | var o = { a: [ 1, 2, 1, 1 ], b: { c: 1 } } 329 | , t = trans(o).groupff('a', 'b.c', ':k:v', null, [add, 1]).value(); 330 | assert.deepEqual(t, { 331 | a: [ 1, 2, 1, 1 ] 332 | , b: { c: [ { k: 2, v: [ 1, 1, 1 ] }, { k: 3, v: [ 2 ] } ] } 333 | }); 334 | }); 335 | 336 | it('should work with nested arrays', function () { 337 | var o = [ 338 | { a: [ 1, 2, 3 ] } 339 | , { a: [ 11, 12, 13, 14 ] } 340 | , { a: [ 99, 98, 77 ] } ] 341 | , t = trans(o).groupff('a', 'c', null, null, [mod, 2]).value(); 342 | assert.deepEqual(t, [ 343 | { 344 | a: [ 1, 2, 3 ] 345 | , c: [ { key: 1, value: [ 1, 3 ] }, { key: 0, value: [ 2 ] } ] 346 | } 347 | , { 348 | a: [ 11, 12, 13, 14 ] 349 | , c: [ { key: 1, value: [ 11, 13 ] }, { key: 0, value: [ 12, 14 ] } ] 350 | } 351 | , { 352 | a: [ 99, 98, 77 ] 353 | , c: [ { key: 1, value: [ 99, 77 ] }, { key: 0, value: [ 98 ] } ] 354 | } 355 | ]); 356 | }); 357 | }); 358 | }; 359 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ``` 2 | __ 3 | _/ |_____________ ____ ______ 4 | \ __\_ __ \__ \ / \ / ___/ 5 | | | | | \// __ \| | \\___ \ 6 | |__| |__| (____ /___| /____ > 7 | \/ \/ \/ 8 | ``` 9 | 10 | [![Latest Stable Version](https://img.shields.io/npm/v/trans.svg?style=flat-square)](https://www.npmjs.com/package/trans) 11 | [![NPM Downloads](https://img.shields.io/npm/dm/trans.svg?style=flat-square)](https://www.npmjs.com/package/trans) 12 | [![Build Status](https://img.shields.io/travis/gabesoft/trans/master.svg?style=flat-square)](https://travis-ci.org/gabesoft/trans) 13 | 14 | 15 | *The ultimate object transformer* 16 | 17 | ## Install 18 | 19 | ``` 20 | $ npm install trans 21 | ``` 22 | 23 | ## Purpose 24 | 25 | The purpose of trans is to make it super easy to transform complex json objects 26 | 27 | ## Overview 28 | 29 | Trans allows specifying composite field names such as ``a.b.c`` and it does the 30 | right thing even across multiple arrays. 31 | For example, the field above could be used to modify or extract a value from an object 32 | that looks like this 33 | 34 | ``` javascript 35 | { a: { b: { c: 1 } } } 36 | ``` 37 | but also if the object looks like this 38 | ``` javascript 39 | { a: [ { b: { c: 1 } }, { b: { c: 2 } } ] } 40 | ``` 41 | or like this 42 | ``` javascript 43 | [ { a: { b: [ { c: 1 }, { c: 2 } ] } } ] 44 | ``` 45 | 46 | There are three types of transformation methods: 47 | - ``map(*transformers)`` transforms the entire object 48 | - ``mapf(field, *transformers)`` transforms the value of a field 49 | - ``mapff(source, destination, *transformers)`` transforms the value of a field and 50 | sets it onto another field 51 | 52 | The transformers specified as parameters to the transformation methods can be functions, 53 | field names, or even objects (which will be used as hash maps). The functions that are not properties 54 | on the object being transformed are assumed to take that object as the first parameter. But, they can 55 | take additional parameters as well. In those case the function should be specified as an array. 56 | When multiple transformers are specified the result of each one is piped over to the next one. 57 | 58 | Here are a couple of examples which result in an identical outcome: 59 | ``` javascript 60 | trans({ a: [ 1, 2 ] }).mapf('a', 'length', [add, 5], [mul, 10], square); 61 | ``` 62 | ``` javascript 63 | trans({ a: [ 1, 2 ] }).mapf('a', function(obj) { 64 | return square(mul(add(obj.length, 5), 10)); 65 | }); 66 | ``` 67 | The result in both cases is: 68 | ``` javascript 69 | { a: 4900 } 70 | ``` 71 | 72 | ## Quickstart 73 | 74 | Using trans is easy, first wrap the data to be transformed by calling ``trans(data)``, 75 | as below, and then call transformation methods on the wrapper. Multiple transformation 76 | methods can be chained. When done call ``value()`` to get back the raw data that has been transformed. 77 | 78 | Here's a quick taste. Assuming we have an object that looks like this 79 | 80 | ``` javascript 81 | var data = [ 82 | { a: { b: 'fbc' }, c: 1 } 83 | , { a: { b: 'foo' }, c: 3 } 84 | , { a: { b: 'fde' }, c: 2 } 85 | , { a: { b: 'def' }, c: 3 } 86 | , { a: { b: 'ghk' }, c: 4 } ]; 87 | ``` 88 | 89 | We can use trans to group the data array by the first letter capitalized of the ``a.b`` field 90 | and set the group value to ``c``, then sort the value array, and finally sort the 91 | entire result array by the group key as follows 92 | 93 | ``` javascript 94 | var trans = require('trans'); 95 | var result = trans(data) 96 | .group('a.b', 'c', ['charAt', 0], 'toUpperCase') 97 | .sortf('value') 98 | .sort('key') 99 | .value(); 100 | 101 | ``` 102 | 103 | After running the above code ``result`` will have the following value 104 | 105 | ``` javascript 106 | [ { key: 'D', value: [ 3 ] }, { key: 'F', value: [ 1, 2, 3 ] }, { key: 'G', value: [ 4 ] } ] 107 | ``` 108 | 109 | 110 | ## Methods (index) 111 | 112 | * [map(*transformers)](#mapfn) 113 | * [mapf(field, *transformers)](#mapffn) 114 | * [mapff(source, destination, *transformers)](#mapfffn) 115 | * [group(groupField, valueField, *key-transformers)](#groupfn) 116 | * [sort(sortField, *transformers, \[comparer\])](#sortfn) 117 | * [object(keyField, valueField, *key-transformers)](#objectfn) 118 | * [array(keyName, valueName)](#arrayfn) 119 | * [filter(filterField, *transformers)](#filterfn) 120 | * [flatten(deep)](#flattenfn) 121 | * [default(key1, value1, key2, value2, ...)](#defaultfn) 122 | * [pick(*fields)](#pickfn) 123 | * [omit(*fields)](#omitfn) 124 | * [remove(*fields)](#removefn) 125 | * [pluck(field, *transformers)](#pluckfn) 126 | * [skip(count)](#skipfn) 127 | * [take(count)](#takefn) 128 | * [first()](#firstfn) 129 | * [last()](#lastfn) 130 | * [uniq(uniqField, *transformers)](#uniqfn) 131 | 132 | 133 | ## Methods (detail) 134 | 135 | ``` javascript 136 | var trans = require('trans'); 137 | ``` 138 | 139 | ### trans(data) 140 | This function creates a transformation wrapper around the specified data. 141 | Further transformation methods can be chained on the trans wrapper. 142 | 143 | ### value() 144 | Unwraps the raw data object. 145 | 146 | ### get(callback) 147 | Makes the current raw data available for inspection. It can be used 148 | to insert console log statements in the transformation chain for debugging purposes. 149 | 150 | ``` javascript 151 | var value = trans(data) 152 | .group('a.b') 153 | .get(console.log) 154 | .sort('key') 155 | .value(); 156 | ``` 157 | 158 | ### map(*transformers) 159 | 160 | This is the main transformation method. It passes the entire raw object to the transformers 161 | and it replaces it with the result returned by the last transformer function. 162 | 163 | Here is a simple example: 164 | 165 | ``` javascript 166 | trans('2.32').map(parseFloat, square, [ add, 10 ]).value(); 167 | ``` 168 | => ``15.3824`` 169 | 170 | Field names, and functions that exist on the object being transformed 171 | can be specified as transformers 172 | 173 | ``` javascript 174 | trans('transform me').map('toUpperCase', [ 'substring', 0, 5 ]).value(); 175 | ``` 176 | => ``'TRANS'`` 177 | 178 | ``` javascript 179 | trans({ a: 1 }).map('a').value(); 180 | ``` 181 | => ``1`` 182 | 183 | ``` javascript 184 | trans({ a: 'foo' }).map('a', 'toUpperCase', [ 'charAt', 1 ]).value(); 185 | ``` 186 | => ``'O'`` 187 | 188 | If the current object is an array the whole array is passed to the transformer functions. 189 | To transform its elements instead precede the transformers with a dot ``'.'`` which will 190 | indicate that array iteration is desired. 191 | 192 | Here are a few array examples: 193 | 194 | ``` javascript 195 | trans([ 1, 2, 3 ]).map('length').value(); 196 | ``` 197 | => ``3`` 198 | 199 | ``` javascript 200 | trans([ 1, 2, 3 ]).map('.', square).value(); 201 | ``` 202 | => ``[ 1, 4, 9 ]`` 203 | 204 | ``` javascript 205 | trans([ [ 1, 2 ], [ 3 ], [ 4, 5, 6 ] ]).map('.', sum).value(); 206 | ``` 207 | => ``[ 3, 3, 15 ]`` 208 | 209 | ``` javascript 210 | trans([ [ 1, 2 ], [ 3 ], [ 4, 5, 6 ] ]).map('.', '.', [ add, 5 ]).value(); 211 | ``` 212 | => ``[ [ 6, 7 ], [ 8 ], [ 9, 10, 11 ] ]`` 213 | 214 | ``` javascript 215 | trans([ { a: [ 1, 2 ] }, { a: [ 3 ] }, { a: [ 4, 5, 6 ] } ]) 216 | .map('.', 'a', 'length') 217 | .value(); 218 | ``` 219 | => ``[ 2, 1, 3 ]`` 220 | 221 | ``` javascript 222 | trans([ { a: [ 1, 2 ] }, { a: [ 3 ] }, { a: [ 4, 5, 6 ] } ]) 223 | .map('.', 'a', '.', square) 224 | .value(); 225 | ``` 226 | => ``[ [ 1, 4 ], [ 9 ], [ 16, 25, 36 ] ]`` 227 | 228 | Objects can be specified as transformers as well. When that is the case the result of 229 | the previous transformation will be used as an index into the transformer object. 230 | 231 | ``` javascript 232 | var intToName = { 1: 'one', 2: 'two', 3: 'three' }; 233 | 234 | trans([ 1, 2 ]).map('length', intToName).value(); 235 | ``` 236 | => ``'two'`` 237 | 238 | [Back to Index](#methodsindex) 239 | 240 | 241 | ### mapf(field, *transformers) 242 | 243 | This is exactly like ``map`` but it is applied at a specified field. In fact if a null 244 | field is specified the result is identical to calling ``map``. Otherwise, the input 245 | to the first transformer function will be the value at the specified field and the result 246 | of the last transformer will replace the value at that field. 247 | 248 | ``` javascript 249 | trans(1).mapf(null, [ add, 1 ]).value(); 250 | ``` 251 | => ``2`` 252 | 253 | ``` javascript 254 | trans({ a: 1 }).mapf('a', [ add, 1 ]).value(); 255 | ``` 256 | => ``{ a: 2 }`` 257 | 258 | Field names can contain dots to reach within nested objects. 259 | 260 | ``` javascript 261 | trans({ a: { b: 1 } }).mapf('a.b', [ add, 1 ]).value(); 262 | ``` 263 | => ``{ a: { b: 2 } }`` 264 | 265 | Such field names work across arrays as well. 266 | 267 | ``` javascript 268 | trans({ a: [ { b: 1 }, { b: 2 } ] }).mapf('a.b', [ add, 1 ]).value(); 269 | ``` 270 | => ``{ a: [ { b: 2 }, { b: 3 } ] }`` 271 | 272 | If the value at the field is an array the entire array is passed to the transformer functions. 273 | 274 | ``` javascript 275 | trans({ a: { b: [ 1, 2 ] } }).mapf('a.b', 'length').value(); 276 | ``` 277 | => ``{ a: { b: 2 } }`` 278 | 279 | Append one last dot to the end of the field name to indicate that array iteration 280 | is desired. In such a case each array item is passed to the transformer functions and a 281 | new array is created with the results of the transformations. 282 | 283 | ``` javascript 284 | trans({ a: { b: [ 1, 2 ] } }).mapf('a.b.', [ add, 1 ]).value(); 285 | ``` 286 | => ``{ a: { b: [ 2, 3 ] } }`` 287 | 288 | Specifying a dot ``'.'`` as the first transformer accomplishes the same thing. 289 | 290 | ``` javascript 291 | trans({ a: { b: [ 1, 2 ] } }).mapf('a.b', '.', [ add, 1 ]).value(); 292 | ``` 293 | => ``{ a: { b: [ 2, 3 ] } }`` 294 | 295 | [Back to Index](#methodsindex) 296 | 297 | 298 | ### mapff(source, destination, *transformers) 299 | 300 | This transformation maps the value of a field and sets the result onto another field. 301 | If the destination is null, the entire object is replaced. If the source and destination are both null it has the exact 302 | same outcome as ``map``. If the destination field does not exist it is created, otherwise its 303 | value is replaced by the result of the transformation. The source field is left unchanged. 304 | 305 | ``` javascript 306 | trans({ a: 1 }).mapff('a', 'b').value(); 307 | ``` 308 | => ``{ a: 1, b: 1 }`` 309 | 310 | ``` javascript 311 | trans({ a: 1 }).mapff('a', 'b', [ add, 1 ], square).value(); 312 | ``` 313 | => ``{ a: 1, b: 4 }`` 314 | 315 | Composite fields are allowed but the value passed to transformers is scoped based on where 316 | the source and destination fields point to. This becomes relevant when we are transforming across 317 | arrays. 318 | 319 | Below the function ``sum`` gets an array containing the values of ``a.b``, in this case ``[ 1, 2 ]`` 320 | and it computes their sum. 321 | 322 | ``` javascript 323 | trans({ a: [ { b: 1 }, { b: 2 } ] }).mapff('a.b', 'c', sum).value(); 324 | ``` 325 | => ``{ a: [ { b: 1 }, { b: 2 } ], c: 3 }`` 326 | 327 | In the next example the scope is reduced to each object inside the array, so the transformers 328 | only get the value of the ``b`` field. 329 | 330 | ``` javascript 331 | trans({ a: [ { b: 1 }, { b: 2 } ] }).mapff('a.b', 'a.c', [ add, 1 ]).value(); 332 | ``` 333 | => ``{ a: [ { b: 1, c: 2 }, { b: 2, c: 3 } ] }`` 334 | 335 | Same thing below, the scope is each item in the array due to the destination field 336 | pointing to items in the array. 337 | 338 | ``` javascript 339 | trans({ a: [ { b: 1, c: 3 }, { b: 2, c: 3 } ] }) 340 | .mapff('a', 'a.d', function(a) { return a.b + a.c; }) 341 | .value(); 342 | ``` 343 | => ``{ a: [ { b: 1, c: 3, d: 4 }, { b: 2, c: 3, d: 5 } ] }`` 344 | 345 | Constrast the above with the next example where the destination is a field 346 | on the outer object. The scope now is the entire array that ``a`` points to. 347 | 348 | ``` javascript 349 | trans({ a: [ { b: 1, c: 3 }, { b: 2, c: 3 } ] }) 350 | .mapff('a', 'd', '.', 'b') 351 | .value(); 352 | ``` 353 | => ``{ a: [ { b: 1, c: 3 }, { b: 2, c: 3 } ], d: [ 1, 2 ] }`` 354 | 355 | If the source field points to an array we can indicate that we want to transform the elements of 356 | the array by appending one last dot to it. Alternatively, a dot ``'.'`` could be specified as 357 | the first transformer. 358 | 359 | ``` javascript 360 | trans({ a: { b: [ 1, 2, 3 ] } }).mapff('a.b', 'a.c', 'length').value(); 361 | ``` 362 | => ``{ a: { b: [ 1, 2, 3 ], c: 3 } }`` 363 | 364 | ``` javascript 365 | trans({ a: { b: [ 1, 2, 3 ] } }).mapff('a.b.', 'a.c', [ add, 5 ]).value(); 366 | ``` 367 | => ``{ a: { b: [ 1, 2, 3 ], c: [ 6, 7, 8 ] } }`` 368 | 369 | ``` javascript 370 | trans({ a: { b: [ 1, 2, 3 ] } }).mapff('a.b', 'a.c', '.', [ add, 5 ]).value(); 371 | ``` 372 | => ``{ a: { b: [ 1, 2, 3 ], c: [ 6, 7, 8 ] } }`` 373 | 374 | If the destination is null the entire object is replaced. This could be useful for picking up values, 375 | although, there is a ``pluck`` method for this purpose. 376 | 377 | ``` javascript 378 | trans([ { a: [ { b: 1 }, { b: 2 } ] }, { a: [ { b: 3 } ] } ]) 379 | .mapff('a.b', null) 380 | .value(); 381 | ``` 382 | => ``[ [ 1, 2 ], [ 3 ] ]`` 383 | 384 | See the [unit tests](https://github.com/gabesoft/trans/blob/master/test/trans/map-test.js) 385 | for additional examples. 386 | 387 | [Back to Index](#methodsindex) 388 | 389 | 390 | ### group(groupField, valueField, *key-transformers) 391 | 392 | Maps an array of objects into an array of key-value pairs where the key is the value 393 | of the specified group field (possibly transformed) and the value is an array of values as 394 | indicated by the value field. If the value field is null the entire array item is used. 395 | 396 | ``` javascript 397 | trans([ 'ray', 'rich', 'charles' ]).group(null, null, [ 'charAt', 0 ]).value(); 398 | ``` 399 | => ``[ { key: 'r', value: [ 'ray', 'rich' ] }, { key: 'c', value: [ 'charles' ] } ]`` 400 | 401 | ``` javascript 402 | trans([ { a: 'ray', b: 1 }, { a: 'rich', b: 2 }, { a: 'charles', b: 3 } ]) 403 | .group('a', 'b', [ 'charAt', 0 ], 'toUpperCase') 404 | .value(); 405 | ``` 406 | => ``[ { key: 'R', value: [ 1, 2 ] }, { key: 'C', value: [ 3 ] } ]`` 407 | 408 | The default key and value names in the output array can be overriden with different names 409 | specified as part of the group field. The syntax of the group field is 410 | ``field:keyName:valueName``. 411 | 412 | ``` javascript 413 | trans([ 1, 1, 2, 1 ]).group(':k:v', null).value(); 414 | ``` 415 | => ``[ { k: 1, v: [ 1, 1, 1 ] }, { k: 2, v: [ 2 ] } ]`` 416 | 417 | ``` javascript 418 | trans([ { a: 1, b: 'x' }, { a: 1, b: 'y' }, { a: 2, b: 'z' } ]) 419 | .group('a:number:letters', 'b') 420 | .value(); 421 | ``` 422 | => ``[ { number: 1, letters: [ 'x', 'y' ] }, { number: 2, letters: [ 'z' ] } ]`` 423 | 424 | The group field name can contain dots to reach within nested objects or arrays. 425 | 426 | ``` javascript 427 | trans([ { a: [ { b: 1 }, { b: 2 } ], c: 'three' }, { a: [ { b: 10 } ], c: 'ten' } ]) 428 | .group('a.b', 'c') 429 | .value(); 430 | ``` 431 | => ``[ { key: [ 1, 2 ], value: [ 'three' ] }, { key: [ 10 ], value: [ 'ten' ] } ]`` 432 | 433 | ``` javascript 434 | trans([ { a: [ { b: 1 }, { b: 2 } ], c: 'three' }, { a: [ { b: 10 } ], c: 'ten' } ]) 435 | .group('a.b', 'c', sum) 436 | .value(); 437 | ``` 438 | => ``[ { key: 3, value: [ 'three' ] }, { key: 10, value: [ 'ten' ] } ]`` 439 | 440 | ``` javascript 441 | trans([ { a: { b: 1, c: 'one' } }, { a: { b: 11, c: 'eleven' } }, { a: { b: 2, c: 'two' } } ]) 442 | .group('a.b', 'a.c', [ mod, 10 ]) 443 | .value(); 444 | ``` 445 | => ``[ { key: 1, value: [ 'one', 'eleven' ] }, { key: 2, value: [ 'two' ] } ]`` 446 | 447 | #### Other versions 448 | - ``groupf(field, groupField, valueField, *key-transformers)`` 449 | - ``groupff(source, destination, groupField, valueField, *key-transformers)`` 450 | 451 | [Back to Index](#methodsindex) 452 | 453 | 454 | ### sort(sortField, *transformers, [comparer]) 455 | 456 | Replaces the target array with a stable sorted copy based on the value at the sort field 457 | (possibly transformed). If the last argument is a function that takes exactly two arguments 458 | it will be used as a comparer, otherwise a default comparer will be used. 459 | 460 | ``` javascript 461 | trans([ 1, 2, 1, 1, 3 ]).sort(null).value(); 462 | ``` 463 | => ``[ 1, 1, 1, 2, 3 ]`` 464 | 465 | ``` javascript 466 | trans([ 'Ash', 'bar', 'Baz', 'baz', 'bak', 'Foo', 'ash' ]).sort(null, 'toUpperCase').value(); 467 | ``` 468 | => ``[ 'Ash', 'ash', 'bak', 'bar', 'Baz', 'baz', 'Foo' ]`` 469 | 470 | ``` javascript 471 | var intToName = { 1: 'one', 2: 'two', 3: 'three', 4: 'four', 5: 'five' }; 472 | 473 | trans([ 1, 2, 3, 4, 5 ]).sort(null, intToName).value(); 474 | ``` 475 | => ``[ 5, 4, 1, 3, 2 ]`` 476 | 477 | ``` javascript 478 | trans([ { a: 1 }, { a: 3 }, { a: 2 } ]).sort('a').value(); 479 | ``` 480 | => ``[ { a: 1 }, { a: 2 }, { a: 3 } ]`` 481 | 482 | ``` javascript 483 | trans([ 484 | { a: 1, b: { c: 'one' } } 485 | , { a: 3, b: { c: 'three' } } 486 | , { a: 10, b: { c: 'ten' } } 487 | , { a: 2, b: { c: 'two' } } ]) 488 | .sort('b.c') 489 | .value(); 490 | ``` 491 | => 492 | ``` javascript 493 | [ { a: 1, b: { c: 'one' } }, 494 | { a: 10, b: { c: 'ten' } }, 495 | { a: 3, b: { c: 'three' } }, 496 | { a: 2, b: { c: 'two' } } ] 497 | ``` 498 | 499 | The sort direction can be specified on the sort field. 500 | The sort field syntax is ``name:direction`` 501 | 502 | ``` javascript 503 | trans([ 1, 1, 3, 2 ]).sort(':descending').value(); 504 | ``` 505 | => ``[ 3, 2, 1, 1 ]`` 506 | 507 | ``` javascript 508 | trans([ { a: { b: 1 } }, { a: { b: 1 } }, { a: { b: 3 } }, { a: { b: 2 } } ]) 509 | .sort('a.b:descending') 510 | .value(); 511 | ``` 512 | => ``[ { a: { b: 3 } }, { a: { b: 2 } }, { a: { b: 1 } }, { a: { b: 1 } } ]`` 513 | 514 | See the [unit tests](https://github.com/gabesoft/trans/blob/master/test/trans/sort-test.js) 515 | for additional examples. 516 | 517 | #### Other versions 518 | - ``sortf(field, sortField, *transformers, [comparer])`` 519 | - ``sortff(source, destination, sortField, *transformers, [comparer])`` 520 | 521 | [Back to Index](#methodsindex) 522 | 523 | 524 | ### object(keyField, valueField, *key-transformers) 525 | 526 | Transforms an array into an object where the key is the value at the specified key field 527 | (possibly transformed) and the value is as indicated by the value field. If the value 528 | field is null, the entire array item is used as the value. If multiple values map to the 529 | same key, the last one wins. 530 | 531 | ``` javascript 532 | trans(['abc', 'def', 'ghk']).object(null, null, [ 'charAt', 0 ]).value(); 533 | ``` 534 | => ``{ a: 'abc', d: 'def', g: 'ghk' }`` 535 | 536 | ``` javascript 537 | trans([ { a: 'abc', b: 1 }, { a: 'def', b: 2 }, { a: 'ghk', b: 3 } ]) 538 | .object('a', 'b', [ 'charAt', 1 ], 'toUpperCase') 539 | .value(); 540 | ``` 541 | => ``{ B: 1, E: 2, H: 3 }`` 542 | 543 | ``` javascript 544 | trans([ { a: 'abc', b: 1 }, { a: 'def', b: 2 }, { a: 'ghk', b: 3 } ]) 545 | .object('a', null, [ 'charAt', 1 ], 'toUpperCase') 546 | .value(); 547 | ``` 548 | => ``{ B: { a: 'abc', b: 1 }, E: { a: 'def', b: 2 }, H: { a: 'ghk', b: 3 } }`` 549 | 550 | See the [unit tests](https://github.com/gabesoft/trans/blob/master/test/trans/object-test.js) 551 | for additional examples. 552 | 553 | #### Other versions 554 | - ``objectf(field, keyField, valueField, *key-transformers)`` 555 | - ``objectff(source, destination, keyField, valueField, *key-transformers)`` 556 | 557 | [Back to Index](#methodsindex) 558 | 559 | 560 | ### array(keyName, valueName) 561 | 562 | Transforms an object into an array where each item is a key-value pair containing each key 563 | and its value. The key and value names can be specified, otherwise they will default 564 | to ``key`` and ``value``. 565 | 566 | ``` javascript 567 | trans({ a: 1, b: 2, c: 3 }).array().value(); 568 | ``` 569 | => ``[ { key: 'a', value: 1 }, { key: 'b', value: 2 }, { key: 'c', value: 3 } ]`` 570 | 571 | ``` javascript 572 | trans({ a: 1, b: 2, c: 3 }).array('letter', 'number').value(); 573 | ``` 574 | => ``[ { letter: 'a', number: 1 }, { letter: 'b', number: 2 }, { letter: 'c', number: 3 } ]`` 575 | 576 | If the target object is an array, ``array`` will be applied to every item in the array. 577 | 578 | ``` javascript 579 | trans([ { a: 1, b: 2 }, { a: 3, b: 4, c: 5 }, { d: 6 } ]).array('k', 'v').value(); 580 | ``` 581 | => 582 | ``` javascript 583 | [ [ { k: 'a', v: 1 }, { k: 'b', v: 2 } ], 584 | [ { k: 'a', v: 3 }, { k: 'b', v: 4 }, { k: 'c', v: 5 } ], 585 | [ { k: 'd', v: 6 } ] ] 586 | ``` 587 | 588 | #### Other versions 589 | - ``arrayf(field, keyName, valueName)`` 590 | - ``arrayff(source, destination, keyName, valueName)`` 591 | 592 | [Back to Index](#methodsindex) 593 | 594 | 595 | ### filter(filterField, *transformers) 596 | 597 | Filters an array by the value at the specified field (possibly transformed). If the result 598 | of the last transformer is not a boolean value, it will be converted to one before filtering. 599 | 600 | ``` javascript 601 | trans([1, 2, 1, 4, 5]).filter(null, [ mod, 2 ]).value(); 602 | ``` 603 | => ``[ 1, 1, 5 ]`` 604 | 605 | ``` javascript 606 | trans([ { a: { b: 1 } }, { a: { b: 0 } }, { a: { b: 3 } } ]).filter('a.b').value(); 607 | ``` 608 | => ``[ { a: { b: 1 } }, { a: { b: 3 } } ]`` 609 | 610 | ``` javascript 611 | trans([ { a: 'real' }, { a: 'rock' }, { a: 'star' } ]) 612 | .filter('a', function (s) { return s.charAt(0) === 'r'; }) 613 | .value(); 614 | ``` 615 | => ``[ { a: 'real' }, { a: 'rock' } ]`` 616 | 617 | The filtering can be inverted by indicating invert on the filter field. 618 | 619 | ``` javascript 620 | trans([1, 2, 1, 4, 5]).filter(':invert', [ mod, 2 ]).value(); 621 | ``` 622 | => ``[ 2, 4 ]`` 623 | 624 | ``` javascript 625 | trans([ { a: 'real' }, { a: 'rock' }, { a: 'star' } ]) 626 | .filter('a:invert', [ 'charAt', 0 ], [ 'localeCompare', 'r' ]) 627 | .value(); 628 | ``` 629 | => ``[ { a: 'real' }, { a: 'rock' } ]`` 630 | 631 | #### Other versions 632 | - ``filterf(field, filterField, *transformers)`` 633 | - ``filterff(source, destination, filterField, *transformers)`` 634 | 635 | [Back to Index](#methodsindex) 636 | 637 | 638 | ### flatten(deep) 639 | 640 | Flattens nested arrays. If deep is set to false or not specified only the first level 641 | is flattened. 642 | 643 | ``` javascript 644 | trans([ [ 1 ], [ 2 ], [ [ 3, 4 ], 5 ], [ [ 6 ] ] ]).flatten().value(); 645 | ``` 646 | => ``[ 1, 2, [ 3, 4 ], 5, [ 6 ] ]`` 647 | 648 | ``` javascript 649 | trans([ [ 1 ], [ 2 ], [ [ 3, 4 ], 5 ], [ [ 6 ] ] ]).flatten(true).value(); 650 | ``` 651 | => ``[ 1, 2, 3, 4, 5, 6 ]`` 652 | 653 | #### Other versions 654 | - ``flatenf(field, deep)`` 655 | - ``flatenff(source, destination, deep)`` 656 | 657 | [Back to Index](#methodsindex) 658 | 659 | 660 | ### default(key1, value1, key2, value2, ...) 661 | 662 | Fills in ``undefined`` and ``null`` values with the specified defaults. The number of 663 | arguments is expected to be an even number. 664 | 665 | ``` javascript 666 | trans({ a: { b: null, c: { }, d: { e: { g: 4 } } } }) 667 | .default('a.b', 1, 'a.c.f', 2, 'a.d.e.f', 5) 668 | .value(); 669 | ``` 670 | => ``{ a: { b: 1, c: { f: 2 }, d: { e: { f: 5, g: 4 } } } }`` 671 | 672 | 673 | If the target object is an array, ``default`` is applied to every item in the array. 674 | 675 | ``` javascript 676 | trans([{}, { a: 1 }, { a: null }, { a: 4 }, {} ]).default('a', 100).value(); 677 | ``` 678 | => ``[ { a: 100 }, { a: 1 }, { a: 100 }, { a: 4 }, { a: 100 } ]`` 679 | 680 | ``` javascript 681 | trans([ { a: [ { b: 1 }, {} ] }, { a: [ {} ] } ]).default('a.b', 10).value(); 682 | ``` 683 | => ``[ { a: [ { b: 1 }, { b: 10 } ] }, { a: [ { b: 10 } ] } ]`` 684 | 685 | [Back to Index](#methodsindex) 686 | 687 | 688 | ### pick(*fields) 689 | 690 | Creates new objects that contain only the specified fields. 691 | 692 | ``` javascript 693 | trans({ a: { b: 1, c: 2 }, d: 5, e: 6 }).pick('a.b', 'e').value(); 694 | ``` 695 | => ``{ a: { b: 1 }, e: 6 }`` 696 | 697 | If the target object is an array, ``pick`` is applied to every item in the array. 698 | 699 | ``` javascript 700 | trans({ a: [ { b: 1, c: 2 }, { b: 3, c: 4 } ], d: 5 }).pick('a.b').value(); 701 | ``` 702 | => ``{ a: [ { b: 1 }, { b: 3 } ] }`` 703 | 704 | #### Other versions 705 | - ``pickf(field, *fields)`` 706 | - ``pickff(source, destination, *fields)`` 707 | 708 | [Back to Index](#methodsindex) 709 | 710 | 711 | ### omit(*fields) 712 | 713 | Creates new objects that do not contain the specified fields. Calling omit with no 714 | parameters will create a deep copy of the current object. 715 | 716 | ``` javascript 717 | trans({ a: { b: 1, c: 2 }, d: 5, e: 6 }).omit('a.c', 'd').value(); 718 | ``` 719 | => ``{ a: { b: 1 }, e: 6 }`` 720 | 721 | If the target object is an array, ``omit`` is applied to every item in the array. 722 | 723 | ``` javascript 724 | trans({ a: [ { b: 1, c: 2 }, { b: 3, c: 4 } ], d: 5 }).omit('a.c', 'd').value(); 725 | ``` 726 | => ``{ a: [ { b: 1 }, { b: 3 } ] }`` 727 | 728 | #### Other versions 729 | - ``omitf(field, *fields)`` 730 | - ``omitff(source, destination, *fields)`` 731 | 732 | [Back to Index](#methodsindex) 733 | 734 | 735 | ### remove(*fields) 736 | 737 | Traverses the object tree and deletes the specified fields in place. 738 | 739 | ``` javascript 740 | trans({ a: { b: 1, c: 2 }, d: 5, e: 6 }).remove('a.c', 'd').value(); 741 | ``` 742 | => ``{ a: { b: 1 }, e: 6 }`` 743 | 744 | If the target object is an array, ``remove`` is applied to every item in the array. 745 | 746 | ``` javascript 747 | trans({ a: [ { b: 1, c: 2 }, { b: 3, c: 4 } ], d: 5 }).remove('a.c', 'd').value(); 748 | ``` 749 | => ``{ a: [ { b: 1 }, { b: 3 } ] }`` 750 | 751 | [Back to Index](#methodsindex) 752 | 753 | 754 | ### pluck(pluckField, *transformers) 755 | 756 | Extracts the value(s) at the indicated field. 757 | 758 | ``` javascript 759 | trans({ a: { b: 100 } }).pluck('a.b').value(); 760 | ``` 761 | => ``100`` 762 | 763 | ``` javascript 764 | trans({ a: [ { b: 1 }, { b: 2 }, { b: 3 } ] }).pluck('a.b').value(); 765 | ``` 766 | => ``[ 1, 2, 3 ]`` 767 | 768 | ``` javascript 769 | trans({ a: [ { b: 1 }, { b: 2 }, { b: 3 } ] }).pluck('a.b', sum).value(); 770 | ``` 771 | => ``6`` 772 | 773 | ``` javascript 774 | trans({ a: [ { b: 1 }, { b: 2 }, { b: 3 } ] }).pluck('a.b', '.', [ add, 1 ]).value(); 775 | ``` 776 | => ``[ 2, 3, 4 ]`` 777 | 778 | ``` javascript 779 | trans([ { a: { b: [ { c: 1 } ] } }, { a: { b: [ { c: 3 }, { c: 4 } ] } } ]) 780 | .pluck('a.b.c') 781 | .value(); 782 | ``` 783 | => ``[ [ 1 ], [ 3, 4 ] ]`` 784 | 785 | #### Other versions 786 | - ``pluckf(field, pluckField, *transformers)`` 787 | - ``pluckff(source, destination, pluckField, *transformers)`` 788 | 789 | [Back to Index](#methodsindex) 790 | 791 | 792 | ### skip(count) 793 | 794 | Creates a new array that skips the specified number of items. 795 | 796 | ``` javascript 797 | trans([ 1, 2, 3, 4, 5, 6 ]).skip(2).value(); 798 | ``` 799 | => ``[ 3, 4, 5, 6 ]`` 800 | 801 | #### Other versions 802 | - ``skipf(field, count)`` 803 | - ``skipff(source, destination, count)`` 804 | 805 | [Back to Index](#methodsindex) 806 | 807 | 808 | ### take(count) 809 | 810 | Creates a new array that contains only the first specified number of items. 811 | 812 | ``` javascript 813 | trans([ 1, 2, 3, 4, 5, 6 ]).take(2).value(); 814 | ``` 815 | => ``[ 1, 2 ]`` 816 | 817 | #### Other versions 818 | - ``takef(field, count)`` 819 | - ``takeff(source, destination, count)`` 820 | 821 | [Back to Index](#methodsindex) 822 | 823 | 824 | ### first() 825 | 826 | Replaces the target array with its first element. 827 | 828 | ``` javascript 829 | trans([ 1, 2, 4 ]).first().value(); 830 | ``` 831 | =>``1`` 832 | 833 | ``` javascript 834 | trans([]).first().value(); 835 | ``` 836 | => ``null`` 837 | 838 | #### Other versions 839 | - ``firstf(field)`` 840 | - ``firstff(source, destination)`` 841 | 842 | [Back to Index](#methodsindex) 843 | 844 | 845 | ### last() 846 | 847 | Replaces the target array with its last element. 848 | 849 | ``` javascript 850 | trans([ 1, 2, 4 ]).last().value(); 851 | ``` 852 | =>``4`` 853 | 854 | #### Other versions 855 | - ``lastf(field)`` 856 | - ``lastff(source, destination)`` 857 | 858 | [Back to Index](#methodsindex) 859 | 860 | 861 | ### uniq(uniqField, *transformers) 862 | 863 | Removes duplicate items from an array according to the value at the specified field 864 | (possibly transformed). 865 | 866 | ``` javascript 867 | trans([ 1, 1, 2 ]).uniq().value(); 868 | ``` 869 | => ``[ 1, 2 ]`` 870 | 871 | ``` javascript 872 | trans([ 1, 11, 12, 22 ]).uniq(null, [ mod, 10 ]).value(); 873 | ``` 874 | => ``[ 1, 12 ]`` 875 | 876 | ``` javascript 877 | trans([ { a: 'abc' }, { a: 'aab' }, { a: 'bcd' }, { a: 'bad' } ]) 878 | .uniq('a', [ 'charAt', 0 ]) 879 | .value(); 880 | ``` 881 | => ``[ { a: 'abc' }, { a: 'bcd' } ]`` 882 | 883 | #### Other versions 884 | - ``uniqf(field, uniqField, *transformers)`` 885 | - ``uniqff(source, destination, uniqField, *transformers)`` 886 | 887 | [Back to Index](#methodsindex) 888 | 889 | ## Gotchas and Limitations 890 | 891 | Some transformations will modify the original data while others won't. See the two 892 | examples below. 893 | 894 | ``` javascript 895 | var a = [ 1, 2, 3 ]; 896 | var t = trans(a).map('shift').value(); 897 | 898 | console.log(a); 899 | console.log(t); 900 | ``` 901 | => ``[ 2, 3 ]`` 902 | => ``1`` 903 | 904 | ``` javascript 905 | var a = [ 1, 2, 3 ]; 906 | var t = trans(a).map(['slice', 0, 1], 'shift').value(); 907 | 908 | console.log(a); 909 | console.log(t); 910 | ``` 911 | => ``[ 1, 2, 3 ]`` 912 | => ``1`` 913 | 914 | Calling ``mapff`` without any transformer functions will just create another reference 915 | to the same object. This may lead to unexpected results. 916 | 917 | ``` javascript 918 | var obj = { a: { b: 2, c: 'X' } }; 919 | var res = trans(obj).mapff('a', 'c').value(); 920 | 921 | console.log(res); 922 | 923 | res.a.c = 'changed'; 924 | console.log(res); 925 | ``` 926 | 927 | => ``{ a: { b: 2, c: 'X' }, c: { b: 2, c: 'X' } }`` 928 | => ``{ a: { b: 2, c: 'changed' }, c: { b: 2, c: 'changed' } }`` 929 | 930 | Calling ``mapff`` without any transformer functions and setting the destination to 931 | be a field on the source object would create a circular reference. 932 | 933 | ``` javascript 934 | trans({ a: { b: { c: 1 } } }).mapff('a', 'a.b.d').value(); 935 | ``` 936 | => ``{ a: { b: { c: 1, d: [Circular] } } }`` 937 | 938 | ## License 939 | 940 | MIT 941 | -------------------------------------------------------------------------------- /test/trans/map-test.js: -------------------------------------------------------------------------------- 1 | module.exports = function (util) { 2 | var trans = util.trans 3 | , assert = util.assert 4 | , add = util.add 5 | , sum = util.sum 6 | , square = util.square; 7 | 8 | describe('map', function () { 9 | it('should map the transformation state - object1', function () { 10 | var o = 'abc' 11 | , t = trans(o).map('toUpperCase').value(); 12 | assert.strictEqual(t, 'ABC'); 13 | }); 14 | 15 | it('should map the transformation state - object2', function () { 16 | var o = { a: 'abc' } 17 | , t = trans(o).map('a', 'toUpperCase').value(); 18 | assert.strictEqual(t, 'ABC'); 19 | }); 20 | 21 | it('should map the transformation state - object3', function () { 22 | var o = { a: { b: 'abc' } } 23 | , t = trans(o).map('a', 'b', ['substring', 1, 3], 'toUpperCase').value(); 24 | assert.strictEqual(t, 'BC'); 25 | }); 26 | 27 | it('should map the transformation state - object3', function () { 28 | var o = { a: { b: { c: 1 } } } 29 | , t = trans(o).map('a', 'b', 'c').value(); 30 | assert.strictEqual(t, 1); 31 | }); 32 | 33 | it('should map the transformation state - array1a', function () { 34 | var o = [ 1, 2, 3 ] 35 | , t = trans(o).map('.', [add, 1]).value(); 36 | assert.deepEqual(t, [ 2, 3, 4 ]); 37 | }); 38 | 39 | it('should accept functions in array format1', function () { 40 | var o = [ 1, 2, 3 ] 41 | , t = trans(o).map('.', [add, 5]).value(); 42 | assert.deepEqual(t, [ 6, 7, 8 ]); 43 | }); 44 | 45 | it('should pass in the element index when iterating through an array', function () { 46 | var o = [ 'a', 'b', 'c', 'd' ] 47 | , t = trans(o).map('.', function (x) { return x + this.getIndex(); }).value(); 48 | assert.deepEqual(t, [ 'a0', 'b1', 'c2', 'd3' ]); 49 | }); 50 | 51 | it('should accept functions in array format2', function () { 52 | var o = [ 'ab', 'cde' ] 53 | , t = trans(o).map('.', ['concat', 'ww', 'zz']).value(); 54 | assert.deepEqual(t, [ 'abwwzz', 'cdewwzz' ]); 55 | }); 56 | 57 | it('should map the transformation state - array 1', function () { 58 | var o = [ { b: 1, c: 3 }, { b: 2, c: 3 } ] 59 | , t = trans(o).map('.', function(x) { return x.b + x.c; }).value(); 60 | assert.deepEqual(t, [ 4, 5 ] ); 61 | }); 62 | 63 | it('should map the transformation state - array1b', function () { 64 | var o = [ { a: 1 }, { a: 2 }, {a: 3 } ] 65 | , t = trans(o).map('.', 'a', [add, 1]).value(); 66 | assert.deepEqual(t, [ 2, 3, 4 ]); 67 | }); 68 | 69 | it('should map the transformation state - array1c', function () { 70 | var o = [ { a: { b: 1 } }, { a: { b: 2 } }, {a: { b: 3 } } ] 71 | , t = trans(o).map('.', 'a', 'b', [add, 1]).value(); 72 | assert.deepEqual(t, [ 2, 3, 4 ]); 73 | }); 74 | 75 | it('should map the transformation state - array1d', function () { 76 | var o = [ 77 | { a: [ { b: 1 }, { b: 2 } ] } 78 | , { a: [ { b: 2 }, { b: 2 } ] } 79 | , { a: [ { b: 1 }, { b: 3 } ] } 80 | , { a: [ { b: 3 }, { b: 4 } ] } ] 81 | , t = trans(o).map('.', 'a', '.', 'b', [add, 5]).value(); 82 | assert.deepEqual(t, [ [ 6, 7 ], [ 7, 7 ], [ 6, 8 ], [ 8, 9 ] ]); 83 | }); 84 | 85 | it('should map the transformation state - array1e', function () { 86 | var o = [ 87 | { a: [ { b: 1 }, { b: 2 } ] } 88 | , { a: [ { b: 2 }, { b: 2 } ] } 89 | , { a: [ { b: 1 }, { b: 3 } ] } 90 | , { a: [ { b: 3 }, { b: 4 } ] } ] 91 | , t = trans(o).map('.', 'a').value(); 92 | assert.deepEqual(t, [ 93 | [ { b: 1 }, { b: 2 } ] 94 | , [ { b: 2 }, { b: 2 } ] 95 | , [ { b: 1 }, { b: 3 } ] 96 | , [ { b: 3 }, { b: 4 } ] ]); 97 | }); 98 | 99 | it('should map the transformation state - array2', function () { 100 | var o = [ 'abc', 'de', 'defg' ] 101 | , t = trans(o).map('length').value(); 102 | assert.deepEqual(t, 3); 103 | }); 104 | 105 | it('should map the transformation state - array2', function () { 106 | var o = [ 'abc', 'de', 'defg' ] 107 | , t = trans(o).map('length').value(); 108 | assert.deepEqual(t, 3); 109 | }); 110 | 111 | it('should map the transformation state - array3', function () { 112 | var o = [ 'abc', 'de', 'defg' ] 113 | , t = trans(o).map('.', 'length').value(); 114 | assert.deepEqual(t, [ 3, 2, 4 ]); 115 | }); 116 | 117 | it('should apply multiple transformers in the given order', function () { 118 | var o = [ 1, 2, 3 ] 119 | , e1 = trans(o).map('.', square, [add, 1]).value() 120 | , e2 = trans(o).map('.', [add, 1], square).value(); 121 | assert.deepEqual(e1, [ 2, 5, 10 ]); 122 | assert.deepEqual(e2, [ 4, 9, 16 ]); 123 | }); 124 | 125 | it('should allow passing field names for transformers', function () { 126 | var o = [ 1, 2, 3 ] 127 | , t = trans(o).map('length', [add, 2]).value(); 128 | assert.strictEqual(t, o.length + 2); 129 | }); 130 | 131 | it('should work with transformers that return null', function () { 132 | var o = 'abc' 133 | , t = trans(o).map(function () { return null; }).value(); 134 | assert.strictEqual(t, null); 135 | }); 136 | 137 | it('should collect values from nested arrays', function () { 138 | var o = [ 139 | [ { a: { b: 1 } }, { a: { b: 2 } } ] 140 | , [ { a: { b: 3 } } ] 141 | , [ { a: { b: 4 } }, { a: { b: 5 } } ] ] 142 | , t = trans(o).map('.', '.', 'a','b', [add, 5]); 143 | assert.deepEqual(t.value(), [ [ 6, 7 ], [ 8 ], [ 9, 10 ] ]); 144 | assert.deepEqual(t.map('.', sum).value(), [ 13, 8, 19 ]); 145 | assert.deepEqual(t.map(sum).value(), 40); 146 | }); 147 | 148 | it('should allow passing hash maps as transformers', function () { 149 | var o = [ 1, 2, 3, 4 ] 150 | , nums = { 1: 'one', 2: 'two', 3: 'three', 4: 'four' } 151 | , t = trans(o).map('.', nums).value(); 152 | assert.deepEqual(t, [ 'one', 'two', 'three', 'four' ]); 153 | }); 154 | 155 | it('should fail if a null transformer is specified', function () { 156 | assert.throws(function () { 157 | trans({ a: 1 }).map(null); 158 | }, /could not apply transformer null/i); 159 | }); 160 | 161 | it('should fail if an invalid transformer is specified', function () { 162 | assert.throws(function () { 163 | trans({ a: 1 }).map(1); 164 | }, /could not apply transformer 1/i); 165 | }); 166 | }); 167 | 168 | describe('mapf', function () { 169 | it('should map the object at the given field - object', function () { 170 | var o = { a: 1 } 171 | , t = trans(o).mapf('a', [add, 1]).value(); 172 | assert.deepEqual(t, { a: 2 }); 173 | }); 174 | 175 | it('should behave like map if the specified field is null', function () { 176 | var o = [ 1, 2 ] 177 | , t = trans(o).mapf(null, 'length').value(); 178 | assert.strictEqual(t, 2); 179 | }); 180 | 181 | it('should map the object at the given field - array1a', function () { 182 | var o = { a: { b: [ 1, 2, 3 ] } } 183 | , t = trans(o).mapf('a.b.', square, [add, 1]).value(); 184 | assert.deepEqual(t, { a: { b: [ 2, 5, 10 ] } }); 185 | }); 186 | 187 | it('should map the object at the given field - array1b', function () { 188 | var o = { a: { b: [ 1, 2, 3 ] } } 189 | , t = trans(o).mapf('a.b', 'length', [add, 1]).value(); 190 | assert.deepEqual(t, { a: { b: 4 } }); 191 | }); 192 | 193 | it('should replace an array with its first element', function () { 194 | var o = { a: { b: [ 1, 2, 3 ] } } 195 | , t = trans(o).mapf('a.b', 'shift').value(); 196 | assert.deepEqual(t, { a: { b: 1 } }); 197 | }); 198 | 199 | it('should replace an array with its last element', function () { 200 | var o = { a: { b: [ 1, 2, 3 ] } } 201 | , t = trans(o).mapf('a.b', 'pop').value(); 202 | assert.deepEqual(t, { a: { b: 3 } }); 203 | }); 204 | 205 | it('should take only the first x elements from an array', function () { 206 | var o = { a: { b: [ 1, 2, 3, 4, 4, 5, 8 ] } } 207 | , t = trans(o).mapf('a.b', ['slice', 0, 3]).value(); 208 | assert.deepEqual(t, { a: { b: [ 1, 2, 3 ] } }); 209 | }); 210 | 211 | it('should map the object at the given field - array2', function () { 212 | var o = { a: [ { b: 1 }, { b: 2 }, { b: 3 } ] } 213 | , t = trans(o).mapf('a.b', [add, 1], square).value(); 214 | assert.deepEqual(t, { a: [ { b: 4 }, { b: 9 }, { b: 16 } ] }); 215 | }); 216 | 217 | it('should map the object at the given field - array3', function () { 218 | var o = { a: [ { b: { c: 1 } }, { b: { c: 2 } }, { b: { c: 3 } } ] } 219 | , t = trans(o).mapf('a.b.c', [add, 1], square).value(); 220 | assert.deepEqual(t, { a: [ { b: { c: 4 } }, { b: { c: 9 } }, { b: { c: 16 } } ] }); 221 | }); 222 | 223 | it('should map the object at the given field - array4', function () { 224 | var o = [ { a: { b: { c: 'abc' } } }, { a: { b: { c: 'def' } } } ] 225 | , t = trans(o).mapf('a.b.c', 'toUpperCase').value(); 226 | assert.deepEqual(t, [ { a: { b: { c: 'ABC' } } }, { a: { b: { c: 'DEF' } } } ]); 227 | }); 228 | 229 | it('should map the object at the given field - array5', function () { 230 | var o = [ 231 | { a: [ { b: { c: 1 } }, { b: { c: 2 } } ] } 232 | , { a: [ { b: { c: 2 } }, { b: { c: 3 } } ] } ] 233 | , t = trans(o).mapf('a.b.c', [add, 1]).value(); 234 | 235 | assert.deepEqual(t, [ 236 | { a: [ { b: { c: 2 } }, { b: { c: 3 } } ] } 237 | , { a: [ { b: { c: 3 } }, { b: { c: 4 } } ] } ]); 238 | }); 239 | 240 | it('should map the object at the given field - array6', function () { 241 | var o = { a: [ { b: [ 1, 1, 2 ] }, { b: [ 3, 3 ] }, { b: [ 1, 2, 3 ] } ] } 242 | , t = trans(o).mapf('a.b.', [add, 1]).value(); 243 | assert.deepEqual(t, { a: [ { b: [ 2, 2, 3 ] }, { b: [ 4, 4 ] }, { b: [ 2, 3, 4 ] } ] }); 244 | }); 245 | 246 | it('should map the object at the given field - array7', function () { 247 | var o = { a: [ { b: [ 1, 1, 2 ] }, { b: [ 3, 3 ] }, { b: [ 1, 2, 3 ] } ] } 248 | , t = trans(o).mapf('a.b', 'length').value(); 249 | assert.deepEqual(t, { a: [ { b: 3 }, { b: 2 }, { b: 3 } ] }); 250 | }); 251 | 252 | it('should create missing fields 1', function () { 253 | var o = [ { a: { b: 1 } }, { a: { } }, { a: { b: 3 } } ] 254 | , t = trans(o).mapf('a.b', [add, 1]).value(); 255 | assert.deepEqual(t, [ { a: { b: 2 } }, { a: { b: 1 } }, { a: { b: 4 } } ]); 256 | }); 257 | 258 | it('should create missing fields 2', function () { 259 | var o = { a: { b: {} } } 260 | , t = trans(o).mapf('a.b.c', [add, 1]).value(); 261 | assert.deepEqual(t, { a: { b: { c: 1 } } }); 262 | }); 263 | 264 | it('should work with nested fields', function () { 265 | var o = { a: { b: { c: 'abc' } } } 266 | , t = trans(o).mapf('a.b.c', 'toUpperCase').value(); 267 | assert.deepEqual(t, { a: { b: { c: 'ABC' } } }); 268 | }); 269 | 270 | it('should work with nested arrays', function () { 271 | var o = { a: [ [ { b: 1 } ], [ { b: 2 } ] ] } 272 | , t = trans(o).mapf('a.b', [add, 1]).value(); 273 | assert.deepEqual(t, { a: [ [ { b: 2 } ], [ { b: 3 } ] ] }); 274 | }); 275 | 276 | it('should handle empty objects', function () { 277 | var o = [ {}, {} ] 278 | , t = trans(o).mapf('b', [ add, 1 ]).value(); 279 | assert.deepEqual(t, [ { b: 1 }, { b: 1 } ]); 280 | }); 281 | 282 | it('should pass the index when iterating arrays 1', function () { 283 | var o = { a: [ 284 | { b: [ 'a', 'b', 'c' ] } 285 | , { b: [ 'd', 'e' ] } 286 | , { b: [ 'f' ] } ] } 287 | , t = trans(o).mapf('a.b.', function(x) { return x + this.getIndex(); }).value(); 288 | assert.deepEqual(t, { a: [ 289 | { b: [ 'a0', 'b1', 'c2' ] } 290 | , { b: [ 'd0', 'e1' ] } 291 | , { b: [ 'f0' ] } ] }); 292 | }); 293 | 294 | it('should pass the index when iterating arrays 2', function () { 295 | var o = [ 296 | { a: [ { b: [ 'aa', 'ab' ] }, { b: [ 'ac', 'ad', 'ae' ] } ] } 297 | , { a: [ { b: [ 'ba', 'bb' ] }, { b: [ 'bc', 'bd', 'be' ] } ] } 298 | , { a: [ { b: [ 'ca', 'cb' ] }, { b: [ 'cc', 'cd', 'ce' ] } ] } 299 | , { a: [ { b: [ 'da', 'db' ] }, { b: [ 'dc', 'dd', 'de' ] } ] } ] 300 | , t = trans(o) 301 | .map('.', 'a', '.', 'b', '.', function (x) { return x + this.getIndex(); }) 302 | .value(); 303 | assert.deepEqual(t, [ 304 | [ [ 'aa0', 'ab1' ], [ 'ac0', 'ad1', 'ae2' ] ] 305 | , [ [ 'ba0', 'bb1' ], [ 'bc0', 'bd1', 'be2' ] ] 306 | , [ [ 'ca0', 'cb1' ], [ 'cc0', 'cd1', 'ce2' ] ] 307 | , [ [ 'da0', 'db1' ], [ 'dc0', 'dd1', 'de2' ] ] 308 | ]); 309 | }); 310 | }); 311 | 312 | describe('mapff', function () { 313 | it('should map the object at the given field - object', function () { 314 | var o = { a: 1 } 315 | , t = trans(o).mapff('a', 'c', [add, 1]).value(); 316 | assert.deepEqual(t, { a: 1, c: 2 }); 317 | }); 318 | 319 | it('should iterate the array when there is a final dot in the field name', function () { 320 | var o = { a: { b: [ 1, 2, 3 ] } } 321 | , t = trans(o).mapff('a.b.', 'c', square, [add, 1]).value(); 322 | assert.deepEqual(t, { a: { b: [ 1, 2, 3 ] }, c: [ 2, 5, 10 ] }); 323 | }); 324 | 325 | it('should not iterate the array when there is no final dot in the field name', function () { 326 | var o = { a: { b: [ 1, 2, 3 ] } } 327 | , t = trans(o).mapff('a.b', 'c', 'length', [add, 1], square).value(); 328 | assert.deepEqual(t, { a: { b: [ 1, 2, 3 ] }, c: 16 }); 329 | }); 330 | 331 | it('should map the object at the given field - array1c', function () { 332 | var o = { a: { b: [ 1, 2, 3 ] } } 333 | , t = trans(o).mapff('a.b.', 'a.b', square, [add, 1]).value(); 334 | assert.deepEqual(t, { a: { b: [ 2, 5, 10 ] } }); 335 | }); 336 | 337 | it('should map the object at the given field - array1d', function () { 338 | var o = { a: { b: [ 1, 2, 3 ] } } 339 | , t = trans(o).mapff('a.b', 'a.b', sum).value(); 340 | assert.deepEqual(t, { a: { b: 6 } }); 341 | }); 342 | 343 | it('should map the object at the given field - array2a', function () { 344 | var o = { a: [ { b: 1 }, { b: 2 }, { b: 3 } ] } 345 | , t = trans(o).mapff('a.b', 'a.c', [add, 1], square).value(); 346 | assert.deepEqual(t, { a: [ { b: 1, c: 4 } , { b: 2, c: 9 } , { b: 3, c: 16 } ] }); 347 | }); 348 | 349 | it('should map the object at the given field - array2b', function () { 350 | var o = { a: [ { b: 1 }, { b: 2 }, { b: 3 } ] } 351 | , t = trans(o).mapff('a.b', 'c', '.', [add, 1], square).value(); 352 | assert.deepEqual(t, { a: [ { b: 1 }, { b: 2 }, { b: 3 } ] , c: [ 4, 9, 16 ] }); 353 | }); 354 | 355 | it('should map the object at the given field - array2c', function () { 356 | var o = { a: [ { b: 1 }, { b: 2 }, { b: 3 } ] } 357 | , t = trans(o).mapff('a', 'c').value(); 358 | assert.deepEqual(t, { 359 | a: [ { b: 1 }, { b: 2 }, { b: 3 } ] 360 | , c: [ { b: 1 }, { b: 2 }, { b: 3 } ] }); 361 | }); 362 | 363 | it('should map the correct source to the correct destination', function () { 364 | var o = { a: [ { b: 1 }, { b: 2 }, { b: 3 } ], e: [ { b: 1 }, { b: 2 } ] } 365 | , t = trans(o).mapff('a.b', 'e.c').value(); 366 | assert.deepEqual(t, { 367 | a: [ { b: 1 }, { b: 2 }, { b: 3 } ] 368 | , e: [ { b: 1, c: [ 1, 2, 3 ] }, { b: 2, c: [ 1, 2, 3 ] } ] 369 | }); 370 | }); 371 | 372 | it('should map the correct source to the correct destination - nested arrays', function () { 373 | var o = { a: [ { b: 1 }, { b: 2 }, { b: 3 } ], e: [ [ { b: 1 } ], [ { b: 2 } ] ] } 374 | , t = trans(o).mapff('a.b', 'e.c').value(); 375 | assert.deepEqual(t, { 376 | a: [ { b: 1 }, { b: 2 }, { b: 3 } ] 377 | , e: [ [ { b: 1, c: [ 1, 2, 3 ] } ], [ { b: 2, c: [ 1, 2, 3 ] } ] ] 378 | }); 379 | }); 380 | 381 | it('should create a field on the target object(s)', function () { 382 | var o = { a: [ { b: 1 }, { b: 2 }, { b: 3 } ] } 383 | , t = trans(o).mapff('a.c', 'a.c', function () { return 'a'; }).value(); 384 | assert.deepEqual(t, { a: [ { b: 1, c: 'a' } , { b: 2, c: 'a' } , { b: 3, c: 'a' } ] }); 385 | }); 386 | 387 | it('should work with nested fields within an array', function () { 388 | var o = { a: [ { b: { c: 1 } }, { b: { c: 2 } }, { b: { c: 3 } } ] } 389 | , t = trans(o).mapff('a.b.c', 'd', '.', [add, 1], square).value(); 390 | assert.deepEqual(t, { 391 | a: [ { b: { c: 1 } }, { b: { c: 2 } }, { b: { c: 3 } } ] 392 | , d: [ 4, 9, 16 ] 393 | }); 394 | }); 395 | 396 | it('should replace nested fields within arrays 1', function () { 397 | var o = { a: [ { b: { c: 1 } }, { b: { c: 2 } }, { b: { c: 3 } } ] } 398 | , t = trans(o).mapff('a.b.c', 'a.b', [add, 1], square).value(); 399 | assert.deepEqual(t, { a: [ { b: 4 }, { b: 9 }, { b: 16 } ] }); 400 | }); 401 | 402 | it('should replace nested fields within arrays 2', function () { 403 | var o = [ { a: { b: { c: 'abc' } } }, { a: { b: { c: 'def' } } } ] 404 | , t = trans(o).mapff('a.b.c', 'a', 'toUpperCase').value(); 405 | assert.deepEqual(t, [ { a: 'ABC' }, { a: 'DEF' } ]); 406 | }); 407 | 408 | it('should map fields within the proper object 1', function () { 409 | var o = { a: [ { b: { c: 1 } }, { b: { c: 2 } }, { b: { c: 3 } } ] } 410 | , t = trans(o).mapff('a.b.c', 'a.b.d', [add, 1], square).value(); 411 | assert.deepEqual(t, { a: [ 412 | { b: { c: 1, d: 4 } } 413 | , { b: { c: 2, d: 9 } } 414 | , { b: { c: 3, d: 16 } } ] }); 415 | }); 416 | 417 | it('should map fields within the proper object 2', function () { 418 | var o = [ 419 | { a: { b: { c: 'abc' } } } 420 | , { a: { b: { c: 'def' } } } ] 421 | , t = trans(o).mapff('a.b.c', 'd', 'toUpperCase').value(); 422 | assert.deepEqual(t, [ 423 | { a: { b: { c: 'abc' } }, d: 'ABC' } 424 | , { a: { b: { c: 'def' } }, d: 'DEF' } 425 | ]); 426 | }); 427 | 428 | it('should map fields within the proper object 3', function () { 429 | var o = [ 430 | { a: [ { b: { c: 1 } }, { b: { c: 2 } } ] } 431 | , { a: [ { b: { c: 2 } }, { b: { c: 3 } } ] } ] 432 | , t = trans(o).mapff('a.b.c', 'd', '.', [add, 1], square).value(); 433 | assert.deepEqual(t, [ 434 | { a: [ { b: { c: 1 } }, { b: { c: 2 } } ], d: [ 4, 9 ] } 435 | , { a: [ { b: { c: 2 } }, { b: { c: 3 } } ], d: [ 9, 16 ] } ]); 436 | }); 437 | 438 | it('should map fields within the proper object 4', function () { 439 | var o = [ 440 | { a: [ { b: { c: 1 } }, { b: { c: 2 } } ] } 441 | , { a: [ { b: { c: 2 } }, { b: { c: 3 } } ] } ] 442 | , t = trans(o).mapff('a.b.c', 'a.b', [add, 1], square).value(); 443 | assert.deepEqual(t, [ 444 | { a: [ { b: 4 }, { b: 9 } ] } 445 | , { a: [ { b: 9 }, { b: 16 } ] } ]); 446 | }); 447 | 448 | it('should map fields within the proper object 5', function () { 449 | var o = [ 450 | { a: [ { b: { c: 1 } }, { b: { c: 2 } } ] } 451 | , { a: [ { b: { c: 2 } }, { b: { c: 3 } } ] } ] 452 | , t = trans(o).mapff('a.b.c', 'a', '.', [add, 1], square).value(); 453 | assert.deepEqual(t, [ 454 | { a: [ 4, 9 ] } 455 | , { a: [ 9, 16 ] } ]); 456 | }); 457 | 458 | it('should map fields within the proper object 6', function () { 459 | var o = [ 460 | { a: [ { b: { c: 1 } }, { b: { c: 2 } } ] } 461 | , { a: [ { b: { c: 2 } }, { b: { c: 3 } } ] } ] 462 | , t = trans(o).mapff('a.b.c', 'a.b.d', [add, 1], square).value(); 463 | assert.deepEqual(t, [ 464 | { a: [ { b: { c: 1, d: 4 } }, { b: { c: 2, d: 9 } } ] } 465 | , { a: [ { b: { c: 2, d: 9 } }, { b: { c: 3, d: 16 } } ] } ]); 466 | }); 467 | 468 | it('should map fields within the proper object 7', function () { 469 | var o = [ { a: [ { b: { c: 1 } }, { b: { c: 2 } } ] }, { a: [ { b: { c: 3 } } ] } ] 470 | , t = trans(o).mapff('a.b.c', 'e', sum).value(); 471 | assert.deepEqual(t, [ 472 | { a: [ { b: { c: 1 } }, { b: { c: 2 } } ], e: 3 } 473 | , { a: [ { b: { c: 3 } } ], e: 3 } ]); 474 | }); 475 | 476 | it('should not skip missing fields 1', function () { 477 | var o = [ { a: { b: 1 } }, { a: { } }, { a: { b: 3 } } ] 478 | , t = trans(o).mapff('a.b', 'c', [add, 1]).value(); 479 | assert.deepEqual(t, [ 480 | { a: { b: 1 }, c: 2 } 481 | , { a: { }, c: 1 } 482 | , { a: { b: 3 }, c: 4 } 483 | ]); 484 | }); 485 | 486 | it('should not skip missing fields 2', function () { 487 | var o = { a: { b: {} } } 488 | , t = trans(o).mapff('a.b.c', 'd', [add, 1]).value(); 489 | assert.deepEqual(t, { a: { b: {} }, d: 1 }); 490 | }); 491 | 492 | it('should not skip missing fields 3', function () { 493 | var o = [ { a: { b: 1 } }, { a: { b: 2 } }, { a: {} }, { a: {} } ] 494 | , t = trans(o) 495 | .mapff('a.b', 'a.b', function (x) { return x || 100; }) 496 | .value(); 497 | assert.deepEqual(t, [ 498 | { a: { b: 1 } } 499 | , { a: { b: 2 } } 500 | , { a: { b: 100 } } 501 | , { a: { b: 100 } } ]); 502 | }); 503 | 504 | it('should allow nested fields', function () { 505 | var o = { a: { b: { c: 'abcde' } } } 506 | , t = trans(o) 507 | .mapff('a.b.c', 'a.d', 'toUpperCase', util.truncate) 508 | .value(); 509 | assert.deepEqual(t, { a: { b: { c: 'abcde' }, d: 'ABC' } }); 510 | }); 511 | 512 | it('should override fields with the same name 1', function () { 513 | var o = { a: { b: 3 } } 514 | , t = trans(o).mapff('a.b', 'a').value(); 515 | assert.deepEqual(t, { a: 3 }); 516 | }); 517 | 518 | it('should override fields with the same name 2', function () { 519 | var o = { 520 | a: [ { b: 1 }, { b: 2 }, { b: 3 } ] 521 | , e: [ { b: 1, c: 'a' }, { b: 2, c: 'b' } ] } 522 | , t = trans(o).mapff('a.b', 'e.c').value(); 523 | assert.deepEqual(t, { 524 | a: [ { b: 1 }, { b: 2 }, { b: 3 } ] 525 | , e: [ { b: 1, c: [ 1, 2, 3 ] }, { b: 2, c: [ 1, 2, 3 ] } ] 526 | }); 527 | }); 528 | 529 | it('should override fields with the same name 3', function () { 530 | var o = { 531 | a: [ { b: '1' }, { b: '2' }, { b: '3' } ] 532 | , e: [ { b: 1, c: 'a' }, { b: 2, c: 'b' } ] } 533 | , t = trans(o).mapff('a.b', 'e.c', '.', function (x) { return x + ':' + this.getIndex(); }).value(); 534 | assert.deepEqual(t, { 535 | a: [ { b: '1' }, { b: '2' }, { b: '3' } ] 536 | , e: [ 537 | { b: 1, c: [ '1:0', '2:1', '3:2' ] } 538 | , { b: 2, c: [ '1:0', '2:1', '3:2' ] } 539 | ] 540 | }); 541 | }); 542 | 543 | it('should work with nested arrays 1', function () { 544 | var o = [ [ { a: { b: 1 } }, { a: { b: 2 } } ], [ { a: { b: 3 } } ] ] 545 | , t = trans(o).mapff('a.b', 'a.c', [add, 2]).value(); 546 | assert.deepEqual(t, [ 547 | [ { a: { b: 1, c: 3 } }, { a: { b: 2, c: 4 } } ] 548 | , [ { a: { b: 3, c: 5 } } ] 549 | ]); 550 | }); 551 | 552 | it('should work with nested arrays 2', function () { 553 | var o = [ [ { a: { b: 1 } }, { a: { b: 2 } } ], [ { a: { b: 3 } } ] ] 554 | , t = trans(o).mapff('a.b', 'c', [add, 2]).value(); 555 | assert.deepEqual(t, [ 556 | [ { a: { b: 1 }, c: 3 }, { a: { b: 2 }, c: 4 } ] 557 | , [ { a: { b: 3 }, c: 5 } ] 558 | ]); 559 | }); 560 | 561 | it('should make all array indexes available', function () { 562 | var o = [ 563 | { a: { b: [ { c: [ 'a', 'b' ] }, { c: [ 'c', 'd', 'e' ] } ] } } 564 | , { a: { b: [ { c: [ 'f', 'g', 'k' ] }, { c: [ 'l', 'm', 'n', 'o' ] }, { c: [ 'r', 's' ] } ] } } 565 | , { a: { b: [ { c: [ 't', 'u', 'v', 'w' ] } ] } } 566 | ] 567 | , t = trans(o).mapff('a.b.c.', null, '.', function (x) { 568 | return x + ':' + this.getIndexes() + ':' + this.getIndex(); 569 | }).value(); 570 | assert.deepEqual(t, [ 571 | [ [ 'a:0,0,0:0', 'b:1,0,0:1' ], [ 'c:0,1,0:0', 'd:1,1,0:1', 'e:2,1,0:2' ] ] 572 | , [ 573 | [ 'f:0,0,1:0', 'g:1,0,1:1', 'k:2,0,1:2' ] 574 | , [ 'l:0,1,1:0', 'm:1,1,1:1', 'n:2,1,1:2', 'o:3,1,1:3' ] 575 | , [ 'r:0,2,1:0', 's:1,2,1:1' ] 576 | ] 577 | , [ [ 't:0,0,2:0', 'u:1,0,2:1', 'v:2,0,2:2', 'w:3,0,2:3' ] ] ]); 578 | }); 579 | 580 | it('should parse field paths properly', function () { 581 | var o = [ { names: { taught: [ 1, 2, 4 ] } }, { names: { taught: [ 1 ] } } ] 582 | , t = trans(o).mapff('names.taught', 'names.taughtCount', 'length').value(); 583 | assert.deepEqual(t, [ 584 | { names: { taught: [ 1, 2, 4 ], taughtCount: 3 } } 585 | , { names: { taught: [ 1 ], taughtCount: 1 } } 586 | ]); 587 | }); 588 | 589 | it('should replace the whole object when the destination field is null 1', function () { 590 | var o = [ 1, 2, 3 ] 591 | , t = trans(o).mapff(null, null, 'length').value(); 592 | assert.strictEqual(t, 3); 593 | }); 594 | 595 | it('should replace the whole object when the destination field is null 2', function () { 596 | var o = [ 1, 2, 3 ] 597 | , t = trans(o).mapff('.', null, [add, 1]).value(); 598 | assert.deepEqual(t, [2, 3, 4]); 599 | }); 600 | 601 | it('should replace the whole object when the destination field is null 3', function () { 602 | var o = [ { a: { b: [ 1, 2 ] } }, { a: { b: [ 4 ] } }, { a: { b: [ 6, 7 ,8 ] } } ] 603 | , t = trans(o).mapff('a.b').value(); 604 | assert.deepEqual(t, [ [ 1, 2 ], [ 4 ], [ 6, 7, 8 ] ]); 605 | }); 606 | 607 | it('should replace the whole object when the destination field is null 3b', function () { 608 | var o = [ { a: { b: [ 'a', 'b' ] } }, { a: { b: [ 'c' ] } }, { a: { b: [ 'd', 'e' ,'f' ] } } ] 609 | , t = trans(o).mapff('a.b.', null, function (x) { return x + this.getIndex(); }).value(); 610 | assert.deepEqual(t, [ [ 'a0', 'b1' ], [ 'c0' ], [ 'd0', 'e1', 'f2' ] ]); 611 | }); 612 | 613 | it('should replace the whole object when the destination field is null 4', function () { 614 | var o = [ 615 | { a: [ { b: [ 1, 2 ] }, { b: [ 3, 4 ] } ] } 616 | , { a: [ { b: [ 4 ] }, { b: [ 11, 22 ] }, { b: [ 199 ] } ] } 617 | , { a: [ { b: [ 6, 7 ,8 ] } ] } ] 618 | , t = trans(o).mapff('a.b').value(); 619 | assert.deepEqual(t, [ 620 | [ [ 1, 2 ], [ 3, 4 ] ] 621 | , [ [ 4 ], [ 11, 22 ], [ 199 ] ] 622 | , [ [ 6, 7, 8 ] ] 623 | ]); 624 | }); 625 | 626 | it('should handle falsy values 1', function () { 627 | var o = { a: 0 } 628 | , t = trans(o).mapff('a', 'b', square).value(); 629 | assert.deepEqual(t, { a: 0, b: 0 }); 630 | }); 631 | 632 | it('should handle falsy values 2', function () { 633 | var o = { a: { b: 0 } } 634 | , t = trans(o).mapff('a.b', 'a.c', square).value(); 635 | assert.deepEqual(t, { a: { b: 0, c: 0 }}); 636 | }); 637 | 638 | it('should handle falsy values 3', function () { 639 | var t = trans(0).mapff(null, null, square).value(); 640 | assert.strictEqual(t, 0); 641 | }); 642 | 643 | it('should handle empty arrays', function () { 644 | var t = trans([]).mapff('a.b', 'a.c', [ add, 1 ]).value(); 645 | assert.deepEqual(t, []); 646 | }); 647 | 648 | it('should handle empty objects', function () { 649 | var o = [ {}, {} ] 650 | , t = trans(o).mapff('a', 'b', [ add, 1 ]).value(); 651 | assert.deepEqual(t, [ { b: 1 }, { b: 1 } ]); 652 | }); 653 | 654 | it('should replace the whole object when the destination field is null 5', function () { 655 | var o = [ { a: { b: 1 } }, { a: { b: 2 } }, { a: {} }, { a: {} } ] 656 | , t = trans(o) 657 | .mapff('a.b', null, function (x) { return x || 100; }, [add, 2]) 658 | .value(); 659 | assert.deepEqual(t, [ 3, 4, 102, 102 ]); 660 | }); 661 | 662 | it('should allow setting a field on the source object 1', function () { 663 | var o = { a: { b: 1, c: 2 } } 664 | , t = trans(o).mapff('a', 'a.d', function (x) { return x.b + x.c; }).value(); 665 | assert.deepEqual(t, { a: { b: 1, c: 2, d: 3 } }); 666 | }); 667 | 668 | it('should allow setting a field on the source object 2', function () { 669 | var o = { a: [ { b: 1 }, { b: 2 } ] } 670 | , t = trans(o).mapff('a', 'a.c', 'b', [ add, 1 ]).value(); 671 | assert.deepEqual(t, { a: [ { b: 1, c: 2 }, { b: 2, c: 3 } ] }); 672 | }); 673 | 674 | it('should allow setting a field on the source object 3', function () { 675 | var o = [ { a: [ { b: 1 }, { b: 2 } ] }, { a: [ { b: 2 } ] } ] 676 | , t = trans(o).mapff(null, 'a.c', 'a', 'length').value(); 677 | assert.deepEqual(t, [ 678 | { a: [ { b: 1, c: 2 }, { b: 2, c: 2 } ] } 679 | , { a: [ { b: 2, c: 1 } ] } ]); 680 | }); 681 | 682 | it('should allow setting a field on the source object 4', function () { 683 | var o = [ { a: [ { b: { c: 1 } }, { b: { c: 2 } } ] }, { a: [ { b: { c: 3 } } ] } ] 684 | , t = trans(o).mapff(null, 'a.b.d', 'a', 'length').value(); 685 | assert.deepEqual(t, [ 686 | { a: [ { b: { c: 1, d: 2 } }, { b: { c: 2, d: 2 } } ] } 687 | , { a: [ { b: { c: 3, d: 1 } } ] } ]); 688 | }); 689 | 690 | it('should allow setting a field on the source object 5', function () { 691 | var o = [ { a: [ { b: { c: 1 } }, { b: { c: 2 } } ] }, { a: [ { b: { c: 3 } } ] } ] 692 | , t = trans(o).mapff('a.b', 'a.b.d', function (x) { return x.c + 1; }).value(); 693 | assert.deepEqual(t, [ 694 | { a: [ { b: { c: 1, d: 2 } }, { b: { c: 2, d: 3 } } ] } 695 | , { a: [ { b: { c: 3, d: 4 } } ] } ]); 696 | }); 697 | 698 | it('should allow setting a field on the source object 6', function () { 699 | var o = { a: 1, b: 2 } 700 | , t = trans(o).mapff(null, 'c', function (x) { return x.a + x.b; }).value(); 701 | assert.deepEqual(t, { a: 1, b: 2, c: 3 }); 702 | }); 703 | 704 | it('should allow setting a field on the source object 7', function () { 705 | var o = [ { b: 1, c: 3 }, { b: 2, c: 3 } ] 706 | , t = trans(o).mapff(null, 'd', function(x) { return x.b + x.c; }).value(); 707 | assert.deepEqual(t, [ { b: 1, c: 3, d: 4 }, { b: 2, c: 3, d: 5 } ]); 708 | }); 709 | 710 | it('should allow setting a field on the source object 8', function () { 711 | var o = { a: [ { b: 1, c: 3 }, { b: 2, c: 3 } ] } 712 | , t = trans(o).mapff('a', 'a.d', function(x) { return x.b + x.c; }).value(); 713 | assert.deepEqual(t, { a: [ { b: 1, c: 3, d: 4 }, { b: 2, c: 3, d: 5 } ] }); 714 | }); 715 | 716 | it('should allow setting a field on the source object 9', function () { 717 | var o = { a: [ { b: 1, c: 3 }, { b: 2, c: 3 } ] } 718 | , t = trans(o).mapff('a', 'd', '.', function(x) { return x.b + x.c; }).value(); 719 | assert.deepEqual(t, { a: [ { b: 1, c: 3 }, { b: 2, c: 3 } ], d: [ 4, 5 ] }); 720 | }); 721 | 722 | it('should allow setting a field on the source object 10', function () { 723 | var o = { a: [ { b: 1, c: 3 }, { b: 2, c: 3 } ] } 724 | , t = trans(o).mapff(null, 'a.d', function(obj) { return obj.a.length; }).value(); 725 | assert.deepEqual(t, { a: [ { b: 1, c: 3, d: 2 }, { b: 2, c: 3, d: 2 } ] }); 726 | }); 727 | 728 | it('should allow setting a field on the source object 11', function () { 729 | var o = { a: [ { b: 1, c: 3 }, { b: 2, c: 3 } ] } 730 | , t = trans(o).mapff(null, 'a.d', 'a', '.', 'b').value(); 731 | assert.deepEqual(t, { a: [ { b: 1, c: 3, d: [ 1, 2 ] }, { b: 2, c: 3, d: [ 1, 2 ] } ] }); 732 | }); 733 | 734 | it('should allow setting a field on the source object 12', function () { 735 | var o = [ 736 | { a: [ { b: 1, c: 3 }, { b: 2, c: 3 } ] }, 737 | { a: [ { b: 5, c: 3 }, { b: 7, c: 3 } ] } ] 738 | , t = trans(o).mapff('a', 'a.d', function(x) { return x.b + x.c; }).value(); 739 | assert.deepEqual(t, [ 740 | { a: [ { b: 1, c: 3, d: 4 }, { b: 2, c: 3, d: 5 } ] }, 741 | { a: [ { b: 5, c: 3, d: 8 }, { b: 7, c: 3, d: 10 } ] } 742 | ]); 743 | }); 744 | 745 | it('should allow setting a field on the source object - nested arrays', function () { 746 | var o = [ 747 | [ { a: { b: 1, c: 10 } }, { a: { b: 2, c: 20 } } ] 748 | , [ { a: { b: 3, c: 30 } } ] ] 749 | , t = trans(o).mapff('a', 'a.e', function (x) { return x.b + x.c; }).value(); 750 | assert.deepEqual(t, [ 751 | [ { a: { b: 1, c: 10, e: 11 } }, { a: { b: 2, c: 20, e: 22 } } ] 752 | , [ { a: { b: 3, c: 30, e: 33 } } ] 753 | ]); 754 | }); 755 | 756 | it('should fail when the destination field is not on an object', function () { 757 | assert.throws(function () { 758 | trans({ a: { b: 1 }, c: { d: 1 } }).mapff('a.b', 'c.d.e'); 759 | }, Error); 760 | }); 761 | }); 762 | }; 763 | --------------------------------------------------------------------------------