5

I have a regular expression that I want to use to pull ID's out of a JSON string. The expression itself appears to work fine based on the data I've given it, so that's good.

^(?:.*)(?:{"id":)(\d+)(?:.*)$

Unfortunately, it involves more than one non-capturing group ("(?:)"), and that seems to break Apex with the dreaded "Invalid regex at index 9." Is there actually a 1 non-capture group limit in Apex's regex flavor or is there something else wrong with this? I wasn't able to find any documentation on such a limit, so i'm inclined to think that there is some other error in my regex, but I can't find it!

Just for completeness, here's an example that is failing on my sandbox.

pattern myPattern = pattern.compile('^(?:.*)(?:{"id":)$'); 
matcher myMatcher = myPattern.matcher('{"user":{"id":11111,"userName":');
System.assert(myMatcher.matches() && myMatcher.hitEnd());

if(myMatcher.matches())
{
    system.debug(myMatcher.group(1));
}

* bolded for easier skimming

  • 3
    Any particular reason you want to use regex instead of deserializing into a concrete type or map structure? – Mark Pond Aug 4 '16 at 17:52
  • 2
    TLDR: yes. This object is coming back from a REST API that is fairly unstable. They are still updating it constantly, and it currently contains a massive number of different fields that I can't prevent the query from returning. All I need is the ID, and I thought that writing a regular expression for this would be fairly straightforward 😅 Since this code has to run quite often, I wanted to avoid the overhead of serializing massive objects that will need to be constantly updated in step with the external service. – Grisk Aug 4 '16 at 17:54
  • 1
    Are you trying to get the id value 11111? My regex is a tad rusty but I don't see you actually capturing any information. Also with the $ indicating the end of the string wouldn't the regex you provided stop before getting to the 11111? – dBeltowski Aug 4 '16 at 18:14
  • 1
    Also maybe not so relevant to the question, but wouldn't the regex be more efficient like this? ^(?:.*?)(?:{"id":)$ – dBeltowski Aug 4 '16 at 18:15
  • No worries! Check out the demo I linked to, the "(\d+)" bit is where I'm capturing the number. The simpler example that I gave inline just shows the Salesforce parser failing when I have two noncapture groups, but that isn't the same as the "real" regex that I linked to. As to the efficiency quesiton, I don't think so, but I could be wrong. The * itself indicates zero or more, so the ? added at the end is redundant, I think. I'll have to read more about that one. – Grisk Aug 4 '16 at 18:17
3

I was able to sort this out just now. Unfortunately, the error that the Force.com IDE gives is not terribly obvious, and the problem appears to be bug in the underlying Java interpreter (compiler? I forget).

Apex really doesn't like curly-brackets in strings. Double escaping them solves the problem!

pattern myPattern = pattern.compile('^(?:.*)(?:\\{"id":)(\\d+)(?:.*)$'); 
string examplestring = '\\{"user":\\{"id":11111,"userName":';
system.debug(examplestring);
matcher myMatcher = myPattern.matcher(examplestring);
System.assert(myMatcher.matches() && myMatcher.hitEnd());

if(myMatcher.matches())
{
    system.debug(myMatcher.group(1));
}

This is why it looked like the optional capture groups were the problem, my second one uses a curly-bracket!

2

Depending on the size of your payload, you might run out of heap space while parsing the string via regex.

If your payload is stable enough, this alternative method using the JSON serializer might allow you to fetch the User Id from the payload and ignore the rest as well as make it possible to quickly access attributes other than id without making your regex pattern more complicated.

String payload = '{"user":{"id":"11111","userName":"bobby@littletables.example", "photoUrl":"https://theurl.com/blah"}}';

Map<String, Object> jsonMap = (Map<String, Object>)JSON.deserializeUntyped(payload);

Map<String, Object> userAttributes = (Map<String, Object>)jsonMap.get('user');
system.debug(userAttributes);
// |DEBUG|{id=11111, photoUrl=https://theurl.com/blah, userName=bobby@littletables.example}

String userId = (String)userAttributes.get('id');
system.debug(userId);
// |DEBUG|11111

Another approach if you would like to be a little more memory efficient, since the JSON attributes which are not defined in the concrete Types are ignored during deserialization. This method would be appropriate if your payload has a stable enough structure for you to build classes to represent the data that is important to your application.

public class UserWrapper {
    public UserElement user { get; set; }
}

public class UserElement {
    public string id       { get; set; }
    public string userName { get; set; }
    public string photoUrl { get; set; }
}

String payload = '{"user":{"id":"11111","userName":"bobby@littletables.example", "photoUrl":"https://theurl.com/blah"}}';

// deserialize into the outer object, discarding the elements that don't match
UserWrapper uWrapper = (UserWrapper)JSON.deserialize(payload, UserWrapper.class);

// easy reference to the user item
UserElement userElement = uWrapper.user;

String userId = userElement.id;

system.debug(userId);
  • And that won't run out of heap space faster for doing all that individual memory allocation? I was under the impression that doing that much assignment might add a ton of addressable memory to each run of the trigger, which could cause a problem. – Grisk Aug 4 '16 at 21:13
  • 1
    You'll have to profile it yourself against some sample payloads. Without seeing the entire payload, it's pretty difficult to assess. – Adrian Larson Aug 4 '16 at 21:42
  • @Grisk, added another approach which would be more efficient on memory as well as allowing for your application to ignore the massive number of fields that you don't care about and cannot remove from the payload. The caveat being that the payload needs to be stable enough with the fields that you care about for you to define classes to represent the structure to deserialize the data into. – Mark Pond Aug 4 '16 at 22:09
  • Now that is a cool solution. I may have to try that, thanks for taking the time to write it up! – Grisk Aug 4 '16 at 22:39

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.