Justin
Justin

Reputation: 568

Loop Through JSON, Insert Key/Value Between Objects?

UPDATE - Thanks for all the great answers and incredibly fast response. I've learned a great deal from the suggested solutions. I ultimately chose the answer I did because the outcome was exactly as I asked, and I was able to get it working in my application with minimal effort - including the search function. This site is an invaluable resource for developers.


Probably a simple task, but I can't seem to get this working nor find anything on Google. I am a Javascript novice and complex JSON confuses the hell out of me. What I am trying to do is make a PhoneGap Application (Phone Directory) for our company. I'll try to explain my reasoning and illustrate my attempts below.

I have JSON data of all of our employees in the following format:

[  
  {  
    "id":"1",
    "firstname":"John",
    "lastname":"Apple",
    "jobtitle":"Engineer"
  },
  {  
    "id":"2",
    "firstname":"Mark",
    "lastname":"Banana",
    "jobtitle":"Artist"
  },
  ... and so on
]

The mobile framework (Framework 7) that I am using offers a "Virtual List" solution which I need to take advantage of as our directory is fairly large. The virtual list requires you to know the exact height of each list item, however, you can use a function to set a dynamic height.

What I am trying to do is create "headers" for the alphabetical listing based on their last name. The JSON data would have to be restructured as such:

[  
  {
    "title":"A"
  },
  {  
    "id":"1",
    "firstname":"John",
    "lastname":"Apple",
    "jobtitle":"Engineer"
  },
  {
    "title":"B"
  },
  {  
    "id":"2",
    "firstname":"Mark",
    "lastname":"Banana",
    "jobtitle":"Artist"
  },
  ... and so on
]

I've been able to add key/value pairs to existing objects in the data using a for loop:

var letter, newLetter;
for(var i = 0; i < data.length; i++) {
    newLetter = data[i].lastname.charAt(0);
    if(letter != newLetter) {
        letter = newLetter
        data[i].title = letter;
    }
}

This solution changes the JSON, thus outputting a title bar that is connected to the list item (the virtual list only accepts ONE <li></li> so the header bar is a div inside that bar):

{  
  "id":"1",
  "firstname":"John",
  "lastname":"Apple",
  "jobtitle":"Engineer",
  "title":"A"
},
{  
  "id":"1",
  "firstname":"Mike",
  "lastname":"Apricot",
  "jobtitle":"Engineer",
  "title":""
}

This solution worked until I tried implementing a search function to the listing. When I search, it works as expected but looks broken as the header titles ("A", "B", etc...) are connected to the list items that start the particular alphabetical section. For this reason, I need to be able to separate the titles from the existing elements and use them for the dynamic height / exclude from search results.

The question: How can I do a for loop that inserts [prepends] a NEW object (title:letter) at the start of a new letter grouping? If there is a better way, please enlighten me. As I mentioned, I am a JS novice and I'd love to become more efficient programming web applications.

Upvotes: 2

Views: 2901

Answers (3)

dfsq
dfsq

Reputation: 193261

Simple for loop with Array.prototype.splice will do the trick:

for (var i = 0; i < data.length; i++) {
    if (i == 0 || data[i-1].lastname[0] !== data[i].lastname[0]) {
        data.splice(i, 0, {title: data[i].lastname[0]});
        i++;
    }
}

Demo. Check the demo below.

var data = [  
    {"lastname":"Apple"},
    {"lastname":"Banana"},
    {"lastname":"Bob"},
    {"lastname":"Car"},
    {"lastname":"Christ"},
    {"lastname":"Dart"},
    {"lastname":"Dog"}
];

for (var i = 0; i < data.length; i++) {
    if (i == 0 || data[i-1].lastname[0] !== data[i].lastname[0]) {
        data.splice(i, 0, {title: data[i].lastname[0]});
        i++;
    }
}

alert(JSON.stringify( data, null, 4 ));

Upvotes: 0

epascarello
epascarello

Reputation: 207511

var items = [  
  { "lastname":"Apple" },
  { "lastname":"Banana" },
  { "lastname":"Box"  },
  { "lastname":"Bump" },
  { "lastname":"Can" },
  { "lastname":"Switch" }
];
  
var lastC = null;  //holds current title
var updated = [];  //where the updated array will live
for( var i=0;i<items.length;i++) {
  var val = items[i];  //get current item
  var firstLetter = val.lastname.substr(0,1);  //grab first letter
  if (firstLetter!==lastC) {  //if current title does not match first letter than add new title
    updated.push({title:firstLetter});  //push title
    lastC = firstLetter;  //update heading
  }
  updated.push(val);  //push current index
}
console.log(updated);

Upvotes: 5

tymeJV
tymeJV

Reputation: 104775

Well right now you have an array of objects - prefixing the title as its own object may be a bit confusing - a better structure may be:

[
  {
    title: "A",
    contacts: [
        {  
            "id":"1",
            "firstname":"John",
            "lastname":"Apple",
            "jobtitle":"Engineer",
            "title":"A"
  }
]

Given your current structure, you could loop and push:

var nameIndexMap = {};
var newContactStructure = [];

for (var i = 0; i < data.length; i++) {
    var letter = data[i].lastname.charAt(0);
    if (nameIndexMap.hasOwnProperty(letter)) {
        //push to existing
        newContactStructure[nameIndexMap[letter]].contacts.push(data[i])
    } else {
        //Create new
        nameIndexMap[letter] = newContactStructure.length;
        newContactStructure.push({
            title: letter,
            contacts: [
                data[i]
            ]
        });
    }
}

newContactStructure will now contain your sorted data.

Demo: http://jsfiddle.net/7s50k104/

Upvotes: 1

Related Questions