loading...

Comparing URLs in Javascript

theonlybeardedbeast profile image TheOnlyBeardedBeast ・3 min read

Why I need to compare URLs

There is an e-shop with a lot of filters, these filters can be saved by the user for notification purpose. The filters on the frontend are stored as a javascript object with a lot of nesting for different needs of the component, so comparing a database stored object with an actual filter object is possible, but we can do it differently. Actually every change in the filter affects the URL pathname and/or query string, this is done because we wanted to have shareable searches by the users.

It is not just comparing 2 strings

Actually it would be nice, but the query params can have a different order, and by changing the URL manually you can even create duplicates of the same property.

My solution

So first we transform 2 URL strings into URL objects.

const urls = [new URL(urlProp1),new URL(urlProp2)];

Comparing the pathname is easy, there is no way to have it in bad order or to add not supported params to it, that's handled by our router, so comparing them is enough.

if(urls[0].pathname !== urls[1].pathname){
    return false;
}

Next we exclude our URLSearchParams from our URL objects before we create our key-value objects.

const params = urls.map(url => url.searchParams);

Before we create key-value objects from our URLSearchParams we quickly compare the length of our params. The object creation would eliminate duplicates in the URLSearchParams objects.

Actually you can skip this step. But it brings up uncertainty about duplicates, because duplacates can have different values with a same key.
(note: our e-shop eliminates duplicates on frontend and backend automatically)

if(params[0].toString().length !== params[1].toString().length){
    return false;
}

So now we can create objects from our params. This step also eliminates duplicates, that is why we compared the length of our params above.

const paramObjects = params.map(param => {
    param.delete("page");

    return Object.fromEntries(param);

    // for nodejs where Object.fromEntries is not implemented
    // let paramObject = {};

    // for (let [key, value] of param.entries()) {
    //   paramObject[key] = value;
    // }

    // return paramObject;
});

So at this point, we know that we have URLSearchParams with the same string length and without duplicate keys. So now we can compare the actual number of our params in our URLSearchParams.

if(Object.keys(paramObjects[0]).length !== Object.keys(paramObjects[1]).length){
    return false;
}

Now we check if every key from one paramsObjects exists in the other and if every key have same value in both objects and we return the result from this check.

return Object.keys(paramObjects[0]).every(key => !!paramObjects[1][key] && paramObjects[1][key] === paramObjects[0][key]);

So I ended up with this helper function.

const urlsEqual = (urlProp1,urlProp2) => {
  const urls = [new URL(urlProp1),new URL(urlProp2)];

  if(urls[0].pathname !== urls[1].pathname){
    return false;
  }

  const params = urls.map(url => url.searchParams);

  if(params[0].toString().length !== params[1].toString().length){
    return false;
  }

  const paramObjects = params.map(param => {
    param.delete("page");
    return Object.fromEntries(param);

    // for nodejs where Object.fromEntries is not implemented
    // let paramObject = {};

    // for (let [key, value] of param.entries()) {
    //   paramObject[key] = value;
    // }

    // return paramObject;
  });

  if(Object.keys(paramObjects[0]).length !== 
     Object.keys(paramObjects[1]).length){
       return false;
  }

  return Object.keys(paramObjects[0]).every(key => !!paramObjects[1][key] 
    && paramObjects[1][key] === paramObjects[0][key]);
}


English isn’t my first language, so please excuse any mistakes.

PS: I know the host is not checked, implement it if you need it. Our client-server solution takes care of it.

Thank you all for your attention and time;

Posted on by:

theonlybeardedbeast profile

TheOnlyBeardedBeast

@theonlybeardedbeast

.NET Core + TypeScript + React + Flutter + UWP Yep, that's me.

Discussion

markdown guide