Skip to content

Commit 5d1eb2d

Browse files
committed
Implement Lib.expandObjectPaths
1 parent 3e18a98 commit 5d1eb2d

File tree

6 files changed

+141
-14
lines changed

6 files changed

+141
-14
lines changed

src/lib/extend.js

+15-6
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,19 @@ var isPlainObject = require('./is_plain_object.js');
1313
var isArray = Array.isArray;
1414

1515
exports.extendFlat = function() {
16-
return _extend(arguments, false, false);
16+
return _extend(arguments, false, false, false);
1717
};
1818

1919
exports.extendDeep = function() {
20-
return _extend(arguments, true, false);
20+
return _extend(arguments, true, false, false);
2121
};
2222

2323
exports.extendDeepAll = function() {
24-
return _extend(arguments, true, true);
24+
return _extend(arguments, true, true, false);
25+
};
26+
27+
exports.extendDeepNoArrays = function() {
28+
return _extend(arguments, true, false, true);
2529
};
2630

2731
/*
@@ -41,7 +45,7 @@ exports.extendDeepAll = function() {
4145
* Warning: this might result in infinite loops.
4246
*
4347
*/
44-
function _extend(inputs, isDeep, keepAllKeys) {
48+
function _extend(inputs, isDeep, keepAllKeys, noArrayCopies) {
4549
var target = inputs[0],
4650
length = inputs.length;
4751

@@ -54,8 +58,13 @@ function _extend(inputs, isDeep, keepAllKeys) {
5458
src = target[key];
5559
copy = input[key];
5660

61+
// Stop early and just transfer the array if array copies are disallowed:
62+
if(noArrayCopies && isArray(copy)) {
63+
target[key] = copy;
64+
}
65+
5766
// recurse if we're merging plain objects or arrays
58-
if(isDeep && copy && (isPlainObject(copy) || (copyIsArray = isArray(copy)))) {
67+
else if(isDeep && copy && (isPlainObject(copy) || (copyIsArray = isArray(copy)))) {
5968
if(copyIsArray) {
6069
copyIsArray = false;
6170
clone = src && isArray(src) ? src : [];
@@ -64,7 +73,7 @@ function _extend(inputs, isDeep, keepAllKeys) {
6473
}
6574

6675
// never move original objects, clone them
67-
target[key] = _extend([clone, copy], isDeep, keepAllKeys);
76+
target[key] = _extend([clone, copy], isDeep, keepAllKeys, noArrayCopies);
6877
}
6978

7079
// don't bring in undefined values, except for extendDeepAll

src/lib/index.js

+34
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ var extendModule = require('./extend');
5757
lib.extendFlat = extendModule.extendFlat;
5858
lib.extendDeep = extendModule.extendDeep;
5959
lib.extendDeepAll = extendModule.extendDeepAll;
60+
lib.extendDeepNoArrays = extendModule.extendDeepNoArrays;
6061

6162
var loggersModule = require('./loggers');
6263
lib.log = loggersModule.log;
@@ -541,6 +542,39 @@ lib.objectFromPath = function(path, value) {
541542

542543
return obj;
543544
};
545+
/**
546+
* Iterate through an object in-place, converting dotted properties to objects.
547+
*
548+
* @example
549+
* lib.expandObjectPaths('nested.test[2].path', 'value');
550+
* // returns { nested: { test: [null, null, { path: 'value' }]}
551+
*
552+
*/
553+
// Store this to avoid recompiling regex on every prop since this may happen many
554+
// many times for animations.
555+
// TODO: Premature optimization? Remove?
556+
var dottedPropertyRegex = /^([^\.]*)\../;
557+
558+
lib.expandObjectPaths = function (data) {
559+
var data, parts, match, key, prop, datum;
560+
if(typeof data === 'object' && !Array.isArray(data)) {
561+
for(key in data) {
562+
if(data.hasOwnProperty(key)) {
563+
if(match = key.match(dottedPropertyRegex)) {
564+
datum = data[key];
565+
prop = match[1];
566+
567+
delete data[key];
568+
569+
data[prop] = lib.extendDeepNoArrays(data[prop] || {}, lib.objectFromPath(key, lib.expandObjectPaths(datum))[prop]);
570+
} else {
571+
data[key] = lib.expandObjectPaths(data[key]);
572+
}
573+
}
574+
}
575+
}
576+
return data;
577+
};
544578

545579
/**
546580
* Converts value to string separated by the provided separators.

src/lib/merge_keyframes.js renamed to src/lib/merge_frames.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ var extend = require('./extend');
2121
*
2222
* Returns: a third object with the merged content
2323
*/
24-
module.exports = function mergeKeyframes(target, source) {
24+
module.exports = function mergeFrames(target, source) {
2525
var result;
2626

2727
result = extend.extendDeep({}, target);

test/jasmine/tests/extend_test.js

+21
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ var extendModule = require('@src/lib/extend.js');
22
var extendFlat = extendModule.extendFlat;
33
var extendDeep = extendModule.extendDeep;
44
var extendDeepAll = extendModule.extendDeepAll;
5+
var extendDeepNoArrays = extendModule.extendDeepNoArrays;
56

67
var str = 'me a test',
78
integer = 10,
@@ -418,3 +419,23 @@ describe('extendDeepAll', function() {
418419
expect(ori.arr[2]).toBe(undefined);
419420
});
420421
});
422+
423+
describe('extendDeepNoArrays', function() {
424+
'use strict';
425+
426+
it('does not copy arrays', function() {
427+
var src = {foo: {bar: [1, 2, 3], baz: [5, 4, 3]}};
428+
var tar = {foo: {bar: [4, 5, 6], bop: [8, 2, 1]}};
429+
var ext = extendDeepNoArrays(tar, src);
430+
431+
expect(ext).not.toBe(src);
432+
expect(ext).toBe(tar);
433+
434+
expect(ext.foo).not.toBe(src.foo);
435+
expect(ext.foo).toBe(tar.foo);
436+
437+
expect(ext.foo.bar).toBe(src.foo.bar);
438+
expect(ext.foo.baz).toBe(src.foo.baz);
439+
expect(ext.foo.bop).toBe(tar.foo.bop);
440+
});
441+
});

test/jasmine/tests/lib_test.js

+63
Original file line numberDiff line numberDiff line change
@@ -475,6 +475,69 @@ describe('Test lib.js:', function() {
475475
});
476476
});
477477

478+
describe('expandObjectPaths', function() {
479+
it('returns the original object', function() {
480+
var x = {};
481+
expect(Lib.expandObjectPaths(x)).toBe(x);
482+
});
483+
484+
it('unpacks top-level paths', function() {
485+
var input = {'marker.color': 'red', 'marker.size': [1, 2, 3]};
486+
var expected = {marker: {color: 'red', size: [1, 2, 3]}};
487+
expect(Lib.expandObjectPaths(input)).toEqual(expected);
488+
});
489+
490+
it('unpacks recursively', function() {
491+
var input = {'marker.color': {'red.certainty': 'definitely'}};
492+
var expected = {marker: {color: {red: {certainty: 'definitely'}}}};
493+
expect(Lib.expandObjectPaths(input)).toEqual(expected);
494+
});
495+
496+
it('unpacks deep paths', function() {
497+
var input = {'foo.bar.baz': 'red'};
498+
var expected = {foo: {bar: {baz: 'red'}}};
499+
expect(Lib.expandObjectPaths(input)).toEqual(expected);
500+
});
501+
502+
it('unpacks non-top-level deep paths', function() {
503+
var input = {color: {'foo.bar.baz': 'red'}};
504+
var expected = {color: {foo: {bar: {baz: 'red'}}}};
505+
expect(Lib.expandObjectPaths(input)).toEqual(expected);
506+
});
507+
508+
it('merges dotted properties into objects', function() {
509+
var input = {marker: {color: 'red'}, 'marker.size': 8};
510+
var expected = {marker: {color: 'red', size: 8}};
511+
expect(Lib.expandObjectPaths(input)).toEqual(expected);
512+
});
513+
514+
it('merges objects into dotted properties', function() {
515+
var input = {'marker.size': 8, marker: {color: 'red'}};
516+
var expected = {marker: {color: 'red', size: 8}};
517+
expect(Lib.expandObjectPaths(input)).toEqual(expected);
518+
});
519+
520+
it('retains the identity of nested objects', function() {
521+
var input = {marker: {size: 8}};
522+
var origNested = input.marker;
523+
var expanded = Lib.expandObjectPaths(input);
524+
var newNested = expanded.marker;
525+
526+
expect(input).toBe(expanded);
527+
expect(origNested).toBe(newNested);
528+
});
529+
530+
it('retains the identity of nested arrays', function() {
531+
var input = {'marker.size': [1, 2, 3]};
532+
var origArray = input['marker.size'];
533+
var expanded = Lib.expandObjectPaths(input);
534+
var newArray = expanded.marker.size;
535+
536+
expect(input).toBe(expanded);
537+
expect(origArray).toBe(newArray);
538+
});
539+
});
540+
478541
describe('coerce', function() {
479542
var coerce = Lib.coerce,
480543
out;

test/jasmine/tests/keyframe_computation.js renamed to test/jasmine/tests/merge_frame_test.js

+7-7
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
var mergeKeyframes = require('@src/lib/merge_keyframes');
1+
var mergeFrames = require('@src/lib/merge_frames');
22

3-
describe('Test mergeKeyframes', function() {
3+
describe('Test mergeFrames', function() {
44
'use strict';
55

66
it('returns a new object', function() {
77
var f1 = {};
88
var f2 = {};
9-
var result = mergeKeyframes(f1, f2);
9+
var result = mergeFrames(f1, f2);
1010
expect(result).toEqual({});
1111
expect(result).not.toBe(f1);
1212
expect(result).not.toBe(f2);
@@ -15,15 +15,15 @@ describe('Test mergeKeyframes', function() {
1515
it('overrides properties of target with those of source', function() {
1616
var tar = {xaxis: {range: [0, 1]}};
1717
var src = {xaxis: {range: [3, 4]}};
18-
var out = mergeKeyframes(tar, src);
18+
var out = mergeFrames(tar, src);
1919

2020
expect(out).toEqual({xaxis: {range: [3, 4]}});
2121
});
2222

2323
it('merges dotted properties', function() {
2424
var tar = {};
2525
var src = {'xaxis.range': [0, 1]};
26-
var out = mergeKeyframes(tar, src);
26+
var out = mergeFrames(tar, src);
2727

2828
expect(out).toEqual({'xaxis.range': [0, 1]});
2929
});
@@ -32,15 +32,15 @@ describe('Test mergeKeyframes', function() {
3232
it('xaxis.range', function() {
3333
var tar = {xaxis: {range: [0, 1]}};
3434
var src = {'xaxis.range': [3, 4]};
35-
var out = mergeKeyframes(tar, src);
35+
var out = mergeFrames(tar, src);
3636

3737
expect(out).toEqual({xaxis: {range: [3, 4]}});
3838
});
3939

4040
it('xaxis.range.0', function() {
4141
var tar = {xaxis: {range: [0, 1]}};
4242
var src = {'xaxis.range.0': 3};
43-
var out = mergeKeyframes(tar, src);
43+
var out = mergeFrames(tar, src);
4444

4545
expect(out).toEqual({xaxis: {range: [3, 1]}});
4646
});

0 commit comments

Comments
 (0)