Remove array object by value

(2) [Array(2), Array(2)]
0: (2) ["Instagram", "https://www.asdf.com/api/feeds/320738?filter=644680"]
1: (2) ["Facebook", "https://www.asdf.com/api/feeds/320738?filter=616690"]

That’s the result after doing this line of code

console.log(Object.entries(sourceData));

Now, I also have another key/value array in Javascript which can hold…

(3) [Array(2), Array(2), Array(2)]
0: (2) ["instagram", 1]
1: (2) ["twitter", 2]
2: (2) ["facebook", 1]

That snippet is done via

console.log(Object.entries(this.filters));

You see how this second array has twitter in it, whereas the first does not? I basically need to identify what values in the second array are missing from the first. So e.g. it’s obvious that “twitter” is missing. There could be others in real life.

I’m having trouble wrapping my brain around it.

let unselectedSources = Object.keys(this.filters);
// for (const [key, value] of Object.entries(sourceData)) {
      //   for (const [key2, value2] of Object.entries(this.filters)) {
      //     if(key.toLowerCase() === key2) {
      //       console.log(key.toLowerCase(), key2);
      //       var arrayCheck = $.inArray(key, unselectedSources);
      //       console.log(arrayCheck);
      //       if(arrayCheck != -1) {
      //         console.log("here");
      //         unselectedSources = unselectedSources.splice(key.toLowerCase(), 1);
      //       }
      //     }
      //   }
      // };
      // console.log(unselectedSources);

My problem is that I can’t see a good way to splice it out of the unselectedSources because I don’t know the index I’m at. Can anyone nudge me in the right direction?

Dont overthink things. Or at least, reinvent the wheel.

var bases = array1.map((x) => x[0].toLowerCase());
var difference = array2.filter((x) => !bases.includes(x[0].toLowerCase()); );

Line 1 reduces your first array down to an array of strings containing the first element of the array, lowercased.
Line 2 filters the second array, and keeps only things that are not in the array we just created.

1 Like

Thank you, it worked great.

var bases = Object.entries(sourceData).map((x) => x[0].toLowerCase());
      console.log(bases);
      var difference = Object.entries(this.filters).filter((x) => !bases.includes(x[0].toLowerCase()));
      console.log(difference);

The output though, is

(2) ["instagram", "facebook"]
0: "instagram"
1: "facebook"
length: 2
__proto__: Array(0)

[Array(2)]
0: Array(2)
0: "twitter"
1: 2

Is it possible to clean up the difference variable? I want to basically only keep the “twitter”, in this example.

E.g., if I remove “facebook”, this is the difference

(2) [Array(2), Array(2)]
0: (2) ["twitter", 2]
1: (2) ["facebook", 1]

I basically want just the social media name, since I need to loop over each “missing” name in difference and do stuff with it.

Got it!

var bases = Object.keys(sourceData).map((x) => x.toLowerCase());
      console.log(bases);
      var difference = Object.keys(this.filters).filter((x) => !bases.includes(x.toLowerCase()));
      console.log(difference);
["instagram"]
0: "instagram"
length: 1

(2) ["twitter", "facebook"]
0: "twitter"
1: "facebook"
1 Like

.map((y) => y[0]);

or that, that works too. hehe.

1 Like

An alternative, which does not work in IE (ES6):

var difference = this.filters.flatMap((x) => bases.includes(x[0].toLowerCase()) ? [] : [x[0]]);

By way of explanation: Flatmap will flatten a multidimensional array, but importantly, will throw away any empty arrays in the first layer. So we can combine map and filter by using the return of flatMap carefully.

1 Like

One last question for this thread…I start with this sort of URL

https://www.asdf.io/api/feeds/320738?filter=123456

But I’m trying to see if the URL is blank, like this

https://www.asdf.io/api/feeds/320738?filter=

If I console log this

Object.entries(endpoint.split("?filter=").pop().split(","));

And there are numbers at the end of the filter=, then I get this result

(2) [Array(2), Array(2)]
0: (2) ["0", "616690"]
1: (2) ["1", "605975"]

If not, then I get

[Array(2)]
0: (2) ["0", ""]

The issue is this function

requestPostData(endpoint, feedOptions, callback) {
    var filters = Object.entries(this.filters);
    var urls = Object.entries(endpoint.split("?filter=").pop().split(","));
    var urls2 = urls.map(filter => endpoint.split("?")[0] + "?filter=" + filter[1]);
    var sourceData = {};
    console.log(urls);
    console.log(urls2);
    Promise.all(urls2.map(url =>
      fetch(url).then(resp => resp.json())
    )).then(values => {
      console.log(values);
      values.forEach(function(value, index) {
        // console.log(value);
        if(value.posts.items[0].source.source in sourceData) {
          sourceData[value.posts.items[0].source.source] = sourceData[value.posts.items[0].source.source] + "," + urls2[index].split("?filter=")[1];
        } else {
          console.log("here");
          sourceData[value.posts.items[0].source.source] = endpoint.split("?filter")[0] + "?filter=" + urls2[index].split("?filter=")[1];
        }
      });
      var sources = Object.keys(sourceData).map((x) => x.toLowerCase());
      var difference = Object.keys(this.filters).filter((x) => !sources.includes(x.toLowerCase()));
      // console.log(sources);
      // console.log(difference);
      difference.forEach(function(value, index) {
        sourceData[value] = endpoint.split("?filter")[0] + "?filter="+value;
      });
      var filterOptions = filters.map(filter => filter[0] + "&per=" + filter[1] + "&page=" + this.options.feed.page);
      var finalData = this.combineURLData(sourceData, filterOptions);
      console.log(finalData);
      return finalData;
    }).then(function(finalizedURLs) {
      Promise.all(finalizedURLs.map(url =>
        fetch(url).then(resp => resp.json())
      )).then(callback);
    });
  }

Basically, even with an empty filter= query string, that still gets counted as a URL, and when it comes tmie to console log finalData, I end up with

0: "https://www.asdf.io/api/feeds/320738?filter=&per=2&page=1"
1: "https://www.asdf.io/api/feeds/320738?filter=instagram&per=1&page=1"
2: "https://www.asdf.io/api/feeds/320738?filter=facebook&per=1&page=1"

The first URL gets ran.

Okay… there’s a lot to unpack here for someone who doesnt know the code, but…
If the filter is empty, simply don’t add it to finalData?

I feel like you’re vastly overcomplicating your effort here…I really really dislike saying this, but… what exactly are you trying to do with this function? What are your inputs, and your desired outputs?

Here’s the whole program, explained. I start with a URL like this

https://www.asdf.io/api/feeds/320738?filter=123456

That end point (123456) can have multiple sets of numbers there, separated by a comma. This URL is basically a JSOn file (more on that later). My current issue is that I need to figure out if there are actually no numbers at the end of filter=. Because that tells me that someone screwed up (to sum it up).
Now, I need to basically take a URL like this:

https://www.asdf.io/api/feeds/320738?filter=123456,234567

And separate it into URLs like this

https://www.asdf.io/api/feeds/320738?filter=123456
https://www.asdf.io/api/feeds/320738?filter=234567

Etc, however many numbers are at the end. EACH number here corresponds to a social media source. E.g. they can both be facebook, or one facebook, one twitter, etc. By separating out each URL, i can parse the JSON to determine which it is, and create URLs based on that.

So let’s say I have a URL with 5 numbers.

https://www.asdf.io/api/feeds/320738?filter=644679,616690,123456,234567,345678

Now, let’s say that the first 2 are facebook, then 2 twitters, and the last is an instagram. I parse each separated URL and combine the URLs (eventually). I’ll break down the program later. So that example will get turned into

https://www.asdf.io/api/feeds/320738?filter=644679,616690
https://www.asdf.io/api/feeds/320738?filter=123456,234567
https://www.asdf.io/api/feeds/320738?filter=345678

Now, below is how I call the function. I call the function like this

$('.home-social2 .fsFeeds.fsCustom').each(function() {
			var self = $(this);
			var options = {
				filters: {
					"instagram": 1,
					"twitter": 2,
					"facebook": 1
				},
			}

			var feedInstance = new MultiRequestFeed(self, options, function() {
				
			});

			feedInstance.init();
		});

However, if they want call this function with “twitter” and “2”, and I don’t find any URLs that are actually “twitter”, I need to create a URL like so

https://www.asdf.io/api/feeds/320738?filter=twitter

In it’s place.

The most important parts of my whole program is this

combineURLData(filteredURLs, options) {
    var finalURLs = [];
    var urlData = Object.entries(filteredURLs);
    urlData.forEach(function(data, index) {
      for(let i=0;i<urlData.length;i++) {
        if(data[0].toLowerCase() === options[i].split("&")[0]) {
          finalURLs.push(data[1]+"&"+options[i].substring(options[i].indexOf("&")+1));
        }
      };
    });
    return finalURLs;
  }

  requestPostData(endpoint, feedOptions, callback) {
    var filters = Object.entries(this.filters);
    var urls = Object.entries(endpoint.split("?filter=").pop().split(","));
    var urls2 = urls.map(filter => endpoint.split("?")[0] + "?filter=" + filter[1]);
    var sourceData = {};
    console.log(urls);
    console.log(urls2);
    Promise.all(urls2.map(url =>
      fetch(url).then(resp => resp.json())
    )).then(values => {
      console.log(values);
      values.forEach(function(value, index) {
        // console.log(value);
        if(value.posts.items[0].source.source in sourceData) {
          sourceData[value.posts.items[0].source.source] = sourceData[value.posts.items[0].source.source] + "," + urls2[index].split("?filter=")[1];
        } else {
          console.log("here");
          sourceData[value.posts.items[0].source.source] = endpoint.split("?filter")[0] + "?filter=" + urls2[index].split("?filter=")[1];
        }
      });
      var sources = Object.keys(sourceData).map((x) => x.toLowerCase());
      var difference = Object.keys(this.filters).filter((x) => !sources.includes(x.toLowerCase()));
      // console.log(sources);
      // console.log(difference);
      difference.forEach(function(value, index) {
        sourceData[value] = endpoint.split("?filter")[0] + "?filter="+value;
      });
      var filterOptions = filters.map(filter => filter[0] + "&per=" + filter[1] + "&page=" + this.options.feed.page);
      var finalData = this.combineURLData(sourceData, filterOptions);
      console.log(finalData);
      return finalData;
    }).then(function(finalizedURLs) {
      Promise.all(finalizedURLs.map(url =>
        fetch(url).then(resp => resp.json())
      )).then(callback);
    });
  }

First, requestPostData is called, and what this.filters basically is, is this part

filters: {
					"instagram": 1,
					"twitter": 2,
					"facebook": 1
				},

This is saying I need 1 instagram, 2 twitter, 1 facebook.
Going down to variables urls/urls2, urls gets me each individual number that’s separated by a comma, in the initial URL
urls2 makes the new URLs that I need to parse over

https://www.asdf.io/api/feeds/320738?filter=644679
https://www.asdf.io/api/feeds/320738?filter=616690
https://www.asdf.io/api/feeds/320738?filter=123456,234567
https://www.asdf.io/api/feeds/320738?filter=234567
https://www.asdf.io/api/feeds/320738?filter=345678

Now, I then do a Promise on EACH of these URLs, and have a sourceData array. I have an if/else. If (e.g.) sourceData[facebook] doesn’t exist, I create the initial URL, and if it’s found, then I just append to the URL by separating it by a comma. So e.g. this might be my final product

1. Facebook: "https://www.asdf.io/api/feeds/320738?filter=616690,123456"
2. Instagram: "https://www.asdf.io/api/feeds/320738?filter=644680,234567"
3. Twitter: "https://www.asdf.io/api/feeds/320738?filter=605975"

Just an example. In this scenario, only one of the URLs were identified as twitter.
Then, I have variables source/difference, which this thread goes over. I basically see which filters are defined, and compare them to my sourceData. If the filters call for 2 instagram, 2 facebook, and 1 twitter, but sourceData[twitter] doesn’t exist (for example), because no URL was found to have twitter, then I need sourceData[twitter] created and have

https://www.asdf.io/api/feeds/320738?filter=twitter

Put in its’ place.
Then I modify the URLs once last time to add in more filter options (see variable filterOptions). So e.g. if the intial function call has twitter: 2, then I append &per=2 to the end of the twitter URL. That tells the URL to only load in 2 posts in the JSON.

I do a lot of URL work and it’s a bit convoluted right now.

Then the rest of the function is of no worry to you. It’s a sloppy bit of code so far, but those 2 functions are the only one you ened to really look at. I need to refactor it but I want the core functionality done first. I’m open to any suggestions or improvements .

So, to put it into terms that I can maybe understand:
Inputs:

URL (endpoint)
Filters (filters)
sourceData (which i assume to be an array-like, given the values you gave above.)

Output:

An array of URL's, with as many entries as there are keys in Filters. If a URL is missing, define filter to be the key; then append the per option to each.

Completely untested code, just spitballing.

//Find our numbers.
let matches = endpoint.match(/filter=([\d,]+)/);
let nums = matches == null ? [] : matches[1].split(",").filter((x) => x); 
//The filter there is probably unnecessary, but it removes any problems caused by having two commas in a row.
//Create our output object.
let output = {};
Object.keys(filters).forEach((x) => { output[x] = []; });
//Walk the numbers and figure them out.
nums.forEach((num) => {
  let source = sourceData.filter((x) => x[1].includes("filter="+num));
  if (source.length == 0) { //Need logic for "What if there isnt a valid source for this number?" }
  output[source[0][0]].push(num);
}
//Now for the output...
return filters.map((x) => {
  if(output[x[0]].length == 0) { output[x[0]].push(x[0]); } //If there were no numbers, push the string.
  return "https://www.asdf.com/api/feeds/320738?filter="+output[x[0]].join()+"&per="+x[1]+"&page="+this.options.feed.page;
})

Thank you - it’s close, but there were some issues with your code. I tried patching it together but now I’m stuck on the nums.forEach. If I have 2 numbers in the initial endpoint URL, and they are 1 facebook, and 1 instagram, then it should try to populate the twitter URL in the output array, right? Here is what I have:

Also, I had to introduce another .then() into my code because of how late I populate sourceData. Not a huge fan of that but maybe that can be refactored.

I guess what I can’t figure out is your intentions.

Object.keys(filters).forEach((x) => { output[x] = []; });

If, for example, I have 3 filters defined in my function call, but 2 sources (2 sets of numbers in my endpoint URL, it’ll populate output with 2 values in its array. The 3rd source is missing.

It’s supposedly handled in the nums.forEach, but the check is wrong. It seems you want output to be the finalized data. So we need to compare the incomplete array of output to something and then populate the missing items.

Still working on it.

Got it! Error coded in in case no numbers are at the end of the URL (so e.g. the error message is for if this URL comes through):

https://www.juicer.io/api/feeds/320738?filter=

Last thing I need to do is somehow sort my sourceData array to match the order of my filters in my function call. Working on that now:

requestPostData(endpoint, feedOptions, callback) {
    var filters = Object.entries(this.filters);
    var matches = endpoint.match(/filter=([\d,]+)/);
    var nums = matches == null ? [] : matches[1].split(",").filter((x) => x); 
    var output = {};
    var sourceData = {};
    var sourceNumbers = Object.entries(endpoint.split("?filter=").pop().split(","));
    var urls = sourceNumbers.map(filter => endpoint.split("?")[0] + "?filter=" + filter[1]);
    if(sourceNumbers[0][1] === "") {
      console.error("Error: Please select sources in the element");
      return;
    }
    Promise.all(urls.map(url =>
      fetch(url).then(resp => resp.json())
    )).then(values => {
      values.forEach(function(value, index) {
        if(value.posts.items[0].source.source.toLowerCase() in sourceData) {
          sourceData[value.posts.items[0].source.source.toLowerCase()] = sourceData[value.posts.items[0].source.source.toLowerCase()] + "," + urls[index].split("?filter=")[1];
        } else {
          sourceData[value.posts.items[0].source.source.toLowerCase()] = endpoint.split("?filter")[0] + "?filter=" + urls[index].split("?filter=")[1];
        }
      });
      filters.forEach((x) => {
        if(sourceData[x[0]] === undefined) {
          sourceData[x[0]] = endpoint.split("?filter")[0] + "?filter="+x[0] + "&per=" + x[1] + "&page=" + this.options.feed.page;
        } else {
          sourceData[x[0]] = sourceData[x[0]] + "&per=" + x[1] + "&page=" + this.options.feed.page;
        }
      });
      console.log(Object.values(sourceData));
      Promise.all(Object.values(sourceData).map(url =>
        fetch(url).then(resp => resp.json())
      )).then(callback);
    });
  }

Codepen above has been updated to show it working.

I’ve googled how to do this sorting, but I’m not really sure about how to do it. Looking into it right now, but thought I’d post for help in case someone figures it out before me.

{facebook: "https://www.asdf.io/api/feeds/320738?filter=616690&per=1&page=1", instagram: "https://www.asdf.io/api/feeds/320738?filter=instagram&per=1&page=1", twitter: "https://www.asdf.io/api/feeds/320738?filter=twitter&per=2&page=1"}

facebook: "https://www.asdf.io/api/feeds/320738?filter=616690&per=1&page=1"
instagram: "https://www.asdf.io/api/feeds/320738?filter=instagram&per=1&page=1"
twitter: "https://www.asdf.io/api/feeds/320738?filter=twitter&per=2&page=1"

That’s my sourceData object

(3) [Array(2), Array(2), Array(2)]
0: (2) ["instagram", 1]
1: (2) ["twitter", 2]
2: (2) ["facebook", 1]

That’s my filters object.

I need the sourceData object order to match the filters object order.

I’ll post back if I figure the answer out first :slight_smile: .

filters is an array, not an object. an Object doesn’t really have an ‘order’ (it does, but it very rarely matters).
When you convert sourceData to an Array, then a defined order is present, but to be honest, i would sort your filters array, rather than the sourceData array. Sourcedata is in lexicographical order there.

Sorting an array is simple enough, for example, if i wanted to put the filters in lexicographical order, filters.sort((x,y) => (x[0] > y[0]) ? 1 : -1) (Yes, there’s a third result with sorting, but screw it, i feel lazy. If you want to do the additional math, filters.sort((x,y) => (x[0] == y[0] ? 0 : ((x[0] > y[0]) ? 1 : -1)))

(Note: Array.prototype.sort is an in-place function.)

Thank you for correcting me. I will say, the typeof was object (I checked before posting in an attempt to not misspeak) which is why I said what I did :slight_smile: . I will update my brains knowledge base.

I need sourceData modified because the URLs in that are to be looped over and data from it will be present on the screen. The order of filters cannot change.

Could I create a new object, compare sourceData to filters? I was trying to avoid making a new object if I could but if that’s what I need to do, I will.

Strtictly speaking, an Array is an Object in terms of type.
Typeof can return:

undefined
boolean
bigint
string
symbol
function
object (default)

(one minor note there: null is an object, because it’s not undefined, and for vaguery reasons of how the return type is referenced)

I don’t really think you need to modify either, tbh.
If you did insist on reordering the object, it would be something like…

let newobj = {}
filters.forEach((x) => { 
  Object.entries(sourceData).forEach((k,v) => {
     if(k.toLowerCase() == x[0].toLowerCase()) { 
           newobj[k] = v;  
           delete sourceData[k];
      }
  });
});
for (const [k,v] of Object.entries(sourceData)) {
  newobj[k] = v;
}
sourceData = newobj;

But instead of trying to do that, i’d walk filters, then walk the difference.

Also another note, what you’ve given here:

Is not the same object as you gave here:

Javascript is Case Sensitive. so the keys of the object being different cases was what was making the comparisons more difficult.

That quote you took is from the first post: I have updated my code to force it lowercase to prevent this from happening :thumbsup:

Sorry about that: I’m working on the project and keep tinkering with the settings and trying out different variations.

I’ll try this out ASAP :thumbsup: thank you.

If you’ve forced both to be lowercase already, it can be simplified slightly:

let keys = new Set(filters.flatMap((x) => (x[0] in sourceData) ? [x[0]] : [] ).concat(Object.keys(sourceData)));
for (const x of keys) {
    let v = sourceData[x]; //Store Value
    delete sourceData[x]; //Remove property
    sourceData[x] = v; //Recreate property.
}

EDIT: that map needs to be a flatmap to filter out missing entities.

1 Like

That worked beautifully! The program is done! Thank you @m_hutley .

1 Like