1

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.

12
  • 5
    Repetitive code? See whether you can factor out the common elements then! It would help if you could post the code that you have (even if it's ugly or non-working) so that we can help you with improving it. Commented Sep 7, 2015 at 20:47
  • Yes I agree that it will get really repetitive but you could abstract the functions meaning you can have a "for each x list all the y" function and recursively call it two time with (artists, albums) and (albums,tracks). In this case you will have a single function and 2 calls to it Commented Sep 7, 2015 at 20:47
  • Btw, this is available in many helper function libraries under names like byProperty, groupBy or so Commented Sep 7, 2015 at 20:48
  • Is your first array of tracks, an array that contains tracks from lots of artists and lots of albums and you want it into second form where it's grouped by artist, then within that grouped by album, then within that an array of tracks on the album? Is that what you're trying to do? Are the keys for artist and album, just the name? Commented Sep 7, 2015 at 20:48
  • artist should be an object instead of an array if you want to combine tracks. Commented Sep 7, 2015 at 20:49

3 Answers 3

1

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);

Sign up to request clarification or add additional context in comments.

1 Comment

Thanks! It's a nice solution that works exactly as expected. Though takes a lot of code and I wonder if it could get any smarter.
0

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);

Comments

0

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>

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.