1

Given a string like "['chad', 123, ['tankie'], '!!!']", I need to return a boolean stating whether this string is a valid array or not.

Am open to most solutions, including regex.

Erik Åsland
  • 9,542
  • 10
  • 30
  • 58
  • 2
    I shudder to say it, but what about `eval()`? – SpeedOfRound Nov 15 '19 at 20:02
  • 1
    Your given string is actually not an array. That would need to have the string `chad` to end in a single quote. – line-o Nov 15 '19 at 20:03
  • Are your strings always delimited by single quotes instead of double quotes? – line-o Nov 15 '19 at 20:04
  • 1
    A string is always an array - an array of characters. If you mean that the content can be an array, like a JSON array, then you need to define the rules for what makes the contents of a string into something that can be parsed into an array of object(s). – fredrik Nov 15 '19 at 20:04
  • 1
    It would be easier if you only accepted valid JSON (double quotes), using `JSON.parse()`. It would throw an error if not valid, which you could catch to return false. If you need to accept the input you provided as an example, `eval()` would also be easy, but not secure depending on your use case (eg. `"[alert('P0wned!')]"`). Are all the items supposed to have primitive types? – blex Nov 15 '19 at 20:10
  • 1
    If it's valid Javascript, you should be able to replace the singlequotes with doublequotes, and parse as JSON etc. – adeneo Nov 15 '19 at 20:12
  • At minimum you can check if the string starts with a `[` and end with a `]`, then proceed to `eval()`. I don't think you can comprehensively regex for a valid JS array because of all the possible features such as anonymous functions which allow for code execution. – MonkeyZeus Nov 15 '19 at 20:13
  • @adeneo You can but then someone will supply a perfectly valid JS array constructed as `["chad", 123, ['ta"nkie'], '!!!']` as the input and now you've danced your way into an invalid JSON for no reason. – MonkeyZeus Nov 15 '19 at 20:15
  • Do you want to support any possible javascript literal, or just a subset? Like "my arrays always contain single quoted strings and other arrays"? – georg Nov 15 '19 at 20:19
  • Possible duplicate of [How to check if an object is an array?](https://stackoverflow.com/questions/4775722/how-to-check-if-an-object-is-an-array) – Pedro Lobito Nov 15 '19 at 21:27

5 Answers5

4

Assuming you want to support some subset of the Javascript grammar, you can use regular expressions to remove whitespace and scalar literals and then check if what is remaining matches the nested pattern [,[,,,],,,].

let remove = [
    /\s+/g,
    /'(\\.|[^'])*'/g,
    /"(\\.|[^"])*"/g,
    /\d+/g,
];

let emptyArray = /\[,*\]/g;

function stringIsArray(str) {

    for (let r of remove)
        str = str.replace(r, '');

    if (str[0] !== '[')
        return false;

    while (str.match(emptyArray))
        str = str.replace(emptyArray, '');

    return str.length === 0;
}

console.log(stringIsArray("'abc'"));
console.log(stringIsArray(`['abc', ['def', [123, 456], 'ghi',,],,]`));
console.log(stringIsArray(String.raw`
          ['a"b"c', ["d'e'f", 
     [123, [[   [[["[[[5]]]"]]]]], 456], '\"\'""""',,],,]
`));

If you want all Javascript grammar to be supported (e.g. arrays that contain objects that contain arrays etc), you need a real parser. I wrote a module called litr that does exactly that - evaluate javascript literals, all of them:

const litr = require('litr'); // or <script src=litr.js>

myJsObject = litr.parse(myString);
console.log('is Array?', Array.isArray(myJsObject))

Basically, it's a thin wrapper around a PEG.js javascript grammar.

georg
  • 211,518
  • 52
  • 313
  • 390
  • Why did you change your answer? – Erik Åsland Nov 15 '19 at 21:01
  • This has good elements in it like parsing out the quoted stuff. However, compile-time constant literals might include more than can be matched with just a `\d`. Examples include floats, exponents, signs, math operative symbols, string catenation, etc.. I guess boolean's true/false as well. –  Nov 15 '19 at 21:47
  • @x15: see the second part of the answer. – georg Nov 15 '19 at 21:51
  • Yeah I see it. Itss a little hard to look at home grown stuff to do this kind of thing. –  Nov 15 '19 at 21:53
  • @x15: not sure what you mean by that, anyways, thanks for the input. – georg Nov 15 '19 at 23:19
3

If you need to support "any" string, you could use new Function() as a much safer alternative to eval(). [ source ]

function stringIsArray(str) {
  try {
    return new Function(`return Array.isArray(${str})`)();
  } catch {
    return false;
  }
}

console.log(stringIsArray("abc"));
console.log(stringIsArray("['chad', 123, ['tankie'], '!!!']"));
Tyler Roper
  • 21,445
  • 6
  • 33
  • 56
  • 1
    How about `['c"h"ad', "1'2'3", 't\\"ankie']` etc? – georg Nov 15 '19 at 20:15
  • 1
    @georg I've added an alternative. – Tyler Roper Nov 15 '19 at 20:21
  • `new Function` and `eval` are basically the same thing. Despite of what that MDN page says, there's no difference between them in security or performance. Of course, both do solve the OP's immediate problem (if they are careful enough). – georg Nov 15 '19 at 20:46
  • @georg Eh, I believe "basically the same thing" is a slight overstatement ([e.g.](https://stackoverflow.com/a/4599946/2026606)), though agreed that both require some forethought as it relates to risk/exploitation. With that said, I do think that an approach like yours - using strictly string manipulation - is OP's best bet. – Tyler Roper Nov 15 '19 at 20:49
1
let strtotest = "['chad', 123, ['tankie'], '!!!']"
Array.isArray(eval(strtotest))

This will return true if the array is valid. It will also throw an error if the syntax within the string is incorrect, so you'll have to handle that aswell.

I'd like to point out that eval is a horrible thing that you should never use, but seeing as the requirement of this question is crazy, I thought this solution is fitting.

SpeedOfRound
  • 1,210
  • 11
  • 26
  • > but seeing as the requirement of this question is crazy, I thought this solution is fitting. What do you know about the data that is handled here? – line-o Nov 15 '19 at 20:21
1

You should not use an eval (for obvious reasons of code injection). Parsing as JSON, what I will be doing, is not the same as an array, of course, but it's the closest you're going to get without the use of eval().

Something you can use is the JSON.parse method. Only downside here is that all strings must be delimited with double quotes (") due to the restriction of JSON.

Quick example using the nodejs interpreter:

> var arrayString = '["chad", 123, ["tankie"], "!!!"]';
undefined
> JSON.parse(arrayString);
[ 'chad', 123, [ 'tankie' ], '!!!' ]
> var notArrayString = 'asdas!asd1{}1239]1[';
undefined
> JSON.parse(notArrayString);
Thrown:
SyntaxError: Unexpected token a in JSON at position 0
> 

You could then use the Array.isArray method to check whether it is an array.

function stringIsArray(str) {
    try {
        return Array.isArray(JSON.parse(str));
    } catch (e) {
        return false;
    }
}

stringIsArray('["chad", 123, ["tankie"], "!!!"]'); // returns true
stringIsArray('something else, not an array'); // returns false

You could add a str.replace('\'', '"') if you don't care about the quotes.

Daniel_I_Am
  • 196
  • 1
  • 5
1

Maybe it's useful for someone else, you could do this too and it's in a few lines. if you define in 'a' the string that you want to analyze

a = "['chad', 123, ['tankie'], '!!!']";
Array.isArray( JSON.parse( a.replace(/'/g,'"') ) );

this will return true or false

manhattan
  • 133
  • 2
  • 10