# Traversing the Parent-child relationship between objects: Part 2

This code builds on the code in the previous iteration. All of the following tests should work:

let myTree = Tree.from({a:{b:{k:2}}});
myTree.a.b;                         // { k: 2 }
myTree.a === myTree.a.b.__parent__; // true
myTree.c = {d:{l:5}};               // {d: {l: 5}}
myTree.c.__parent__ === myTree;     // true
myTree.a.b.__parent__ = myTree.c;   // {d: {l: 5}}
myTree.c.b.k;                       // 2
myTree.a.b;                         // undefined
// moving branches to other trees.
let anotherTree = new Tree();
myTree.a.l = 3;
myTree.a.l;     // 3
myTree.a.__parent__ = anotherTree;
anotherTree.a;  // {l: 3}
myTree.a;       // undefined
anotherTree.a.__parent__ === anotherTree; // true


It also introduces the Fragment class, which behaves both like a tree and a branch in the fact that it can be assigned to both and have child properties.

Upon an object or primitive being assigned, it is immediately converted to a branch or a leaf, respectively.

All leaves are dynamically inherited.

Code:

const [Tree, Fragment] = ( function(){
const LeafCache = Object.create( null ),
genLeaf = ( CLASS ) => {
return LeafCache[CLASS.name] || LeafCache[CLASS.name] = eval( class Leaf extends ${CLASS.name} { constructor(val) { super(val); } }; Leaf.prototype.__isleaf__ = true; Leaf ); }, TARGET = Symbol( '__target__' ), HANDLER = Symbol( '__handler__' ), PROXY = Symbol( '__proxy__' ), ISTREE = Symbol( '__istree__' ), { assign, defineProperty, entries, setPrototypeOf } = Object, { hasOwnProperty } = {}, convert = ( obj ) => { let res = obj instanceof Branch ? obj : new Branch( obj ); for( const key in obj ) { const value = obj[key]; if( hasOwnProperty.call( obj, key ) ) { if( '__isfragment__' !== key ) { if( typeof value === 'object' ) { res[key] = convert( value ); Object.defineProperty( res[key], '__parent__', { value: res[PROXY], configurable: false, protected: false } ); } else { let val; res[key] = new Proxy( val = new ( genLeaf( value.constructor ) )( value ), genHandler( val ) ); } } } } return res; }, getKey = ( obj, val ) => { for( const key in obj ) { const value = obj[key]; if( value[TARGET] === val ) { return key; } } }; let genHandler = ( _target ) => { return ( function(){ let res; const _raw = __raw__.bind( _target ), _keys = { '__raw__': _raw, [ISTREE]: true, [TARGET]: _target, get [PROXY]() { return res.proxy; }, get [HANDLER]() { return res; } }; res = { set: ( target, prop, value ) => { if( prop === '__parent__' ) { if( _keys[PROXY] instanceof Fragment ) { throw TypeError( 'Cannot set __parent__ on fragments.' ); } else if( typeof value === 'object' && value[ISTREE] ) { const key = getKey( target.__parent__, target ); if( target.__parent__[key] ) { delete target.__parent__[key]; } value[key] = target; return value; } else { throw TypeError( 'Cannot assign __parent__ to a non-tree value' ); } } if( typeof value === 'object' && value.constructor.name !== 'Leaf' ) { value = convert( value ); if( value[PROXY] instanceof Tree ) { throw TypeError( 'Cannot have a tree as a child of another tree.' ); } value = convert( value ); defineProperty( value, '__parent__', { value: _keys[PROXY], configurable: false, writable: true } ); } else if ( typeof value !== 'object' ) { let val; value = new Proxy( val = new ( genLeaf( value.constructor ) )( value ), genHandler( val ) ); } target[prop] = value; return value; }, get: ( target, prop ) => { if( prop === 'toJSON' ) { return _raw; } if( [HANDLER, PROXY, '__raw__', ISTREE, TARGET].includes( prop ) ) { return _keys[prop]; } return target[prop]; } }; return res; } )(); }; /** * Get the raw value of the tree, without all that proxy stuff. * @returns {Object} The raw object. Please not that objects will not be the same instances. * @memberof Tree# */ function __raw__() { let res = setPrototypeOf( {}, this.__proto__ ); for( const key in this ) { if( key.slice( 0, 2 ) === key.slice( -2 ) && key.slice( -2 ) === '__' ) { continue; } else { const value = this[key]; if( hasOwnProperty.call( this, key ) ) { res[key] = typeof value === 'object' ? __raw__.call( value[TARGET] ) : value; } } } return res; } /** * A class that enables navigation from child properties to parent. WIP - currently figuring out how to make new properties. * For all purposes this functions as intended, but it doesn't print well in the console. It even perserves prototypes. * @property {(Branch|Leaf)} * Properties. */ class Tree { /** * Constructs a new Tree instance. * @constructs Tree */ constructor() { return Tree.from( {} ); } /** * Attempt to create a tree from an existing object. * @param {Object} obj The object to convert to a tree. * @throws {TypeError} You probably passed it a primitive. * @returns {Tree} The resulting tree. */ static from( obj ) { const self = {}, res = new Proxy( setPrototypeOf( self, obj.__proto__ ), genHandler( self ) ); defineProperty( res[HANDLER], 'proxy', { value: res, configurable: false, protected: true } ); if( typeof obj !== 'object' ) { throw TypeError( 'Tree expects an object' ); } else { for( const key in obj ) { const value = obj[key]; let val; res[key] = typeof value === 'object' ? convert( value ) : new Proxy( val = new ( genLeaf( value.constructor ) )( value ), genHandler( val ) ); console.log( res[key][TARGET] ); } } defineProperty( res, '__istree__', { value: true, configurable: false, protected: true } ); return res; } static [Symbol.hasInstance]( obj ) { return obj[ISTREE] && obj.__istree__ || false; } } /** * A class that behaves similar to a tree and similar to a branch. It can be added to a tree like a branch. * @class */ class Fragment { /** * Construct a new fragment. * @constructs Fragment */ constructor() { return Fragment.from( {} ); } /** * Attempt to make a fragment from an existing object. * @param {Object} obj The object to use. * @returns {Fragment} The resulting fragment. */ static from( obj ) { const self = {}, res = new Proxy( setPrototypeOf( self, obj.__proto__ ), genHandler( self ) ); defineProperty( res[HANDLER], 'proxy', { value: res, configurable: false, protected: true } ); if( typeof obj !== 'object' ) { throw TypeError( 'Tree expects an object' ); } else { for( let key in obj ) { let value = obj[key]; res[key] = typeof value === 'object' ? convert( value ) : value; } } defineProperty( res, '__isfragment__', { value: true, configurable: false, protected: true } ); return res; } static [Symbol.hasInstance]( obj ) { return obj[ISTREE] && obj.__isfragment__ || false; } } class Branch { constructor( obj ) { let self = {}, res = new Proxy( setPrototypeOf( self, obj.__proto__ ), genHandler( self ) ); defineProperty( res[HANDLER], 'proxy', { value: res, configurable: false, protected: true } ); defineProperty( res, '__isbranch__', { value: true, configurable: false, protected: true } ); return res; } static [Symbol.hasInstance]( obj ) { return obj[ISTREE] && obj.__isbranch__ || false; } } return [Tree, Fragment]; } )(); /** * A class that shows that an item is a terminal node. Parent properties cannot be accessed by this node. * @alias Brance * @class Leaf * @extends Primitive */ /** * A class that simply shows that it is an inner object of a Tree. * @alias Branch * @class Branch * @property {(Tree|Branch)} __parent__ The parent element. This can be changed to move the object to another tree or branch. */  This is an iterative review. Previous iteration: Traversing the parent-child relationship between objects ## 1 Answer ## Errors I tried this both in jsFiddle (with and without JS 1.7 and Babel) as well as NodeJS but saw a few errors. The first one was:  return LeafCache[CLASS.name] || LeafCache[CLASS.name] = eval( class Leaf extends${CLASS.name} {
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^


ReferenceError: Invalid left-hand side in assignment

So then I tried wrapping the second conditional in parentheses and that appeared to get me past that error:

return LeafCache[CLASS.name] || (LeafCache[CLASS.name] = eval( class Leaf extends \${CLASS.name} {
constructor(val) {
super(val);
}
}; Leaf.prototype.__isleaf__ = true; Leaf ));


But then I see another error:

               Object.defineProperty( res[key], '__parent__', {
^


TypeError: Cannot redefine property: __parent__

## Other feedback

Other than those errors I don't see much I would change - maybe a few places where variables can be declared using const instead of let - e.g. genHandler, res within the function __raw__, so as to avoid unintentional re-assignment...

Some of this code appears to be identical to the changes added in revision 7 to your previous post but with the Leaf` class added. Was that in response to the suggestion at the end of rm-'s answer?

but still the code could be simplified a little bit, split into separate small utility functions, organized in a way to be easy to read from top to bottom, and such.1

• The Leaf class is mostly in response to the answer, yes, and also to show dynamic inheritance. Thanks. I think one more post should do this project. It'll be ready in about a week. or 2. – FreezePhoenix Sep 27 '18 at 12:03