4

I'm trying to improve my understanding of Regex, but this one has me quite mystified. I started with some text defined as:

var txt = "{\"columns\":[{\"text\":\"A\",\"value\":80},{\"text\":\"B\",\"renderer\":\"gbpFormat\",\"value\":80},{\"text\":\"C\",\"value\":80}]}";

and do a replace as follows:

txt.replace(/\"renderer\"\:(.*)(?:,)/g,"\"renderer\"\:gbpFormat\,");

which results in:

"{"columns":[{"text":"A","value":80},{"text":"B","renderer":gbpFormat,"value":80}]}"

What I expected was for the renderer attribute value to have it's quotes removed; which has happened, but also the C column is completely missing! I'd really love for someone to explain how my Regex has removed column C?

As an extra bonus, if you could explain how to remove the quotes around any value for renderer (i.e. so I don't have to hard-code the value gbpFormat in the regex) that'd be fantastic.

2
  • Where is txt coming from? It may be broken JSON, I'd try to fix it at the source. Then you can parse it and change the attribute using JavaScript syntax. Commented Nov 5, 2014 at 23:51
  • That's because you are using a greedy operator, change it to a lazy one using \"renderer\"\:(.*?)(?:,) Commented Nov 5, 2014 at 23:53

3 Answers 3

2

You are using a greedy operator while you need a lazy one. Change this:

"renderer":(.*)(?:,)
              ^---- add here the '?' to make it lazy

To

"renderer":(.*?)(?:,)

Working demo

enter image description here

Your code should be:

txt.replace(/\"renderer\"\:(.*?)(?:,)/g,"\"renderer\"\:gbpFormat\,");

If you are learning regex, take a look at this documentation to know more about greedyness. A nice extract to understand this is:

Watch Out for The Greediness!

Suppose you want to use a regex to match an HTML tag. You know that the input will be a valid HTML file, so the regular expression does not need to exclude any invalid use of sharp brackets. If it sits between sharp brackets, it is an HTML tag.

Most people new to regular expressions will attempt to use <.+>. They will be surprised when they test it on a string like This is a first test. You might expect the regex to match and when continuing after that match, .

But it does not. The regex will match first. Obviously not what we wanted. The reason is that the plus is greedy. That is, the plus causes the regex engine to repeat the preceding token as often as possible. Only if that causes the entire regex to fail, will the regex engine backtrack. That is, it will go back to the plus, make it give up the last iteration, and proceed with the remainder of the regex.

Like the plus, the star and the repetition using curly braces are greedy.

Sign up to request clarification or add additional context in comments.

Comments

0

Try like this:

txt = txt.replace(/"renderer":"(.*?)"/g,'"renderer":$1');

The issue in the expression you were using was this part:

(.*)(?:,)

By default, the * quantifier is greedy by default, which means that it gobbles up as much as it can, so it will run up to the last comma in your string. The easiest solution would be to turn that in to a non-greedy quantifier, by adding a question mark after the asterisk and change that part of your expression to look like this

(.*?)(?:,)

For the solution I proposed at the top of this answer, I also removed the part matching the comma, because I think it's easier just to match everything between quotes. As for your bonus question, to replace the matched value instead of having to hardcode gbpFormat, I used a backreference ($1), which will insert the first matched group into the replacement string.

Comments

0

Don't manipulate JSON with regexp. It's too likely that you will break it, as you have found, and more importantly there's no need to.

In addition, once you have changed

'{"columns": [..."renderer": "gbpFormat", ...]}'

into

'{"columns": [..."renderer": gbpFormat, ...]}'      // remove quotes from gbpFormat

then this is no longer valid JSON. (JSON requires that property values be numbers, quoted strings, objects, or arrays.) So you will not be able to parse it, or send it anywhere and have it interpreted correctly.

Therefore you should parse it to start with, then manipulate the resulting actual JS object:

var object = JSON.parse(txt);
object.columns.forEach(function(column) {
    column.renderer = ghpFormat;
});

If you want to replace any quoted value of the renderer property with the value itself, then you could try

    column.renderer = window[column.renderer];

Assuming that the value is available in the global namespace.

This question falls into the category of "I need a regexp, or I wrote one and it's not working, and I'm not really sure why it has to be a regexp, but I heard they can do all kinds of things, so that's just what I imagined I must need." People use regexps to try to do far too many complex matching, splitting, scanning, replacement, and validation tasks, including on complex languages such as HTML, or in this case JSON. There is almost always a better way.

The only time I can imagine wanting to manipulate JSON with regexps is if the JSON is broken somehow, perhaps due to a bug in server code, and it needs to be fixed up in order to be parseable.

2 Comments

It's funny; every time I ask a question to learn more about Regexp, people say '"Don't use Regexp". Well guess what? Regexp exists in a lot of places, can be very useful, and I'd like to learn it. So no, my question doesn't fall into the category you mention.
@Francis: sure, but you should come up with more realistic examples of useful ways to use regexp, not things like parsing HTML. Remember that SO is not just a Q&A site, but rather a repository of knowledge for future generations of programmers. We don't want to leave them examples of bad practices. Since people also search for questions, within SO and also through Google, it's also better to give questions good titles that will help them find what they are looking for. In this case, the relevant question is not that the "regexp is removing an Object", but rather "regexp is replacing too much."

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.