Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Convert dot notation string to object and lookup reference object

I have an dot notated array such as:

var base = ['a.b.c','a.e','h'];

I also have a reference object:

var reference = { 
  a: { 
    b: { 
      c:"Hello", 
      d:"you" 
    }, 
    e:"beautiful",
    f:"and"
  },
  g:"kind",
  h:"World" 
};

I need to build an object out of the base notated string and get the values from the reference object. Unfortunately, I cannot change the base nor the reference.

I have it working for each individual part of the array but the problem is when I try to merge the object. It overwrites the value a.b.c with a.e.

My desired output would be:

var desiredOutput = { 
  a: { 
    b: { 
      c:"Hello" 
    }, 
    e:"beautiful" 
  }, 
  h:"World" 
};

However, with the code below my output is:

var output = { 
    a: { 
        e: "beautiful" 
    },
    h: "World" 
};

Any help would be appreciated. I am limited to < ES5 but could use some polyfills such as Object.assign if needed.

function convert(base,reference) {
    var obj = {};

    var getObjectValue = function(string, reference) {
        var i = 0;
        while (reference && i < string.length) {
            reference = reference[string[i++]];
        }
        return reference;
    };

    for (var n = 0; n < base.length; n++) {    
        var s = base[n].split("."),
            x = obj;

        for(var i = 0; i < s.length-1; i++) {
            x = x[s[i]] = {}; 
        }

        x[s[i]] = getObjectValue(s,reference);
    }
    return obj;
}

var base = ['a.b.c','a.e','h'];

var reference = { 
  a: { 
    b: { 
      c:"Hello", 
      d:"you" 
    }, 
    e:"beautiful",
    f:"and"
  },
  g:"kind",
  h:"World" 
};

var desiredOutput = { 
  a: { 
    b: { 
      c:"Hello" 
    }, 
    e:"beautiful" 
  }, 
  h:"World" 
};

console.log(convert(base,reference));
like image 288
shd.lux Avatar asked Mar 25 '26 14:03

shd.lux


2 Answers

With your

x = x[s[i]] = {}; 

You're unconditionally overwriting the s[i] property even if it's already been populated. Only assign a new object if another object doesn't already exist at that property.

if (x[s[i]]) {
  x = x[s[i]];
} else {
  x = x[s[i]] = {};
}

function convert(base,reference) {
    var obj = {};

    var getObjectValue = function(string, reference) {
        var i = 0;
        while (reference && i < string.length) {
            reference = reference[string[i++]];
        }
        return reference;
    };

    for (var n = 0; n < base.length; n++) {    
        var s = base[n].split("."),
            x = obj;

        for(var i = 0; i < s.length-1; i++) {
            if (x[s[i]]) {
              x = x[s[i]];
            } else {
              x = x[s[i]] = {};
            }
        }

        x[s[i]] = getObjectValue(s,reference);
    }

    return obj;

}

var base = ['a.b.c','a.e','h'];

var reference = { 
  a: { 
    b: { 
      c:"Hello", 
      d:"you" 
    }, 
    e:"beautiful",
    f:"and"
  },
  g:"kind",
  h:"World" 
};

var desiredOutput = { 
  a: { 
    b: { 
      c:"Hello" 
    }, 
    e:"beautiful" 
  }, 
  h:"World" 
};


console.log(convert(base,reference));

This is how I'd approach it:

var base = ['a.b.c','a.e','h'];
var reference = { 
  a: { 
    b: { 
      c:"Hello", 
      d:"you" 
    }, 
    e:"beautiful",
    f:"and"
  },
  g:"kind",
  h:"World" 
};

const results = {};
// Helper function to get the nested value from an array of properties:
const getVal = props => props.reduce((a, b) => a[b], reference);
for (const propStr of base) {
  // Turn string into an array of properties:
  const props = propStr.split('.');
  // Get nested value to assign at the end:
  const val = getVal(props);
  // Get last property to assign on the last object:
  const lastProp = props.pop();
  let obj = results;
  // Iterate through to the nested object on the `results`,
  // creating objects on the way only if they don't exist yet:
  while (props.length) {
    const prop = props.shift();
    if (obj[prop]) obj = obj[prop];
    else {
      obj[prop] = {};
      obj = obj[prop];
    }
  }
  obj[lastProp] = val;
}
console.log(results);
like image 177
CertainPerformance Avatar answered Mar 27 '26 03:03

CertainPerformance


This can be achieved without while loops!

I've only used es6's Array.prototype.reduce (and semantics, but you can convert/transpile those your self). You can also easily poly/ponyfill it, see this article.

const base = ['a.b.c', 'a.e', 'h'];

const reference = { 
  a: { 
    b: { 
      c: 'Hello', 
      d: 'you' 
    }, 
    e: 'beautiful',
    f: 'and'
  },
  g: 'kind',
  h: 'World' 
};

const isObject = item => (item && typeof item === 'object' && !Array.isArray(item) && item !== null);

const pick = (obj, keys) => keys.split('.').reduce((prev, key) => {
  const ret = (prev || obj);
  if (isObject(ret) && key in ret) {
    return ret[key];
  }

  return prev;
}, null);

const extend = (target, source) => {
  if (isObject(target) && isObject(source)) {
    Object.keys(source).forEach((key) => {
      if (isObject(source[key])) {
        if (!target[key] || !isObject(target[key])) {
          target[key] = source[key];
        }
        extend(target[key], source[key]);
      } else {
        Object.assign(target, { [key]: source[key] });
      }
    });
  }
  return target || source;
};

const fromDotNotation = (dotNotation, value) => {
  const ret = {};
  dotNotation.split('.').reduce((prev, key, i, self) => {
    return prev[key] = (i === self.length - 1) ? value : prev[key] || {};
  }, ret);

  return ret;
}

const fromDotNotations = (source, dotNotations) => dotNotations.reduce((prev, dotNotation) => {
  return extend(prev, fromDotNotation(dotNotation, pick(source, dotNotation)));
}, {});

const output = fromDotNotations(reference, base);

console.log(JSON.stringify(output, null, 2));

In here, we want to be able to build an object from any dot notation, with an initial value at its tail. We'll use a plucking method to pick the initial value from the reference object. Looping over multiple dot notations, we can create an array of constructed objects. simply using a merging method, we can create a target object to which we want to merge our constructed objects into.

like image 28
Shane Avatar answered Mar 27 '26 04:03

Shane



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!