1

I have csv files that I am reading using nodeJS. I convert each file to text before reading.

Each line in the file have data delimited with =.

Each line looks something like

data.location.degree.text=sometexthere

The first portion before the "=" represents an index to a JSON object in my app. My aim is to parse this data and build a JSON representation of it so that the line above becomes

data:{
  location:{
    degree:{
      text: 'sometexthere'
    }
  }
}

Using javascript/nodejs; How can I convert a string which is supposed to represent a sequence of nested JSON keys, into a JSON object like above?

vzwick
  • 11,008
  • 5
  • 43
  • 63
C Scott
  • 31
  • 1
  • 3

2 Answers2

10

You could split the path and make a check if the following element exist. If not assign an object to the new property.

Return then the value of the property.

At the end assign the value.

function setValue(object, path, value) {
    path = path.replace(/[\[]/gm, '.').replace(/[\]]/gm, ''); //to accept [index]
    var keys = path.split('.'),
        last = keys.pop();

    keys.reduce(function (o, k) { return o[k] = o[k] || {}; }, object)[last] = value;
}

var data = {};

setValue(data, 'location.degree.text', 'sometexthere');
console.log(data);
SwiftNinjaPro
  • 787
  • 8
  • 17
Nina Scholz
  • 376,160
  • 25
  • 347
  • 392
2

// result container
var res = {};

// input data
var inp = [
    'data.location.degree.text=sometexthere',
    'data.otherLocation.degree.otherText=foo',
    'data.location.degree.otherText=bar',
    'we.can.handle.values.that.are_undefined=',
    'we.can.handle.values.that.contain_equals_signs=yes=we=can'
];

// recursive function
var pathToObject = function(resultReference, path)
{
    // split path on dots
    // e.g. data.location.degree.text=sometexthere
    // -> ["data", "location", "degree", "text=sometexthere"]
    var splitPathParts = path.split('.');

    // if there is only one part, we're at the end of our path expression
    // e.g. ["text=sometexthere"]
    if (splitPathParts.length === 1){
        // split "text=sometexthere" into ["text", "sometexthere"]
        var keyAndValue = splitPathParts[0].split('=');

        // set foo = bar on our result object reference
        resultReference[keyAndValue.shift()] = keyAndValue.join('=');
        return;
    }
  
    // the first element of the split array is our current key
    // e.g. for ["data", "location", "degree", "text=sometexthere"],
    // the currentKey would be "data";
    var currentKey = splitPathParts.shift();

    // if our object does not yet contain the current key, set it to an empty object
    resultReference[currentKey] || (resultReference[currentKey] = {});
    
    // recursively call ourselves, passing in
    // the nested scope and the rest of the path.
    // e.g. { data : {} } and 'location.degree.text=sometexthere'
    pathToObject(resultReference[currentKey], splitPathParts.join('.'));
}

for (var i = 0; i < inp.length; i++)
{
    pathToObject(res, inp[i]);
}
console.log(res);

ES6 syntax makes things slightly more terse:

'use strict';

const pathToObject = (resultReference, path) => {
  let [currentKey, ...restOfPath] = path.split('.');
  
  if (restOfPath.length === 0) {
    let [k, ...v] = currentKey.split('=');
    resultReference[k] = v.join('=');
    return;
  }

  resultReference[currentKey] || (resultReference[currentKey] = {});
  pathToObject(resultReference[currentKey], restOfPath.join('.'));
}

let res = {};

[
  'data.location.degree.text=sometexthere',
  'data.otherLocation.degree.otherText=foo',
  'data.location.degree.otherText=bar',
  'we.can.handle.values.that.are_undefined=',
  'we.can.handle.values.that.contain_equals_signs=yes=we=can'
].forEach(x => pathToObject(res, x));

console.log(res);
vzwick
  • 11,008
  • 5
  • 43
  • 63