Skip to content
This repository was archived by the owner on Feb 16, 2022. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 62 additions & 41 deletions index.js
Original file line number Diff line number Diff line change
@@ -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'
Expand All @@ -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;
}
Expand Down
9 changes: 4 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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",
Expand Down
19 changes: 17 additions & 2 deletions test/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ var objectWithArrays = {
{
c: 'test'
}
],
d: [
[1, 2, 3]
]
}
};
Expand All @@ -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);
Expand All @@ -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('');
Expand All @@ -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');
});
Expand Down
14 changes: 7 additions & 7 deletions test/perf.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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) {
Expand All @@ -85,7 +85,7 @@ types.forEach(function (type) {
scenarios.push({
name: 'access ' + type,
type: type,
defaultValue: defaults[type],
defaultResult: defaults[type],
useWrongPath: false
});
});
Expand All @@ -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
});
});
Expand All @@ -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
});
});
Expand All @@ -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);
});
});
Expand Down
4 changes: 0 additions & 4 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down