intro28
intro28

Reputation: 13

How to group JSON using JavaScript or Node?

I have a JSON of music audio metadata with several tracks in a following structure:

var jsonObj = {
 tracks: [
   { title: 'Another',
     artist: 'Ataxia',
     album: 'Automatic Writing',
     year: '2004',
     duration: 382
   }]
};

And I want to transform it to have a following grouped structure:

var jsonObj = {
  artists: [
    { name: 'Ataxia',
      albums: [
       { name: 'Automatic Writing',
         year: '2004',
         tracks: [
          { title: 'Another',
            duration: '382'
          }]
       }]
    }]
 };

Of course I tried doing it using pure JavaScript forEach() methods but it's a hell load of repetitive, immersive code and I'm looking for some smart solution. It could rely on some external Node.js package or JavaScript library.

Upvotes: 1

Views: 487

Answers (3)

ivern
ivern

Reputation: 141

A simple way to combine artists and albums is to use dictionaries. Here's one way of processing the data through a dictionary, then generating the desired arrays once the tracks are organized by album and artist. Look in the console to see the results.

var jsonObj = {
  tracks: [{
    title: 'Another',
    artist: 'Ataxia',
    album: 'Automatic Writing',
    year: '2004',
    duration: 382
  }]
};

var byArtist = {};
jsonObj.tracks.forEach(function(e) {
  if (byArtist[e.artist] === undefined) {
    // New artist, add to the dictionary
    byArtist[e.artist] = {
      artist: e.artist,
      albums: {}
    };
  }

  if (byArtist[e.artist].albums[e.album] == undefined) {
    // New album, add to the dictionary
    byArtist[e.artist].albums[e.album] = {
      name: e.album,
      year: e.year,
      tracks: []
    };
  }

  // Add the track
  byArtist[e.artist].albums[e.album].tracks.push({
    title: e.title,
    duration: e.duration
  });
});

// Convert the dictionaries to the final array structure
var result = {
  artists: []
};
for (var artistKey in byArtist) {
  if (byArtist.hasOwnProperty(artistKey)) {
    var artist = {
      name: byArtist[artistKey].artist,
      albums: []
    };

    // We need to convert the album dictionary as well
    for (var albumKey in byArtist[artistKey].albums) {
      if (byArtist[artistKey].albums.hasOwnProperty(albumKey)) {
        artist.albums.push(byArtist[artistKey].albums[albumKey]);
      }
    }

    result.artists.push(artist);
  }
}

console.log(result);

Upvotes: 1

jfriend00
jfriend00

Reputation: 708156

As I was working on putting it into the desired format, I had to search for an artist, search for an album, search for a track, etc... So, in thinking about it, I realized that you will probably want those functions yourself on the final data structure so rather than just write throw away code for converting one to the other, I made the resulting music list into an object that has various methods on it. One of those methods will add a list of tracks to the music list and have it organized as you've asked for.

So, here's that object and resulting demo. I add a couple more tracks just to better show the result. The end result here is you get an object that contains the music list and you get methods for finding an artist, album and track.

var trackList = {
 tracks: [
   { title: 'Another',
     artist: 'Ataxia',
     album: 'Automatic Writing',
     year: '2004',
     duration: 382
   },
   { title: 'Another2',
     artist: 'Ataxia',
     album: 'Automatic Writing',
     year: '2007',
     duration: 412
   },
   { title: 'Another3',
     artist: 'Ataxia3',
     album: 'Automatic Writing',
     year: '2008',
     duration: 366
   },
 ]
};

function Music() {
    this.artists = [];
}

// helper function
// In an array of objects, it finds an object that has a specific property name 
// with a specific value.
function findPropInArray(arr, propName, propValue) {
    
    for (var i = 0; i < arr.length; i++) {
        if (arr[i][propName] === propValue) {
            return arr[i];
        }
    }
    return null;
}

Music.prototype = {
    addTracks: function(list) {
        var self = this;
        list.forEach(function(t) {
            self.addTrack(t);
        });
    },
    addTrack: function(t) {
        if (t.artist) {
            var artistObj = this.findArtist(t.artist);
            if (!artistObj) {
	            // if artist not found, then add the artist
                artistObj = {name: t.artist, albums: []};
                this.artists.push(artistObj);
            }
            var albumObj = this.findAlbum(artistObj, t.album);
            if (!albumObj) {
                // create album Obj
                albumObj = {name: t.album, year: t.year, tracks: []};
                artistObj.albums.push(albumObj);
            }
            if (!this.findTrack(albumObj, t.title)) {
            	albumObj.tracks.push({title: t.title, duration: t.duration});            
            }
        }
    },
    findArtist: function(name) {
        return findPropInArray(this.artists, "name", name);
    },
    findAlbum: function(artist, albumName) {
        var artistObj = artist;
        // if artist name was passed as a string, then find the object
        if (typeof artist === "string") {
            artistObj = this.findArtist(artist);
        }
        if (!artistObj) {
            return null;
        }
        return findPropInArray(artistObj.albums, "name", albumName);
    },
    findTrack: function(albumObj, name) {
        return findPropInArray(albumObj.tracks, "title", name);
    }
}

var m = new Music();
m.addTracks(trackList.tracks);
var text = JSON.stringify(m, null, "  ");
document.getElementById("result").textContent = text;
<pre id="result"></pre>

Upvotes: 0

Moishe Lipsker
Moishe Lipsker

Reputation: 3032

You can do something like the following (you will need to look at the console to see the results):

var jsonObj = {
 tracks: [
   { title: 'Another',
     artist: 'Ataxia',
     album: 'Automatic Writing',
     year: '2004',
     duration: 382
   }]
};
var jsonObjResult = {};
jsonObj['tracks'].forEach(function(track, index){
    jsonObjResult['artists'] = [];
    jsonObjResult['artists'][index] = {
            'name' : track['artist']
        ,   'albums' : []
    };
    jsonObjResult['artists'][index]['albums'] = {
            'name' : track['album']
        ,   'year' : track['year']
        ,   'tracks' : []
    };
    jsonObjResult['artists'][index]['albums']['tracks'] = {
            'title' : track['title']
        ,   'duration' : track['duration']
    };
});
console.log(jsonObjResult);

Upvotes: 0

Related Questions