javascript regex parsing array-syntax strings almost working - javascript

So I'm parsing strings (from a URL) that have an array-like syntax, such as:
variable[foo]
variable[foo][bar]
I need EACH of the indexes (in square brackets) to be it's own capturing group, and I need it to work with one OR MORE indexes... My regex ALMOST works, but only captures the FINAL index, not the proceeding ones, so works perfect with one index.
here you can see my best attempt, and when you hover over the second example, you'll see that group_4 becomes captured group #2 and the rest are lost. I need the captured groups to match the example names.
Just for good measure, here you can see my whole solution for parsing the regex results into actual javascript objects.
getUrlParams: function() {
let query = decodeURIComponent(window.location.search);
let paramRegex = /[&?]([\w[\]\-%]+)=([\w[\]\-%/,\s]+)(?=&|$)/igm;
let arrayRegex = /([\w]+)(?:(?:\[|%5B)([\w]+)(?:]|%5D))+/igm;
let params = {};
let match = paramRegex.exec(query);
while (match !== null) {
if (match && match[1]) {
let array = arrayRegex.exec(match[1]);
while(array !== null) {
if (array && array[1] && array[2]) {
console.log("ARRAY: ", array);
let deepParam = {};
deepParam[array[2]] = match[2];
if (array[1] in params) {
$.extend(params[array[1]], deepParam);
} else {
params[array[1]] = deepParam;
}
} else {
params[match[1]] = match[2];
}
array = arrayRegex.exec(match[1]);
}
}
match = paramRegex.exec(query);
}
return params;
},
This code works great with only one index, but once the regex captures multiple indexes, this code will have to handle it too.
Any help is much appreciated.
UPDATE:
Here is my final function solution, based on bowheart's very elegant code.
getUrlParams: function() {
let query = decodeURIComponent(window.location.search);
let paramRegex = /[&?]([\w[\]\-%]+)=([\w[\]\-%/,\s]+)(?=&|$)/igm;
let params = {};
let match = paramRegex.exec(query);
while (match !== null) {
if (match && match[1] && match[2]) {
let key = match[1];
let val = match[2];
let arrayKeys = key.split(/\[|]/g).filter(node => node);
populateObject(params, arrayKeys, val);
}
match = paramRegex.exec(query);
}
return params;
function populateObject(obj, keys, val) {
if (keys.length === 1) return obj[keys[0]] = (isNaN(+val) ? val : +val);
let nextKey = keys.shift();
if (!obj[nextKey]) obj[nextKey] = isNaN(+keys[0]) ? {} : [];
populateObject(obj[nextKey], keys, val);
}
},

What on earth gave you the idea to accomplish all this with two massive regular expressions? Just...Don't do that. You'll probably live longer. You will need regex to some degree, but always keep it as short as possible.
Here's a solution, if you're interested. You'll notice it's shorter, much easier to read, and accomplishes all the requirements:
// Recursively populates nested objects/arrays.
function populateObj(obj, keys, val) {
if (keys.length === 1) return obj[keys[0]] = val
let nextKey = keys.shift()
if (!obj[nextKey]) obj[nextKey] = isNaN(+keys[0]) ? {} : []
populateObj(obj[nextKey], keys, val)
}
let params = {}
let search = '?filters[name]=sa&filters[group_2][group_3][group_4]=4&order_bys[0][field]=name&order_bys=desc'
search.slice(1).split('&').forEach(pair => {
let [key, val] = pair.split('=')
key = key.split(/\[|]/g).filter(node => node)
populateObj(params, key, val)
})
// Just for display:
document.body.innerHTML = JSON.stringify(params, null, ' ').replace(/\n/g, '<br>')
The basic algorithm is:
Split the GET params on '&', then split each param into a key-val pair on '='.
Regex out any square brackets in the keys to get all nodes for nested arrays/objects.
Recursively traverse an object, creating child objects/arrays when necessary, and assign the given value to the last node.
Create an array if the next key is numeric. Otherwise, create an object.
(Note from your regexr snippet that order_bys[0][field]=name and order_bys=desc params are incompatible as one indicates that order_bys is a zero-indexed array and the other that it's a string. Not sure where you got that data...).

Try this regex:
(?:[\?|\&]([\w]+))|((?:\[|%5B)(\w+)(?:]|%5D))
It captures each group value as an independent match

Split on square brackets and filter out empty strings:
"variable[foo][bar]".split(/\]|\[/).filter(s => !!s)
> [ "variable", "foo", "bar" ]

Related

Change object key names won't accept replacing dots

I've got a JSON object whose keys have dots in their names. I need to replace the dots with 'DOT' (for example).
{
"key1.key": "merciful",
"key2": {
"key2.key": "grateful"
}
}
So, key1.key converts to key1DOTkey
Using the approach suggested in Change key name in nested JSON structure I used the reviver parameter of JSON.Parse, which works like a charm to replace anything within the key name, except for dots: when replacing dots, it truncates the object.
This code replaces all "e" and works fine
var parseE = JSON.parse(obj, function (k, v) {
if (k.search(".") != -1)
this[k.replace(/e/g, 'DOT')] = v;
else
return v;
});
returns
{
"kDOTy1.kDOTy": "merciful",
"kDOTy2": {
"kDOTy2.kDOTy": "grateful"
}
}
But if I try to replace the dot ".", then the object truncates
var parseDOT = JSON.parse(obj, function (k, v) {
if (k.search(".") != -1)
this[k.replace(/\./g, 'DOT')] = v;
else
return v;
});
returns the first key-value pair, well replaced, but nothing else:
{
"key1DOTkey": "merciful"
}
I have tried using replaceAll and even creating a function replacing the characters one by one to avoid using regex in case that was the origin. Nothing, same outcome.
Here's a fiddle: https://jsfiddle.net/dpiret/dgk5fp16/7/
Note: replacing all dots from the stringified object won't work for me because it would replace the dots within values as well.
I would much appreciate any indication
search uses regular expressions (so . matches any character!), just use string methods like indexOf
var obj = `{
"key1.key": "merciful",
"key2": {
"key2.key": "grateful"
},
"key3": {
"key3.keyA.keyB": "thankful"
}
}`
var parseDOT = JSON.parse(obj, function (k, v) {
let key = k;
if (key.indexOf(".") != -1){
while(key.indexOf(".") != -1)
key = key.replace(".","DOT");
this[key] = v;
}
else
return v;
});
console.log(parseDOT)
I'm sorry that I'm struggling to explain why the above works. What I can tell you is your original solution matched every key - what is not clear is why it didnt just update every property with a no-op for those without a dot.
Replace will work - this one does not touch the values
const str = `{
"key1.key": "merciful",
"key2": {
"key2.key": "grateful.dead"
},
"key3": {
"key3.key.key": "text.text"
}
}`
const str1 = str.replace(/"(\w+\.\w+)+":/g,function(match) { return match.replace(/\./g,"DOT")})
const obj = JSON.parse(str1);
console.log(obj);
Checking "text.text": as value
const str = `{
"key1.key": "merciful",
"key2": {
"key2.key": "grateful.dead"
},
"key3": {
"key3.key.key": "text.text"
}
}`
const obj = JSON.parse(str);
console.log(obj);
obj["key3"]["key3.key.key"] = `"text.text":`
console.log(obj);
let str1 = JSON.stringify(obj)
console.log(str1);
str1 = str1.replace(/"(\w+\.\w+)+":/g,function(match) { return match.replace(/\./g,"DOT")})
console.log(str1);
const obj1 = JSON.parse(str1)
console.log(obj1)

How to work out which variable has the most characters using javascript

How to work out which variable has the most characters.
For example :
var one = "qwert";
var two = "qwertyu"
var three ="qwertyuiop";
How to work out which variable has the most character.
First thing I am doing is counting the number of characters in each string.
var onelength = one.length;
var twolength = two.length;
var threelength = three.length;
The part I am struggling on is javascript to work out which of the above lengths has the most characters.
There's really no way to do this in Javascript (nor indeed in most languages).
What you're asking for is a kind of reflection. Conceptually, a function nameOfVariable that takes a variable and gives you it's string name in return:
nameOfVariable(one): 'one'
Not possible.
The answers above are attempts to work around that. Ultimately in real code this would be structured not as a bag of variables but as an object (which is kinda like a bag of variables, except you can recover the names)
const strings = {
one: 'abcde',
two: 'abcdef',
three: 'abcd',
};
// Return key in object corresponding to longest string,
// or null if object is empty.
const longest_string = strings => {
let max = null;
for (let name in strings) {
if (max === null || strings[name].length > strings[max].length) {
max = name;
}
}
return max;
}
console.log(longest_string(strings));
try
[one,two,three].reduce((a,c) => a.length>c.length? a:c,'');
var one = "qwert";
var two = "qwertyu";
var three ="qwertyuiop";
let r= [one,two,three].reduce((a,c) => a.length>c.length? a:c,'');
let v= Object.entries({one,two,three}).reduce((a,c) => a[1].length>c[1].length ? a:c,['','']);
console.log('longest variable:', v[0], '=', r);
var words=['ape','parrot','elephant'];
console.log( longestWord( words ) );
function longestWord( words ){
var longest = '';
words.forEach(function(element){
if(longest.length < element.length)
longest = element;
});
return longest;
}
You don't need to use JQuery for this. If you save the strings in an array:
const a = ["qwert","qwertadf","qwertfasdf"]
you can use this (using ES2015 features):
let max_length = Math.max(...a.map(e=>e.length))
If you want to know the greater element, you can use this:
a.findIndex(e => e.length === ml)
Just do Math.max();
Example:
Math.max(onelength, twolength, threelength);
This will return the highest value.

regex to extract array indices

I'm still having a hard time to understand regex... :-/
Given strings (JavaScript-like expressions) like these...
foo[0]
foo[4][2]
foo[4][2][234523][3]
...I'm trying to deconstruct the indices in regex, so that I have
the name of the variable: foo
the single indices: fox example 4, 2, 234523 and 3 in the last example
while not accepting invalid syntax like
foo[23]bar[55]
foo[123]bar
[123]bla
foo[urrrr]
It would be nice to also ignore whitespace like foo [13] or foo[ 123 ] but that's not important.
Is that possible with regex?
I was able to extract the brackets with var matches = s.match(/\[([0-9]?)\]/g); but that includes the brackets in the result, is missing the variable name (could get around that) and also does not respect the edge cases as described above.
You'll have to use loops to extract multiple matches. Here's one way:
function run(string) {
var match;
if(match = string.match(/^([^[]+)\s*(\[\s*(\d+)\s*\]\s*)+\s*$/)) {
var variable = match[1], indices = [];
var re = /\[\s*(\d+)\s*\]/g;
while(match = re.exec(string)) {
indices.push(+match[1]);
}
return { variable: variable, indices: indices };
} else {
return null;
}
}
var strings = [
"foo[0]",
"foo[4][2]",
"foo[4][2][234523][3]",
"foo [13]",
"foo[ 123 ]",
"foo[1] [2]",
"foo$;bar%[1]",
// The following are invalid
"foo[23]bar[55]",
"foo[123]bar",
"[123]bla",
"foo[urrrr]",
];
// Demo
strings.forEach(function(string) {
document.write("<pre>" + JSON.stringify(run(string), null, 4) + "</pre>");
});
That is not possible.
You can test if it is a correct statement, and as long you know how many indices you have you can select them, but there is no way to catch a group multiple times with javascript .exec.
However the language is regular. So it would be this:
^([a-zA-Z][a-zA-Z_0-9]*)(\[[0-9]*\])*
The first group will match the variable, and the second group (with the *quantifier 0-n times) the index.
So if you want to do this I recommend to use another parsing approach:
function parse(str) {
let idx = 0;
while(str[idx+1] != '[') {
idx++;
}
let name = str.substr(0, idx+1);
let indices = [];
while(str[idx+1] == '[') {
idx++;
let startIdx = idx;
while(str[idx+1] != ']') {
idx ++;
}
console.log(idx);
indices.push(str.substr(startIdx+1, idx-startIdx));
idx++;
}
return {name,indices};
}
Here is small ES6 version of the 2 step regular expression to get the desired array:
function interpret(s) {
return (/^(\w+)\s*((?:\[\s*\d+\s*\]\s*)*)$/.exec(s) || [,null]).slice(1).reduce(
(fun, args) => [fun].concat(args.match(/\d+/g)));
}
var s = 'foo[4][2][234523][3]';
var result = interpret(s);
console.log(result);
It first gets the 2 main parts via exec(), which returns the complete match, the function name and the rest in an array (with 3 elements). Then with slice(1) it ignores the first of those three. The two others are passed to reduce.
The reduce callback will only be called once, since there is no initial value provided.
This is convenient, as it actually means the callback gets the two parts as its two arguments. It applies the second regular expression to split the index string, and returns the final array.
The || [,null] will take care of the case when the original match fails: it ensures that reduce acts on [null] and thus will return null.

How to check if values in one JavaScript object are present in another one?

I am trying to compare json_str1 and json_str2, here it should return true as all elements in json_str1 are present in json_str2.
For now I am doing this the long way like this
json_str1 = '{"0":"a","1":"b","2":"c"}';
json_str2 = '{"0":"c","1":"b","2":"a"}';
json_obj1 = $.parseJSON(json_str1);
json_obj2 = $.parseJSON(json_str2);
arr1 = $.map(json_obj1, function(el) { return el });
arr2 = $.map(json_obj2, function(el) { return el });
if($(arr1).not(arr2).length === 0 && $(arr2).not(arr1).length === 0)
alert("equal");
else
alert("not equal");
How could I make it short and simple, without converting the objects into an array ?
https://jsfiddle.net/kq9gtdr0/
Use the following code:
Object.keys(json_obj1) . every(k1 =>
Object.keys(json_obj2) . some(k2 =>
json_obj1[k1] === json_obj2[k2]
)
);
In English:
Every key k1 in json_obj1 satisfies the condition that some key k2 in json_obj2 satisifies the condition that the value of json_obj1 with key k1 is equal to the value of json_obj2 with key k2.
Or in more conversational English:
Every value in the first object matches some value in the second.
Using lodash
var _ = require('lodash');
function compareValues(jstr1, jstr2) {
return _.isEqual(_.valuesIn(JSON.parse(jstr1)).sort(), _.valuesIn(JSON.parse(jstr2)).sort());
}
json_str1 = '{"0":"a","1":"b","2":"c"}';
json_str2 = '{"0":"c","1":"b","2":"a"}';
console.log(compareValues(json_str1, json_str2));
There is short and easy accurate way to this.
You can use a third party but extremely popular utility library called Lodash. Chaining functions you can check for equality.
First parse both JSON into objects
Then use _.values() to extract the values of all keys of each into separate arrays
Find difference of two arrays. If its an empty array then both of them are equal.
You can chain all the steps into one statement like:
_.isEmpty(_.difference(_.values(json_obj1), _.values(json_obj2)))
Example: https://jsfiddle.net/kq9gtdr0/4/
For more information:
https://lodash.com/docs#values
https://lodash.com/docs#difference
https://lodash.com/docs#isEmpty
You can include the library from CDN(https://cdn.jsdelivr.net/lodash/4.5.1/lodash.min.js) or download and use it as normal script. Lodash offers plenty of useful utility functions that makes JS programming a lot easier. You better try it out.
If you prefer using libraries, then you could use underscore isMatch
_.isMatch(object, properties)
Tells you if the keys and values in properties are contained in
object.
Extending the awesome answer by #user663031, in case you need to do deep comparison, here's code that works:
export function objectOneInsideObjectTwo(jsonObj1: any, jsonObj2: any): boolean {
return Object.keys(jsonObj1).every((k1) => {
if (parseType(jsonObj1[k1]) === 'dict') {
return objectOneInsideObjectTwo(jsonObj1[k1], jsonObj2[k1]);
}
if (parseType(jsonObj1[k1]) === 'array') {
const results: boolean[] = [];
jsonObj1[k1].forEach((o: any, i: number) => {
if (parseType(o) === 'dict') {
results.push(objectOneInsideObjectTwo(o, jsonObj2[k1][i]));
} else {
results.push(o === jsonObj2[k1][i]);
}
});
return results.every((r) => r);
}
return Object.keys(jsonObj2).some((k2) => jsonObj1[k1] === jsonObj2[k2]);
});
}
export function parseType<T>(v: T): string {
if (v === null || v === undefined) {
return 'null';
}
if (typeof v === 'object') {
if (v instanceof Array) {
return 'array';
}
if (v instanceof Date) {
return 'date';
}
return 'dict';
}
return typeof v;
}
You can try this
var json_str1 = {"0":"a","1":"b","2":"c"};
var json_str2 = {"0":"c","1":"b","2":"a"};
var flag = 1;
if(Object.keys(json_str1).length == Object.keys(json_str2).length){
Object.keys(json_str1).forEach(function(x){
if(!json_str2.hasOwnProperty(x) || json_str2[x] != json_str1[x]){
flag = 0;
return;
}
});
}
if(flag)
alert('equal');
else
alert('Not Equal');
If you want to find out if both Objects have the same keys there is no way to do this without at least converting the keys of both Objects to an array with Object.keys or looping through both Objects!
The reason is simple: It's clear that you have to compare the number of keys of both Objects and the only way to do this is by looping through all properties or Object.keys.
So I think the shortest way to do this is:
json_obj1 = JSON.parse('{"0":"a","1":"b","2":"c"}');
json_obj2 = JSON.parse('{"0":"c","1":"b","2":"a"}');
keys_1 = Object.keys(json_obj1);
keys_2 = Object.keys(json_obj2);
if(keys_1.length === keys_2.length && keys_1.every(key => keys_2.indexOf(key) >= 0)) {
alert('equal')
} else {
alert('not equal')
}
If you only want to check if all keys from json1 are present in json2 you can do:
json_obj1 = JSON.parse('{"0":"a","1":"b","2":"c"}');
json_obj2 = JSON.parse('{"0":"c","1":"b","2":"a"}');
if(Object.keys(json_obj1).every(key => key in json_obj2)) {
alert('equal');
} else {
alert('not equal');
}
In your question and comments you indicate you are only looking to verify that "all elements in json_str1 are present in json_str2". Your example code doesn't just do that, it checks for the complete equality of keys by testing if all the keys (not values) in the first object are in the second object AND all the keys in the second object are in the first object. By looking at your code, i assume that when you say "elements" you mean keys.
All that aside, this might help:
// Your first few lines of code
json_str1 = '{"0":"a","1":"b","2":"c"}';
json_str2 = '{"0":"c","1":"b","2":"a"}';
json_obj1 = $.parseJSON(json_str1);
json_obj2 = $.parseJSON(json_str2);
// My new code
var values1 = Object.keys(json_obj1).map(key => json_obj1[key]);
var values2 = Object.keys(json_obj2).map(key => json_obj2[key]);
// Check if every key in the first object is in the second object.
values1.every(k1 => values2.indexOf(k1) >= 0);
// OR
// Check for equality of keys by checking both directions.
values1.every(k1 => values2.indexOf(k1) >= 0) && values2.every(k2 => values1.indexOf(k2) >= 0);
That's 2 lines to get the keys, and one line to check. You only need one of those two checks.

javascript - match string against the array of regular expressions

Is there a way in JavaScript to get Boolean value for a match of the string against the array of regular expressions?
The example would be (where the 'if' statement is representing what I'm trying to achieve):
var thisExpressions = [ '/something/', '/something_else/', '/and_something_else/'];
var thisString = 'else';
if (matchInArray(thisString, thisExpressions)) {
}
Using a more functional approach, you can implement the match with a one-liner using an array function:
ECMAScript 6:
const regexList = [/apple/, /pear/];
const text = "banana pear";
const isMatch = regexList.some(rx => rx.test(text));
ECMAScript 5:
var regexList = [/apple/, /pear/];
var text = "banana pear";
var isMatch = regexList.some(function(rx) { return rx.test(text); });
http://jsfiddle.net/9nyhh/1/
var thisExpressions = [/something/, /something_else/, /and_something_else/];
var thisExpressions2 = [/else/, /something_else/, /and_something_else/];
var thisString = 'else';
function matchInArray(string, expressions) {
var len = expressions.length,
i = 0;
for (; i < len; i++) {
if (string.match(expressions[i])) {
return true;
}
}
return false;
};
setTimeout(function() {
console.log(matchInArray(thisString, thisExpressions));
console.log(matchInArray(thisString, thisExpressions2));
}, 200)​
You could use .test() which returns a boolean value when is find what your looking for in another string:
var thisExpressions = [ '/something/', '/something_else/', '/and_something_else/'];
var thisString = new RegExp('\\b' + 'else' + '\\b', 'i');
var FoundIt = thisString.test(thisExpressions);
if (FoundIt) { /* DO STUFF */ }
look this way...
function matchInArray(stringSearch, arrayExpressions){
var position = String(arrayExpressions).search(stringSearch);
var result = (position > -1) ? true : false
return result;
}
You can join all regular expressions into single one. This way the string is
scanned only once. Even with a sligthly more complex regular expression.
var thisExpressions = [ /something/, /something_else/, /and_something_else/];
var thisString = 'else';
function matchInArray(str, expr) {
var fullExpr = new RegExp(expr
.map(x=>x.source) // Just if you need to provide RegExp instances instead of strings or ...
// .map(x=>x.substring(1, x.length -2) // ...if you need to provide strings enclosed by "/" like in original question.
.join("|")
)
return str.match(fullExpr);
};
if (matchInArray(thisString, thisExpressions)) {
console.log ("Match!!");
}
In fact, even with this approach, if you need check the same expression set
against multiple strings, this is a few suboptimal because you are building
(and compiling) the same regular expression each time the function is called.
Better approach would be to use a function builder like this:
var thisExpressions = [ /something/, /something_else/, /and_something_else/];
var thisString = 'else';
function matchInArray_builder(expr) {
var fullExpr = new RegExp(expr
.map(x=>x.source) // Just if you need to provide RegExp instances instead of strings or ...
// .map(x=>x.substring(1, x.length -2) // ...if you need to provide strings enclosed by "/" like in original question.
.join("|")
)
return function (str) {
return str.match(fullExpr);
};
};
var matchInArray = matchInArray_builder(thisExpressions);
if (matchInArray(thisString)) {
console.log ("Match!!");
}
Consider breaking this problem up into two pieces:
filter out the items that match the given regular expression
determine if that filtered list has 0 matches in it
const sampleStringData = ["frog", "pig", "tiger"];
const matches = sampleStringData.filter((animal) => /any.regex.here/.test(animal));
if (matches.length === 0) {
console.log("No matches");
}
Andersh's solution will not work if you have global flags. A true return will toggle on and off on future identical tests.
regexArray.some( rx => rx.test( "a" )) // true
regexArray.some( rx => rx.test( "a" )) // false
regexArray.some( rx => rx.test( "a" )) // true
(read why here)
This works and is also a one-liner:
const isMatch = regexList.map( rx => rx.source).includes( string )
.source returns the text string of the RegExp pattern.
.map returns an array of these strings.
.includes returns if the string is in the array(if you need the index, use .indexOf)
Alternatively:
function isInsideArray( string, regexArray ){
return regexArray.map( regex => regex.source).includes( string )
}
function isInsideArray_Andersh( string, regexArray ){
return regexArray.some( rx => rx.test( string ))
}
const list_rx = [ /apple/g, /pear/g, /banana/g ],
string = "pear"
console.log( isInsideArray( string, list_rx ))
console.log( 'Andersh:', isInsideArray_Andersh( string, list_rx ))
console.log( 'Andersh (same test):', isInsideArray_Andersh( string, list_rx ))
let expressions = [ '/something/', '/something_else/', '/and_something_else/'];
let str = 'else';
here will be the check for following expressions:
if( expressions.find(expression => expression.includes(str) ) ) {
}
using Array .find() method to traverse array and .include to check substring
If you would like to use String.match(), in case your array contains both match strings and regular expressions, you can do
let str = "The quick brown fox";
let matches = ["fox", "The.*fox", /the.*fox/i];
let strInMatches = matches.some(match => str.match(match));
console.log(strInMatches);
So we make a function that takes in a literal string, and the array we want to look through. it returns a new array with the matches found. We create a new regexp object inside this function and then execute a String.search on each element element in the array. If found, it pushes the string into a new array and returns.
// literal_string: a regex search, like /thisword/ig
// target_arr: the array you want to search /thisword/ig for.
function arr_grep(literal_string, target_arr) {
var match_bin = [];
// o_regex: a new regex object.
var o_regex = new RegExp(literal_string);
for (var i = 0; i < target_arr.length; i++) {
//loop through array. regex search each element.
var test = String(target_arr[i]).search(o_regex);
if (test > -1) {
// if found push the element#index into our matchbin.
match_bin.push(target_arr[i]);
}
}
return match_bin;
}
// arr_grep(/.*this_word.*/ig, someArray)

Categories

Resources