BlogSalesforce

Simple Clean Maskable ID Generator

By May 29, 2019 June 3rd, 2019 2 Comments
Generating a unique ID seems like a simple task, but sometimes it just isn’t. Especially if your boss wants it in a strange format.

A Quick, Simple, Clean ID Generator

Of course, you could use the built-in auto-number system that Salesforce provides. You could even just use a Salesforce ID – you know it’s going to be unique. However, you might just need a number with a unique format, formatted in a certain way, e.g., XX-XXX-XXXX, that is not going to come back and bite you with some rude letter combination.

No one wants an ID like 12-HOT-DUDE.

Highlevel

My approach is pretty simple, and since it’s tied to querying Salesforce for prior IDs may not work for everyone.

My unique approach is thus:

  1. Generate an ID (details below).
  2. Look for it in Salesforce. If not found, return it.
  3. Generate X (in my case 10) IDs (this is quick).
  4. Look for all 10 at once in Salesforce (better to look for all in one query than one by one).
  5. If there are less than 10 matches get the non matched Ids and return one of them.
  6. Else, generate an error! Perhaps your ID length is too short (and hence not unique enough)!

Details

So, the inner workings of generating one of these IDs are, again a little tricky.

Why? Formatting and making the ID clean can be fiddly!

There are lots of tricky and code-intensive ways to try to detect if a word is bad or not… but in English (and probably other languages), this is pretty hard.

An easier approach is just to remove the ingredients that would allow the construction of a word at all.

First, we want to mix up letters and numbers — this makes it a LOT harder to construct a word! Next, we remove numbers that look like letters and letters that are commonly involved in making words.

This is what remains – and it must be a good list because these are the letters and numbers that you are allowed when entering serial numbers for Microsoft products.

While I can’t find any official documentation about this, it’s clear Microsoft uses the same list. Finally, we also remove a few outliers that might still conceivably still make words:

  • The numbers: ‘3’,’4′,’6′,’7′,’8′,’9′
  • The letters: ‘B’,’C’,’D’,’F’,’G’,’H’,’J’,’K’,’M’,’P’,’Q’,’R’,’T’,’V’,’W’,’X’,’Y’
  • The letter combos: ‘TH’,’DT’,’TT’,’KK’,’CK’

From here, we just randomly pick letters and numbers from the lists above, excluding any of the combos.

This is just a matter of getting random integers in the right range (code shown here):

public static String getId(Integer length){
  //MICROSOFT Omits these letters and numbers: 0 1 2 5   A E I O U   L N S Z
  //leaving: 
  //array of numbers: [3,4,6,7,8,9]
  //array of letters: ['B','C','D','F','G','H','J','K','M','P','Q','R','T','V','W','X','Y']

  Map<Integer,List<String>> seeds = new Map<Integer,List<String>>{
   0=>new String[]{'3','4','6','7','8','9'},
   1=>new String[]{'B','C','D','F','G','H','J','K','M','P','Q','R','T','V','W','X','Y'}
  };
  Set<String> invalidCombinations = new Set<String>{'TH','DT','TT','KK','CK'};
  Map<String,Integer> charactersUsed = new Map<String,Integer>();
  String[] generatedString = new String[]{};
  String[] seedArray;
  Integer seedArrayLength;
  Integer elementIndex;
  for (Integer i = 0; i < length; i++){
    
    Integer startWith = getRandomInt(2);
    
    seedArray = seeds.get(startWith);
    Integer generatedStringLength = generatedString.size();
    Boolean valid = false;
    Integer counter = 0;
    //while we haven't added anything to the array
    while (generatedStringLength == generatedString.size() && counter < 100){
      counter++;
      elementIndex = getRandomInt(seedArray.size());
      String element = seedArray[elementIndex];
      if (generatedStringLength > 0){
        String previousElement = generatedString[i - 1];
        if (!invalidCombinations.contains(previousElement + element)){
          generatedString.add(element);
        }
      }
      else {
        generatedString.add(element);
      }
    }
  }
  return String.join(generatedString,'');
}

At this point, we have a nice ID, un-masked.

To mask it, we use a little bit of regex and a loop. There are probably lots of ways of doing this, but this is a pretty simple one.

  //this is the essence of the mask routine (look at my github for more details)
  String result = mask.replaceAll('X', '_');
  String regExp = '_';
  String[] idChars = idString.split('');
  //loop through id and and replace all placeholder chars
  for (String idChar : idChars){
    result = result.replaceFirst(regExp, idChar);
  }

Hence a mask of ‘XX-XXX-XXXX’ becomes ‘3B-4C9-G8J6’ for example.

Then, we return this nicely masked, generated ID back to the top routine, where it is compared against other IDs in the database.

To use my version on Github call like this:
“`acct.Code__c = IDGenerator.getUniqueId(6, ‘Account’, ‘Code__c’, ‘XXX-XXX’);“`

No Problemo!!

All the files are located on Github.

Enjoy!

Apex Tricks

Have questions? Let us know in a comment below, or contact our team directly. You can also check out our other posts on customizing your Salesforce solution.

Caspar Harmer

Caspar Harmer

Caspar is a New Zealander working for Soliant from far-off Wellington. He loves exploring new technologies and solving problems. Caspar also loves getting into the outdoors; he runs, mountain bikes and does a lot of orienteering when he can fit it in.

2 Comments

  • Avatar Jurgis says:

    Hw do you make sure you do not get collisions?

    • Avatar Caspar Harmer says:

      It’s tied back to the database. I first generate 1 code and check if it’s unique. If not, I then generate 10 and check them all simultaneously. If all 10 fail, I generate an error code – at this point, I’d say it’s time to increase the length of the id. It’s a compromise (using the 1 then 10 approach), I’d be happy to modify if you had suggestions.

Leave a Reply