diff --git a/index.js b/index.js index 889dd4e..807c6dc 100755 --- a/index.js +++ b/index.js @@ -1,9 +1,6 @@ 'use strict'; -var _get = require('lodash.get'); - var logger; -var isArray = Array.isArray; var EVENT_TYPES = { DATA_MISSING: 'dataMissing', TYPE_MISMATCH: 'typeMismatch' @@ -28,87 +25,111 @@ function isEmpty(obj) { * @param val - value of which to check type */ function getType(val) { - var type; - - if (isArray(val)) { - type = 'array'; - } else if (val === null) { - type = 'null'; - } else { - type = typeof val; - } - - return type; + return toString.call(val).slice(8, -1); } /** * Log event * @param path - a string representation of the lookup * @param eventType - event type from EVENT_TYPES enum - * @param defaultValue - default when data is absent, also used to check type + * @param defaultResult - default when data is absent, also used to check type * @param logType - logger method to use */ -function log(eventType, path, defaultValue, logType) { +function log(eventType, path, defaultResult, logType) { if (logger[logType]) { - logger[logType]('event: %s, path: %s, default: %s', eventType, path, defaultValue); + logger[logType]('event: %s, path: %s, default: %s', eventType, path, defaultResult); + } +} + +/** + * Attempt to get object path + * @param obj - the object where we are extracting data + * @param path - a string representation of the lookup + */ +function getResult(obj, path) { + var lastIndex = 0; + var index; + var len = path.length; + var cur = obj; + + // transform array syntax to use dot delimeters for easier parsing + if (path.indexOf('[') !== -1) { + path = path.replace(/\[/g, '.').replace(/\]/g, ''); } + + do { + if (cur == null) { // eslint-disable-line eqeqeq + return; + } + + index = path.indexOf('.', lastIndex); + if (index === -1) { + index = len; + } + + cur = cur[path.slice(lastIndex, index)]; + lastIndex = index + 1; + } while (lastIndex < len); + + return cur; } /** - * Attempt to get object path, otherwise use defaultValue - * @param object - the object where we are extracting data + * Attempt to get object path, otherwise use defaultResult + * @param obj - the object where we are extracting data * @param path - a string representation of the lookup - * @param defaultValue - default when data is absent, also used to check type + * @param defaultResult - default when data is absent, also used to check type * @param logType - logger method to use */ -function access(object, path, defaultValue, logType) { +function access(obj, path, defaultResult, logType) { var eventType; - var result = _get(object, path); + var result = getResult(obj, path); + var typeofResult = getType(result); - var typeofDefaultValue = getType(defaultValue); + var typeofDefaultResult = getType(defaultResult); - if (typeofDefaultValue === 'undefined') { - defaultValue = ''; - typeofDefaultValue = 'string'; - } else if (typeofDefaultValue === 'object' && isEmpty(defaultValue)) { - defaultValue = {__isEmpty: true}; + if (typeofDefaultResult === 'Undefined') { + defaultResult = ''; + typeofDefaultResult = 'String'; + } else if (typeofDefaultResult === 'Object' && isEmpty(defaultResult)) { + defaultResult = {__isEmpty: true}; } - if (typeofResult !== 'undefined') { - if (typeofResult !== typeofDefaultValue) { + if (typeofResult !== 'Undefined') { + if (typeofResult !== typeofDefaultResult) { eventType = EVENT_TYPES.TYPE_MISMATCH; - result = defaultValue; + result = defaultResult; } } else { eventType = EVENT_TYPES.DATA_MISSING; - result = defaultValue; + result = defaultResult; } if (logger && logType && eventType) { - log(eventType, path, defaultValue, logType); + log(eventType, path, defaultResult, logType); } return result; } -function need(object, path, defaultValue) { - return access(object, path, defaultValue, 'warn'); +function need(obj, path, defaultResult) { + return access(obj, path, defaultResult, 'warn'); } -function get(object, path, defaultValue) { - return access(object, path, defaultValue); +function get(obj, path, defaultResult) { + return access(obj, path, defaultResult); } /** * Return whether given object has path with value that is not null or undefined - * @param object - the object where we are extracting data + * @param obj - the object where we are extracting data * @param path - a string representation of the lookup */ -function has(object, path) { - var result = _get(object, path); +function has(obj, path) { + var result = getResult(obj, path); var typeofResult = getType(result); - result = !(typeofResult === 'undefined' || typeofResult === 'null'); + result = !(typeofResult === 'Undefined' || typeofResult === 'Null'); return result; } diff --git a/package.json b/package.json index 168fce3..bbe679d 100755 --- a/package.json +++ b/package.json @@ -5,8 +5,9 @@ "main": "index.js", "scripts": { "lint": "eslint *.js **/*.js", - "test": "istanbul cover _mocha test/index.js && node test/perf.js", - "build": "yarn run lint && yarn test", + "test": "istanbul cover _mocha test/index.js", + "perf": "node test/perf.js", + "build": "yarn run lint && yarn test && yarn perf", "rep": "open coverage/lcov-report/index.html", "all": "yarn run lint && yarn test && yarn run rep", "wrap": "rm -rf node_modules; rm yarn.lock; yarn cache clean && yarn" @@ -21,9 +22,7 @@ ], "author": "Yoni Medoff", "license": "MIT", - "dependencies": { - "lodash.get": "^4.4.2" - }, + "dependencies": {}, "devDependencies": { "chai": "^3.5.0", "chai-spies": "^0.7.1", diff --git a/test/index.js b/test/index.js index a7fa91f..84c339d 100755 --- a/test/index.js +++ b/test/index.js @@ -24,6 +24,9 @@ var objectWithArrays = { { c: 'test' } + ], + d: [ + [1, 2, 3] ] } }; @@ -37,10 +40,18 @@ var emptyObjectWithPrototype = Object.create({prototypeProperty: 1}); var value = r[accessor](basicObject, 'a.b.c', 1); expect(value).to.equal(0); }); - it('accesses array elements', function () { + it('accesses array elements at end of path', function () { + var value = r[accessor](objectWithArrays, 'a.b[0]', {}); + expect(value).to.equal(objectWithArrays.a.b[0]); + }); + it('accesses array elements in middle of path', function () { var value = r[accessor](objectWithArrays, 'a.b[0].c'); expect(value).to.equal(objectWithArrays.a.b[0].c); }); + it('accesses nested arrray elements', function () { + var value = r[accessor](objectWithArrays, 'a.d[0][1]', 1); + expect(value).to.equal(objectWithArrays.a.d[0][1]); + }); it('gets object value for basic object', function () { var value = r[accessor](basicObject, 'a.b', {}); expect(value).to.deep.equal(basicObject.a.b); @@ -53,6 +64,10 @@ var emptyObjectWithPrototype = Object.create({prototypeProperty: 1}); var value = r[accessor](basicObject, 'a.b.d', 'default'); expect(value).to.equal('default'); }); + it('safely handles missing results in middle of path', function () { + var value = r[accessor](basicObject, 'a.asdf[1][2]', null); + expect(value).to.equal(null); + }); it('assumes default empty string when not passed in', function () { var value = r[accessor](basicObject, 'a.b.d'); expect(value).to.equal(''); @@ -73,7 +88,7 @@ var emptyObjectWithPrototype = Object.create({prototypeProperty: 1}); var value = r[accessor](basicObject, 'a.b.d', emptyObjectWithPrototype); expect(value).to.deep.equal(defaultEmptyObject); }); - it('defaults to defaultValue when result is undefined', function () { + it('defaults to defaultResult when result is undefined', function () { var value = r[accessor](basicObject, 'a.b.e', 'default'); expect(value).to.deep.equal('default'); }); diff --git a/test/perf.js b/test/perf.js index c053041..19407e4 100755 --- a/test/perf.js +++ b/test/perf.js @@ -49,7 +49,7 @@ function buildObject(breadth, depth, type) { return obj; } -function retrieverLoop(accessor, breadth, depth, calls, obj, paths, defaultValue, useWrongPath) { +function retrieverLoop(accessor, breadth, depth, calls, obj, paths, defaultResult, useWrongPath) { var callsDone = 0; // keep repeating the breadth loop as necessary while (callsDone < calls) { @@ -62,7 +62,7 @@ function retrieverLoop(accessor, breadth, depth, calls, obj, paths, defaultValue if (useWrongPath) { path += 'asdf'; // e.g. look for .stringasdf instead of .string } - r[accessor](obj, path, defaultValue); + r[accessor](obj, path, defaultResult); // stop breadth loop early when we hit the calls number if (++callsDone >= calls) { @@ -85,7 +85,7 @@ types.forEach(function (type) { scenarios.push({ name: 'access ' + type, type: type, - defaultValue: defaults[type], + defaultResult: defaults[type], useWrongPath: false }); }); @@ -94,7 +94,7 @@ types.forEach(function (type) { scenarios.push({ name: 'access ' + type + ' with wrong default', type: type, - defaultValue: null, // takes advantage of `null` being its own type in retriever + defaultResult: null, // takes advantage of `null` being its own type in retriever useWrongPath: false }); }); @@ -103,7 +103,7 @@ types.forEach(function (type) { scenarios.push({ name: 'access ' + type + ' with wrong path', type: type, - defaultValue: defaults[type], + defaultResult: defaults[type], useWrongPath: true }); }); @@ -123,14 +123,14 @@ var objectVariations = [ var breadth = objectVariation.breadth; var depth = objectVariation.depth; var type = scenario.type; - var defaultValue = scenario.defaultValue; + var defaultResult = scenario.defaultResult; var useWrongPath = scenario.useWrongPath; var label = 'calls:' + calls + ', breadth:' + breadth + ', depth:' + depth; var obj = buildObject(breadth, depth, type); var paths = buildPaths(breadth, depth, type); console.time(label); - retrieverLoop(accessor, breadth, depth, calls, obj, paths, defaultValue, useWrongPath); + retrieverLoop(accessor, breadth, depth, calls, obj, paths, defaultResult, useWrongPath); console.timeEnd(label); }); }); diff --git a/yarn.lock b/yarn.lock index 4e15c77..ba4c0f7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -760,10 +760,6 @@ lodash.create@3.1.1: lodash._basecreate "^3.0.0" lodash._isiterateecall "^3.0.0" -lodash.get@^4.4.2: - version "4.4.2" - resolved "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99" - lodash.isarguments@^3.0.0: version "3.1.0" resolved "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz#2f573d85c6a24289ff00663b491c1d338ff3458a"