0

I wrote out this basic functionality in ES5 that works for browsers. What it's supposed to do is iterate over a list of character/ encoding pairings and convert them through a passed string/ query. I know there is probably a sleeker way of writing this archaic code style. Would you guys mind taking a minute to share your implementation?

I have been writing Python for a year now and am rusty in ES7.

function encodeURLBreakers(query) {
    var URLBreakers = {
        '/': '%2F',
        '?': '%3F',
        '#': '%23'
    }; 
    for (var key in URLBreakers) {
        var reg = '/' + URLBreakers[key] + '/g';
        query.replace(key, reg);
    }   
    return query;
}

What would be a good way to refactor this into a reusable function using a map type loop over the Javascript Object.

This is all I've tried and it works, but it uses old syntax. I am very interested in learning and using modern JS (ES7) paradigms to improve my code.

Philip Lee
  • 93
  • 3
  • 7
  • 1
    Did you mean `var reg = new RegExp(URLBreakers[key], 'g')`? Also [your `replace` call doesn't work](https://stackoverflow.com/q/1433212/1048572). – Bergi Jul 09 '19 at 19:39
  • 2
    Btw, why not just use [`encodeURIComponent`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent)? – Bergi Jul 09 '19 at 19:42
  • 1
    Or, if you happen to be constructing a querystring, [`URLSearchParams`](https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams). – Ry- Jul 09 '19 at 19:47

3 Answers3

1

Ignoring encodeURIComponent, I'd probably use the callback functionality of replace:

function encodeURLBreakers(query) {
    const URLBreakers = {
        '/': '%2F',
        '\\?': '%3F',
        '#': '%23'
    };
    const regex = new RegExp(Object.keys(URLBreakers).join("|"), "g");
    return query.replace(regex, match => URLBreakers[match]);
}

Made into a reusable function:

function makeReplacer(map) {
    const regex = new RegExp(Object.keys(map).join("|"), "g");
    return string => string.replace(regex, match => map[match]);
}

const encodeURLBreakers = makeReplacer({
    '/': '%2F',
    '\\?': '%3F',
    '#': '%23'
});

This uses regex alternatives as a trick to avoid looping altogether. If you absolutely had to do it iteratively (e.g. because order mattered, and some expressions matched results of earlier replacements), I'd go for reduce:

function encodeURLBreakers(string) {
    return [
         [/\//g, '%2F'],
         [/?/g, '%3F'],
         [/#/g, '%23'],
    ].reduce(
         (str, [regex, replacement]) => str.replace(regex, replacement),
         string
    );
}
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • You might need `.join("|\\")` to deal with RegExp special characters. – Scott Sauyet Jul 09 '19 at 19:49
  • @ScottSauyet Yes, I didn't escape the regexes since I didn't expect special characters, but no just prefixing some of them with ``\`` [doesn't suffice](https://stackoverflow.com/q/3561493/1048572) – Bergi Jul 09 '19 at 20:00
  • I know that it's not entirely generic, but I believe it would solve the problem as presented. I believe your first two examples will fail on `new RegExp('/|?|#', 'g')` as that isn't an allowable regex string. – Scott Sauyet Jul 09 '19 at 20:08
  • @ScottSauyet Oops, I totally missed the `?` which of course is a special character... – Bergi Jul 09 '19 at 20:43
0

Very much related to Bergi's answer, with just a slightly different flavor, is this:

const URLBreakers = {
    '/': '%2F',
    '?': '%3F',
    '#': '%23'
}

const changes = Object .entries (URLBreakers) .map 
  ( ([unsafe, safe]) => [new RegExp (`\\${unsafe}`, 'g'), safe]
  )

const encodeURLBreakers = (str) => 
  changes .reduce
    ( (s, [regex, safe]) => s .replace (regex, safe)
    , str
    )

console .log (
  encodeURLBreakers ('aaa/bbb?#ccc') //~> 'aaa%2Fbbb%3F%23ccc'
)

If you don't want to use this as a module and want to encapsulate the helpers, just wrap everything in an IIFE, returning the final function and assign the results to encodeURLBreakers.

Note that we escape the characters as we create regexes out of them. '?' for instance, is not the valid body of a regex. This is not as generic as we might like. See the comments to Bergi's answer for a more complete answer to that.

Finally, note that one of the characters you would really need to escape in true query strings is %. If you actually need to do that, this won't suffice, since you'd hit an infinite regress as your result added more %s; you'd have to use something more like Bergi's first version, perhaps with the regex escaping I suggested.

Scott Sauyet
  • 49,207
  • 4
  • 49
  • 103
0

If you’re converting single characters, Array.from is a way to make an array from a transformation of each codepoint of a string (or each element of any iterable in general):

const substitute = map => c => {
    const t = map.get(c);
    return t === undefined ? c : t;
};

const translateString = map => text =>
    Array.from(text, substitute(map)).join('');

const encodeURLBreakers = translateString(new Map([
    ['/', '%2F'],
    ['?', '%3F'],
    ['#', '%23'],
]));


console.log(encodeURLBreakers('hello? world?'));

Almost everything here is ES6, so that’s as ES6y as I can make it. :)

Ry-
  • 218,210
  • 55
  • 464
  • 476