2

I have a bunch of strings of the form: "AAA.BBB[0].CCC.DDD[5].EEE = 123". Some contain even more deeply nested arrays. How would I convert that dot notation to the equivalent JSON object? I am using jQuery if that provides any additional advantages. I've found ways to ALMOST do this, but no solution works when arrays are included.

edit: I need to do this for many strings and ultimately combine them. This one, in particular, should become an object of the form (assuming I didn't make any errors): { "AAA" : { "BBB": [ "CCC : { "DDD": [ {}, {}, {}, {}, {}, { "EEE": 123 } ] } ] }

Chris
  • 23
  • 1
  • 4
  • 2
    What do you expect the "JSON object" to be? Do you want a JavaScript object that contains merely the objects/arrays needed to traverse that path? – pimvdb Jun 19 '12 at 21:34
  • What would an "equivalent JSON object" look like? What does `AAA.BBB[0].CCC.DDD[5].EEE` mean? – gen_Eric Jun 19 '12 at 21:38
  • { "AAA" : { "BBB": [ "CCC : { "DDD": [ {}, {}, {}, {}, {}, { "EEE": 123 } ] } ] }...there may be slight errors in that, but hopefully you'll get the point. I'll edit the question to include the = 123 part since I did forget that. – Chris Jun 19 '12 at 21:38
  • I think you're mixing concepts. A path only consists of specific keys - it does not describe a complete object with values and other keys. Consequently you cannot convert the one into the other really. – pimvdb Jun 19 '12 at 21:40
  • Empty objects will suffice for missing array elements and the like. I edited above to state that my collection of strings will completely describe the final object. Validation is not a concern. – Chris Jun 19 '12 at 21:46
  • @Chris can you test this for me? – jcolebrand Jun 19 '12 at 22:26

2 Answers2

3

What you're describing isn't something that can just be converted to JSON. What you're describing is the hierarchy to the object, sure, but with those braces in there, you're obviously looking at but one piece of a much larger object. However, for a parser to turn that string into a javascript object, we can do this:

function splitStringToObject(string){
  var sourceArray = string.split('.');
  var top = {};
  var point = top;
  while (sourceArray.length){
    var work = sourceArray.shift();
    if ( /([a-zA-Z_][a-zA-Z0-9_]*)\[([a-zA-Z0-9_]+)\]/.test(work) ){
      console.log('found match alpha index')
      //found an array identifier with a variable name inside ('bbb[aaa]')
      var matches = /([a-zA-Z_][a-zA-Z0-9_]*)\[([a-zA-Z0-9_]+)\]/.exec(work);
      var matchName = matches[1];
      var matchIndex = matches[2];
      point[matchName] = [];
      point[matchName][matchIndex] = {};
      point = point[matchName][matchIndex];
    } else if ( /([a-zA-Z_][a-zA-Z0-9_]*)\[([0-9]+)\]/.test(work) ) {
      console.log('found match numeric index')
      //found an array identifier with a numeric index inside ('bbb[0]')
      var matches = /([a-zA-Z_][a-zA-Z0-9_]*)\[([a-zA-Z0-9_]+)\]/.exec(work);
      var matchName = matches[1];
      var matchIndex = matches[2];
      point[matchName] = [];
      point[matchName][matchIndex] = {};
      point = point[matchName][matchIndex];
    } else if ( work.indexOf('[') > -1 || work.indexOf(']') > -1 ) {
      console.log('found bad egg with ' + work)
    } else if ( work.indexOf('=') > 0 ) { 
      console.log('found = inside')
      //test for equals sign in the string
      var morework = work.split('=');
      work = morework[0].trim();
      sourceArray.push('='+morework[1])
      point[work] = morework[1].trim();
      point = point[work];
    } else { 
      console.log('found plain word')
      //assume work is aok to use as another object here.
      work = work.trim();
      point[work] = {};
      point = point[work];
    }
  }
  //you may not want this next part
  var ret;
  //let's pull our value off the top, altho if we do this, I don't know
  //how to retain the name. I prefer to return it under top still
  for (var k in top){
    ret = top[k];
  }

  console.log(ret);
  return ret;

  // alternately call
  return top;
}

However, this is just a parser, how do we use that? Well, we need to feed it our strings. I'm going to assume you can neatly get all your strings into an array, like this:

var strings = [
  "AAA.BBB[0].CCC.DDD[1].EEE = 123",
  "AAA.BBB[0].CCC.DDD[2].EEE = 123",
  "AAA.BBB[0].CCC.DDD[4].EEE = 123",
  "AAA.BBB[0].CCC.DDD[5].EEE = 123",
];

and then the next part becomes really easy, as we see here:

var objectsConverted = [];
for(var k in strings){
  objectsConverted[k] = splitStringToObject(strings[k]);
}

var result = {};
for(var k in objectsConverted){
  jQuery.extend(true,result,objectsConverted[k]);
}

console.log(result);

and at the end of the day if I JSON.stringify(result) I get:

"{"BBB":[{"CCC":{"DDD":[null,{"EEE":"123"},{"EEE":"123"},null,{"EEE":"123"},{"EEE":"123"}]}}]}"

PLEASE read the caveat inside the end of the function, the namespace is lost because of the choice of return method (I'm returning from the top instead of nested once down)

jcolebrand
  • 15,889
  • 12
  • 75
  • 121
  • I may need to modify it slightly, but this is 90% of what I need. Thanks so much for your time! – Chris Jun 20 '12 at 14:17
  • Do share your changes, I tried to document liberally where I thought it was doing something non-obvious and on each branch entry so you could find where something broke, if it did, and I encourage you to omit the last "child seeking" loop before the final return, and instead return top. – jcolebrand Jun 20 '12 at 15:58
  • And let me know how it turns out, I'm kinda excited to see what that resulted in for someone else, it was kinda fun to write. – jcolebrand Jun 20 '12 at 15:59
0

AAA.BBB[0].CCC.DDD[5].EEE

Becomes:

{AAA : {
  BBB : [{ 
   CCC: {
    DDD: [ 
...

If you have a member X[Y] you just need to convert it to an object mapping X to an array of elements.

Does that help?

Justin Ethier
  • 131,333
  • 52
  • 229
  • 284