Salesforce: Acquiring Dependent Picklists in Apex

Introduction

They said I had to use the API and that Salesforce doesn’t provide a way to natively do it in Apex.  I said no.

Welcome back my people.  Once more I’ve encountered a situation which required quite a bit of elaborate effort to figure out.

Note: There is an optimized adjustment to some of the final code at the end of this article in my post Acquiring Dependent Picklists in Apex Contd.

The Background:

According to the API Docs for the DescribeSObjectResult, a PicklistEntry object has a validFor property which is not available in Apex, but is available through the API.  To me, this implied that if one were to serialize a PicklistEntry to JSON, this validFor property should become available. If such a scenario held true, then a deserialization of that content into a custom type which provided a property for validFor should allow us to access it in Apex.  That’s part one of the problem hypothetically solved.

The second problem is that Apex doesn’t make it simple to manipulate a byte array or a base64 string.  However, most of the time, we never really need to manipulate a byte array.  A proper understanding of decimal should suffice.  For this reason, I reference the MSDN library for a base64 mapping table [ http://msdn.microsoft.com/en-us/library/cc422512.aspx ] which will act as a reference for what I want to do.

The Issue:

You have a controlling picklist C, with N values and a dependent picklist D with M values.  Let n represent an index in N, and m represent an index in M, such that 0<=n<N and 0<=m<M.

For a value D[m] we need to know if it is associated with n. According the Salesforce API Docs, validFor is defined as:

A set of bits where each bit indicates a controlling value for which this PicklistEntry is valid.

The docs had some good sample code at the end which showed how to get dependent values for controlling ones.  However, we need to concern ourselves only with picklists for this problem.

The Scene:

For example, a value of “Blue” in D may have a validFor string of “AgAA”.  One must remember that the string is encoded in base64 (2^6).  This implies that “AgAA” uses 6 bits for each character.

The Explanation/Algorithm:

Since we don’t have access to the bits, what we can do, is get the corresponding value for each character from a base64 map of string to integers.  We can then shift each integer to it’s respective segment by defining the shift amount as “number of bits per segment” multiplied by “number of segments to shift”.  Therefore, if we reference “AgAA”, the first element get’s shifted to the left, to begin the binary string, by 3 segments.  The second element gets shifted by 2 segments, the third by 1, and the fourth by none.  By themselves, they would look like
000000 000000 000000 000000,000000 000000 000000 100000,000000 000000 000000 000000,000000 000000 000000 000000

After shifting, they look like

000000 100000 000000 000000

Once we’ve attained individual values, we sum them together to get full decimal representation of the string.  Now all we have to do is check the bit which corresponds to the index of the controlling value n. We must remember that the position we seek is in a 0 indexed (upper bound is 7) bit in a set of bytes (8 bits). So the target bit is defined as 7 – (n % 8). With this information, we will provide a means to “test the bit” for a given base64 encoded validFor string.  We will call this the “testBit” method.

Once we have a means to test the bit, we will get the values of the Controlling picklist and the dependent picklist.  For each dependent entry (D[m]), we will test it against a controlling picklist entry (C[n]) to see if it’s valid for that entry.

 The Solution:

Let us create a class called Bitset.  The purpose of this class is to provide a way to manipulate base64 values.  The property AlphaNumCharCodes is just there for ASCII values (not really used, but I might as well have them since computing them alongside the base 64 doesn’t take away any time).

Next we implement the testBit method I spoke about earlier.  This method determines the bytes being used as the length of validFor (e.g. AgAA) multiplied by the number of bits per character divided by 8.

After that, we calculate the target bit as I stated earlier as 7 – (n % 8) (in salesforce, 7 – Math.Mod(n, 8)).  Next, we calculate the target octet (now that we are seeing things in bytes) that has in the bit we want as

(bytesBeingUsed -1 ) – (n >> bytesBeingUsed).  How do i know this? According to the Salesforce API Docs. Test bit is defined as:

A bit ambiguous, but that’s what I translated it out to be.  That tells me that Salesforce depends on that bit combination to determine the dependency.

From here on out, I begin to shift the bits and calculate the full decimal value as I explained earlier up.

The final part is where the definition of validFor comes back into play.  Once we find the bit to compare, we apply a bit mask to zero out all other bits, then we shift to the target bit and check for a value of true (1) or false (0).

The final definition of the Bitset class comes out to be

Next, we define the PicklistEntry custom class.  It’s really just a “word for word” definition of the JSON serialization:

 The Meat:

Now we finally apply the features we just added.  There are a few things that you need to know first:

  • How to prepare for an arbitrary string to SObjectType association:  Schema.getGlobalDescribe() gives us a map of String to SObjectTypes.
  • How to prepare for an arbitrary field to SObjectField association: For an entity of type Schema.SObjectType, we have access to <MyEntity>.getDescribe().fields.getMap();
  • Both the classes, Bitset and TPicklistEntry, and the static method below GetDependentOptions,  are defined in a class I called TStringUtils.  This is not a required step, but it does help in understanding the code listed below.

 

My code is documented on a line by line level for readability purposes:

At the end of the day, there is a line between explaining what you did, and just plain out going to far.  I think I crossed that line today.  Oh well.

Note: There is an optimized adjustment to some of the final code in my post Acquiring Dependent Picklists in Apex Contd.

titancronusSalesforce: Acquiring Dependent Picklists in Apex

59 Comments on “Salesforce: Acquiring Dependent Picklists in Apex”

  1. Tommy

    Dude, DUDE!!!! This is awesome. The only suggestion I have is to maybe change the verbiage here, “are defined in a static class I called TStringUtils”, and remove the static word…..Just nitpicking. YOUR CODE ROCKS!!! Thank you so much!

    1. titancronus

      Thanks @Tommy. Always helps to have someone else see those little “unnecessary extras” in one’s post.

  2. green

    Mate, you the man!! Luckily I didn’t spend much time before I saw yours. Your code works, your comments and description make it looks simple. Thanks a lot!

    1. titancronus

      Glad to know that it doesn’t just work on my machine green (always got to wonder about that)

    1. titancronus

      Guys, I added some optimizations you may be interested in. The link is at the bottom of the post, feel free to check it out!

  3. Pingback: Acquiring Dependent Picklists In Apex Contd. | Tales of an earthling

  4. Poppy

    It’s hard to find your posts in google. I found it on 22 spot, you should build quality
    backlinks , it will help you to get more visitors.
    I know how to help you, just type in google – k2 seo tips

  5. Rakesh

    This is exactly what I’m looking for. Very interesting to know how dependency picklist works , thanks a ton!.

  6. Kelly

    You should be in mensa – i have no idea what any of your code actually does but i know it works. Thank you so much! Saved my butt…

    1. titancronus

      Lol, I don’t think I’m ready for mensa yet, but I do appreciate the kind words and I’m glad it helped. I know what “needing that functionality” feeds like.

  7. Harriett

    I see you share interesting stuff here, you
    can earn some extra money, your website has big potential, for the monetizing method, just search in google – K2 advices how to monetize a website

  8. Matthias Spicher

    Hey, nice post! It helped me a lot in understanding the calculation of dependencies of picklists. In one point though I think you might be wrong:

    Note: I didn’t say for each controlling entry, test the dependent ones. The reason for this is that, statistically speaking, it is more likely that you have more Dependent values (M) than controlling ones (N). As a result, if I loop through the bigger list and compare it against a smaller list, I end up with a average run time of O(M^N) instead of O(N^M). Mathematically speaking, for large values, M^N <= N^M.

    It's not O(M^N) or O(N^M), but O(M*N) instead, so actually it doesn't matter which one is the outer and which one the inner loop. Or in other words, for infinitely large picklists, complexity would still be O(n^2)).

    Cheers,

    Matthias

    1. titancronus

      Thank you for catching that Matthias and it has been corrected. I don’t know what I was thinking at that time. Much appreciated!

  9. Karthick

    Hey thanks for giving a such a wonderful information

    Can you tell me how to execute the following scenario.
    I have created field dependencies on countries and state picklist as such when we change the country the respective states should appear on the state picklist. So how can we achieve this using apex code

  10. Jennifer O'Neill

    Love the code – but need help – I am only returning a portion of the picklist values. Does anyone know why? Thank you in advance.

  11. Piyush

    Awesome Code.But something is wrong with new class although that is more fast . But older class is giving me perfect results. Please fix it. It giving me values of wrong values.

  12. Prachi

    Really nice code and very helpful for me. But I am getting “Apex CPU time limit exceeded Class” error. I am having 250 picklist value for controlling fields and I think that’s an issue with it. Can you please help me to solve. Your help will be appreciated.

  13. Zach McElrath

    Thanks so much for posting this!

    There’s one other type of “picklist dependency” in Salesforce that this approach unfortunately does not solve, and for which in my experience you still need the API for (and which is actually a much bigger problem), and that’s Record Type picklist value dependencies. Each Record Type is essentially a “super controlling field” for all picklist fields on an object. The problem is that this metadata is available through the API via the describeLayout() call, which there is no equivalent for in Apex at all. Which is why the following Ideas still so desperately need to be implemented by Salesforce:

    https://success.salesforce.com/ideaView?id=08730000000BpHAAA0
    https://success.salesforce.com/ideaView?id=08730000000l5SnAAI

    Once they expose some kind of describeLayout in Apex, we’ll be free to JSON-round-trip-it it and expose its private variables! 🙂

  14. Adam B

    This was super helpful. I’ve been writing a GUI in python to manage records in SF via API calls and I was able to transcribe this into some working Python 3 modules.

    I’m using a Python lib called simple_salesforce as the ‘auth’ object for my API calls, but here’s your code transcribed for Py3, if anyone is interested:


    # SF_PicklistParser.py
    import copy

    class Bitset():

    def __init__(self):
    self.AlphaNumCharCodes = {}
    self.Base64CharCodes = {}
    self.LoadCharCodes()

    def LoadCharCodes(self):
    self.AlphaNumCharCodes = {
    ‘A’:65,’B’:66,’C’:67,’D’:68,’E’:69,’F’:70,’G’:71,’H’:72,’I’:73,’J’:74,
    ‘K’:75,’L’:76,’M’:77,’N’:78,’O’:79,’P’:80,’Q’:81,’R’:82,’S’:83,’T’:84,
    ‘U’:85,’V’: 86,’W’:87,’X’:88,’Y’:89,’Z’:90
    }
    self.Base64CharCodes = {}
    #lower case
    pUpperCase = copy.deepcopy(self.AlphaNumCharCodes)
    for pKey in pUpperCase:
    #the difference between upper case and lower case is 32
    self.AlphaNumCharCodes.update({pKey.lower(): self.AlphaNumCharCodes.get(pKey)+32})
    #Base 64 alpha starts from 0 (The ascii charcodes started from 65)
    self.Base64CharCodes.update({pKey: self.AlphaNumCharCodes.get(pKey) – 65})
    self.Base64CharCodes.update({pKey.lower(): self.AlphaNumCharCodes.get(pKey) – (65) + 26})

    #numerics
    for i in range(10):
    self.AlphaNumCharCodes.update({str(i): i+48})
    #base 64 numeric starts from 52
    self.Base64CharCodes.update({str(i): i+52})

    def testBit(self, pValidFor, n):
    #the list of bytes
    pBytes = []
    #multiply by 6 since base 64 uses 6 bits
    bytesBeingUsed = (len(pValidFor) * 6) // 8
    #will be used to hold the full decimal value
    pFullValue = 0
    #must be more than 1 byte
    if (bytesBeingUsed > bytesBeingUsed)
    #the number of bits to shift by until we find the bit to compare for true or false
    shiftBits = (targetOctet * 8) + bit
    #get the base64bytes
    i = 0
    while i < len(pValidFor):
    #get current character value
    pBytes.append((self.Base64CharCodes.get((pValidFor[i:(i+1)]))))
    i += 1
    #calculate the full decimal value
    i = 0
    while i < len(pBytes):
    #used to shift by a factor 6 bits to get the value
    pShiftAmount = (len(pBytes)-(i+1)) * 6
    pFullValue = pFullValue + (pBytes[i] <> shiftBits;

    return tBitVal


    # BitOper.py
    from BitOper import Bitset
    import json

    def GetDependentOptions(pControllingFieldName, pDependentFieldName, recordId, auth):
    objResults = {}
    #get the string to sobject global map
    objGlobalMap = auth.case.describe_layout(recordId)
    #get the type being dealt with
    objGlobalMap = auth.case.describe_layout(recordId)
    # Set values to none for validation
    ctrl_ple = None
    dep_ple = None
    for item in objGlobalMap[‘detailLayoutSections’]:
    for child in item[‘layoutRows’]:
    for grand in child[‘layoutItems’]:
    for comp in grand[‘layoutComponents’]:
    #get the control values
    if str(pControllingFieldName).lower() == str(comp[‘value’]).lower():
    ctrl_ple = comp[‘details’][‘picklistValues’]
    #get the dependent values
    if str(pDependentFieldName).lower() == str(comp[‘value’]).lower():
    dep_ple = comp[‘details’][‘picklistValues’]
    #print(“Controlling picklist values: \n” + str(ctrl_ple) + “\n****************”)
    #print(“Dependent picklist values: \n” + str(dep_ple) + “\n****************”)
    #verify field names
    if (ctrl_ple is None) or (dep_ple is None):
    return objResults

    #iterate through the values and get the ones valid for the controlling field name
    objBitSet = Bitset()

    pControllingIndex = 0

    #set up the results
    while pControllingIndex < len(ctrl_ple):
    #get the pointer to the entry
    ctrl_entry = ctrl_ple[pControllingIndex]
    #get the label
    pControllingLabel = ctrl_entry['label']
    #create the entry with the label
    objResults.update({pControllingLabel: []})
    pControllingIndex += 1

    #check the dependent values
    pDependentIndex = 0
    while pDependentIndex < len(dep_ple):
    #get the pointer to the dependent index
    dep_entry = dep_ple[pDependentIndex];
    #get the valid for
    if dep_entry is None:
    pDependentIndex += 1
    continue
    pEntryStructure = json.dumps(dep_entry)
    objDepPLE = json.loads(pEntryStructure)
    #if valid for is empty, skip
    if (objDepPLE['validFor'] is None) or (objDepPLE['validFor'] == ''):
    pDependentIndex += 1
    continue
    #iterate through the controlling values
    pControllingIndex = 0
    while pControllingIndex >> auth = Salesforce(username=creds, password=creds,security_token=creds)
    >>> import SF_PicklistParser as pl_parser
    >>> pl_parser.GetDependentOptions(‘Status’, ‘Sub_Status__c’, ‘01280000000cYyhAAE’, auth)
    {‘New’: [‘Customer Requests Close’, ‘Inbound message received’, ‘Unassigned’], ‘Pending’: [‘Customer Requests Close’, ‘Inbound message received’, ‘Pro
    blem Report’, ‘Waiting on Partner’], ‘Closed’: [‘By customer’, ‘Contact not on Account’, ‘Created in error’, ‘Duplicate’, ‘Inbound message received’,
    ‘Issue not Technical’, ‘No Entitlement’, ‘No response from customer’, ‘Resolved’, ‘Unsupported configuration’], ‘Open’: [‘Assigned’, ‘Close in 24 hour
    s – No cust Resp’, ‘Close in 24 hours – Resolved’, ‘Close in 72 hours – No Cust Resp’, ‘Close in 72 hours – Resolved’, ‘Customer Requests Close’, ‘Cus
    tomer To Confirm Fix’, ‘Escalation Requested’, ‘Inbound message received’, ‘No Response’, ‘On Hold Per Customer’, ‘Unassigned’, ‘Waiting on Customer’,
    ‘Waiting on Escalation Engineer’, ‘Waiting on OPS’, ‘Waiting on Partner’, ‘Wait on Engineering’, ‘Working’]}

    Thanks again for your work on this. It was extremely helpful and got me through a challenging obstacle in my development!

    Cheers!

  15. vas

    Hello,

    Is it possible to provide some example how to use these classes?
    Suppose I have controlling list P1 (A1, B1, C1) and dependable list P2 (A2, B2, C2).

    How can I find what values of P2 belong to what values in P1 ?

    Thanks!

  16. Prasad

    Hi Nethaneel,

    Thank you very much for your work on this. It was extremely helpful and saved my time 🙂

  17. Yixao

    Great!
    But I’m newbie and having difficulties how to pass parameters to these classes. could you explain please?

  18. Trophy

    I have try to apply to the compound field of address, i.e. the “CountryCode” and “StateCode “. And it doesn’t work. I have use “user”/”Contact(MailingCountryCode and MailingStateCode)”/”Account(ShippingCountryCode and ShippingStateCode)” for testing but failed.

    So for the compound address fields, how can we get the dependency values of state?

  19. Hari

    Hi,

    I took your code and trying to run on one of my object and I am receiving NULL reference error in testBits method.

    Line: pFullValue = pFullValue + (pBytes[i] << (pShiftAmount));

    Can you please help me?

    Thanks
    Hari

  20. Hari

    Hi,

    I did further debugging and found below:

    My picklists are Product and Version (Product is controlling field for Version). I have Version value as 10.2.2 and converted to PicklistWrapper using below statement:
    objJsonEntries = (List)JSON.deserialize(JSON.serialize(objEntries), List.class);

    After converting:

    PicklistEntryWrapper:[active=true, defaultValue=false, label=10.2.2, validFor=hKv4H8C/8iEQAsh8, value=10.2.2]

    The “validFor” has “/” and the BitSet class is returning “null” for “/” at pBytes[i] in my previous comment.

    Line: pFullValue = pFullValue + (pBytes[i] << (pShiftAmount));

    Debug log for
    14:32:45.051 (2051450466)|USER_DEBUG|[44]|DEBUG|********** pValidFor char: h *** base 64 code: 33
    14:32:45.051 (2051580717)|USER_DEBUG|[44]|DEBUG|********** pValidFor char: K *** base 64 code: 10
    14:32:45.051 (2051705340)|USER_DEBUG|[44]|DEBUG|********** pValidFor char: v *** base 64 code: 47
    14:32:45.051 (2051829637)|USER_DEBUG|[44]|DEBUG|********** pValidFor char: 4 *** base 64 code: 56
    14:32:45.051 (2051952643)|USER_DEBUG|[44]|DEBUG|********** pValidFor char: H *** base 64 code: 7
    14:32:45.052 (2052075412)|USER_DEBUG|[44]|DEBUG|********** pValidFor char: 8 *** base 64 code: 60
    14:32:45.052 (2052198921)|USER_DEBUG|[44]|DEBUG|********** pValidFor char: C *** base 64 code: 2
    14:32:45.052 (2052320420)|USER_DEBUG|[44]|DEBUG|********** pValidFor char: / *** base 64 code: null
    14:32:45.052 (2052442088)|USER_DEBUG|[44]|DEBUG|********** pValidFor char: 8 *** base 64 code: 60
    14:32:45.052 (2052567420)|USER_DEBUG|[44]|DEBUG|********** pValidFor char: i *** base 64 code: 34
    14:32:45.052 (2052692673)|USER_DEBUG|[44]|DEBUG|********** pValidFor char: E *** base 64 code: 4
    14:32:45.052 (2052816002)|USER_DEBUG|[44]|DEBUG|********** pValidFor char: Q *** base 64 code: 16
    14:32:45.052 (2052939342)|USER_DEBUG|[44]|DEBUG|********** pValidFor char: A *** base 64 code: 0
    14:32:45.053 (2053061557)|USER_DEBUG|[44]|DEBUG|********** pValidFor char: s *** base 64 code: 44
    14:32:45.053 (2053185562)|USER_DEBUG|[44]|DEBUG|********** pValidFor char: h *** base 64 code: 33
    14:32:45.053 (2053308526)|USER_DEBUG|[44]|DEBUG|********** pValidFor char: 8 *** base 64 code: 60

    Anyone has any idea why this null exception? I tried to skip if the value is "null", then I got wrong mappings for depedent picklist.

    Thanks in advance.

    Hari

    1. Trophy

      I have the same issue as Hari.

      Having “/” in “validFor ” is very common and this always make the dependent fields wrong.

      Anyone has work around or enhancement?

    2. Trophy

      Hi,

      I have the same issue as Hari.

      Having “/” in “validFor ” is very common and this always make the dependent fields wrong.

      Anyone has work around or enhancement?

      1. Raj

        Add these lines at the end of the BitSet.LoadCharCodes

        Base64CharCodes.put(‘+’, 62);
        Base64CharCodes.put(‘/’, 63);

  21. Dimtry

    Hi,

    I have the same issue as Hari.

    Also, solutions doesn’t work correctly for dependent Picklist3: PickList1 -> PickList2 -> PickList3.

  22. Bob

    The problem with only seeing some of the dependent entries is in the alphaNumCharCodes where it is throwing an exception because the pValidFor starts with a char not in the Map.

    The following snippet fixed the issue for me.

    alphaNumCharCodes = new Map
    {
    ‘!’=>33,’\”‘=>34,’#’=>35,’$’=>36,’%’=>37,’&’=>38,’\”=>39,'(‘=>40,’)’=>41,’*’=>42,’+’=>43,’,’=>44,’-‘=>45,
    ‘.’=>46,’/’=>47,’0’=>48,’1’=>49,’2’=>50,’3’=>51,’4’=>52,’5’=>53,’6’=>54,’7’=>55,
    ‘8’=>56,’9’=>57,’:’=>58,’;’=>59,’60,’=’=>61,’>’=>62,’?’=>63,’@’=>64,
    ‘A’=>65,’B’=>66,’C’=>67,’D’=>68,’E’=>69,’F’=>70,’G’=>71,’H’=>72,’I’=>73,’J’=>74,
    ‘K’=>75,’L’=>76,’M’=>77,’N’=>78,’O’=>79,’P’=>80,’Q’=>81,’R’=>82,’S’=>83,’T’=>84,
    ‘U’=>85,’V’=> 86,’W’=>87,’X’=>88,’Y’=>89,’Z’=>90
    };

  23. Sabina

    Hi, Bob! Thanks for the code, it works great. I was wondering, did you also write a test class for this? I can’t figure out how to check in a test class if the method returns the right values. I’m not entirely sure it’s even possible 🙂

  24. Dwayne

    Thank you VERY much. This was the starting point of solving a pain point for me. The only issue I had with it was that I was trying to deal with a VERY large dataset – the Countries and States picklists. There was so much data that the CPU limits were being reached. However, I only actually needed to know if a given country HAD dependent values, not what they actually were, so I thought I might be able to refactor the code for my purposes. I did, and then realized that my refactoring could also be rolled back into the original code, to allow it to run against a much larger dataset. Rather than looping over the parent records for each possible child, it parses the entire validFor string into an array of valid parent indexes, and so it builds the list much more quicky.

    Here are my changes:

    A new method in the “Bitset” class:
    // Returns a list of all parent indexes for which this dependent entry is valid
    public List validFor(String pValidFor) {
    // Target list
    List ret = new List();

    // Start at offset 0
    Integer target = 0;

    // Process each character in order
    for(Integer i=0; i < pValidFor.length();i++){
    //get current character value
    Integer cur = (Base64CharCodes.get((pValidFor.Substring(i, i+1))));

    // 6 bits per character, starting at bit position 5
    for (Integer j = 0; j < 6; j++) {
    // Is the current bit set?
    if ((cur & 32) == 32) {
    // Yes, this index is valid
    ret.add(target);
    }
    // Shift the next bit into position
    cur <<= 1;

    // Move to the next target index
    target++;
    }
    }

    return ret;
    }

    The "testBit" function is no longer used.

    The refactored GetDependentOptions function:

    // Build a map of parents and their valid children (faster version)
    public static Map<String,List> GetDependentOptions(String pObjName, String pControllingFieldName, String pDependentFieldName){
    Map<String,List> objResults = new Map<String,List>();
    // This list will hold the children during the build, and will be copied to the final map at the end
    List<List> workingList = new List<List>();

    //get the string to sobject global map
    Map objGlobalMap = Schema.getGlobalDescribe();
    List counts = new List();
    if (!objGlobalMap.containsKey(pObjName))
    return objResults;
    //get the type being dealt with
    Schema.SObjectType pType = objGlobalMap.get(pObjName);
    Map objFieldMap = pType.getDescribe().fields.getMap();
    //verify field names
    if (!objFieldMap.containsKey(pControllingFieldName) || !objFieldMap.containsKey(pDependentFieldName))
    return objResults;
    //get the control values
    List ctrl_ple = objFieldMap.get(pControllingFieldName).getDescribe().getPicklistValues();
    //get the dependent values
    List dep_ple = objFieldMap.get(pDependentFieldName).getDescribe().getPicklistValues();

    //iterate through the values and get the ones valid for the controlling field name
    Bitset objBitSet = new Bitset();

    //set up the results
    for(Integer pControllingIndex=0; pControllingIndex<ctrl_ple.size(); pControllingIndex++){
    //get the pointer to the entry
    // Schema.PicklistEntry ctrl_entry = ctrl_ple[pControllingIndex];
    //get the label
    // String pControllingLabel = ctrl_entry.getLabel();
    //create the entry with the label
    // objResults.put(pControllingLabel,new List());
    workingList.add(new List());
    }

    //check the dependent values
    for(Integer pDependentIndex=0; pDependentIndex<dep_ple.size(); pDependentIndex++){
    //get the pointer to the dependent index
    Schema.PicklistEntry dep_entry = dep_ple[pDependentIndex];
    //get the valid for
    String pEntryStructure = JSON.serialize(dep_entry);
    TPicklistEntry objDepPLE = (TPicklistEntry)JSON.deserialize(pEntryStructure, TPicklistEntry.class);
    //if valid for is empty, skip
    if (objDepPLE.validFor==null || objDepPLE.validFor==''){
    continue;
    }

    List valids = objBitSet.validFor(objDepPLE.validFor);
    for(Integer v : valids) {
    // As a safety valve, make sure we didn’t get an index out of range
    if (v < workingList.size()) {
    workingList[v].add(objDepPLE.label);
    }
    }
    }

    // Now populate the map
    objResults.put('',new List());
    objResults.put(null,new List());
    for(Integer pControllingIndex = 0; pControllingIndex < ctrl_ple.size(); pControllingIndex++) {
    String pControllingLabel = ctrl_ple[pControllingIndex].getLabel();
    objResults.put(pControllingLabel, workingList[pControllingIndex]);
    }
    return objResults;
    }

    With these changes, the code can easily handle the countries and states picklists with no problem.

    1. James

      @Neel and @Dwayne – awesome, awesome work. I had to break out my Computer Science 101 notes to decode this validFor property, and ye really helped. Thank you!

  25. Carlos Iribar

    Very good solution. Just a little comment, Base 64 char also includes the character / and + too. Salesforce uses those character, then I needed to improved the code:
    //Method loads the char codes
    private void loadCharCodes(){
    AlphaNumCharCodes = new Map{
    ‘A’=>65,’B’=>66,’C’=>67,’D’=>68,’E’=>69,’F’=>70,’G’=>71,’H’=>72,’I’=>73,’J’=>74,
    ‘K’=>75,’L’=>76,’M’=>77,’N’=>78,’O’=>79,’P’=>80,’Q’=>81,’R’=>82,’S’=>83,’T’=>84,
    ‘U’=>85,’V’=> 86,’W’=>87,’X’=>88,’Y’=>89,’Z’=>90
    };
    Base64CharCodes = new Map();
    //lower case
    Set pUpperCase = AlphaNumCharCodes.keySet();
    for(String pKey : pUpperCase){
    //the difference between upper case and lower case is 32
    AlphaNumCharCodes.put(pKey.toLowerCase(),AlphaNumCharCodes.get(pKey)+32);
    //Base 64 alpha starts from 0 (The ascii charcodes started from 65)
    Base64CharCodes.put(pKey,AlphaNumCharCodes.get(pKey) – 65);
    Base64CharCodes.put(pKey.toLowerCase(),AlphaNumCharCodes.get(pKey) – (65) + 26);
    }
    //numerics
    for (Integer i=0; i<=9; i++){
    AlphaNumCharCodes.put(string.valueOf(i),i+48);
    //base 64 numeric starts from 52
    Base64CharCodes.put(string.valueOf(i), i + 52);
    }
    //Symbols
    Base64CharCodes.put('+', 62);
    Base64CharCodes.put('/', 63);
    }

  26. Andrew

    Hi Bob, thanks for the solution – especially liked the explanation of the approach.

    However, I have found that this implementation does not spot dependency for some picklist items. After researching this, it looked like an issue with converting the validFor into a byte value – overflow occurred, and it didn’t calculate correctly.

    So I have managed to re-implement the BitsetChecker class according to the SF documentation (please see my answer on Stack Overflow for details – http://salesforce.stackexchange.com/a/164491/29123).

    The solution is based on breaking the validFor value into bits by using the EncodingUtil.convertToHex(), and then breaking the received hex string into 2 character chunks – those reperesent the dependency bits. After that, we can applying the check of controlling picklist entry’s index only to the specific bit.

  27. Veena putta

    I am facing issue while copying the code and my dependent picklist values are not taking correctly 🙁

  28. Michael Roberts

    I’m not sure if you monitoring this any more but I wondering if length of the validFor affects the bit Shifting in
    anyway. All the examples that I have seen validFor has been 4 characters long but One pick list in my org is returning 8 characters. This pick list is not returning the correct values when it’s the controlling pick list. I believe that the size of validFor is the reason why the algorithm is failing.

    1. Glyn Anderson

      Michael,

      ValidFor will always be some multiple of 4 characters long. 4 characters can encode 24 bits of information. So as soon as there are more than 24 controlling field values, validFor will become 8 characters. A controlling field can have up to 300 values, which would result in validFor being a string 52 characters long.

      I recently posted a new solution for the dependent picklist value problem. It is a single self-contained method of about 30 lines of Apex. My post includes test code as well for 100% coverage with assertions.

      Please check out my blog here: https://glyntalkssalesforce.blogspot.com/2018/08/dependent-picklist-values-in-apex.html

      It works for arbitrarily large datasets, and it works even for Checkbox controlling fields.

      I hope it solves your problem!

  29. Logan

    Hey,

    Your code is very helpful for my requirement. First thanks.

    Your code is working for one level only. Not working extra one level.
    Below I have given my code. If you have any idea give me the solution.

    Thanks in Advance.

    /******Apex Class*******/
    public class TStringUtils {

    @AuraEnabled
    public static List getDependentOptions() {

    /******Object Name******/
    string pObjName = ‘InventoryItem__c’;

    /*******Controlling Field******/
    1. string conFieldName = ‘Inventory_Category_Mirror__c’;

    /*******Dependent for 1st controlling field and Controlling for 3rd depenent field*******/
    2. string conDepFieldName = ‘Inventory_Sub_Category_Mirror__c’;

    /*******Dependent for 2nd controlling field***********/
    3. string depFieldName = ‘Inventory_Sub_Category_Qualifier_Mirror__c’;

    List pickListWrappers = new List();
    picklistWrapper plWrapper = new picklistWrapper();

    //get the string to sobject global map
    Map objGlobalMap = Schema.getGlobalDescribe();
    if (!objGlobalMap.containsKey(pObjName))
    return pickListWrappers;

    //get the type being dealt with
    Schema.SObjectType pType = objGlobalMap.get(pObjName);
    Map objFieldMap = pType.getDescribe().fields.getMap();

    //verify field names
    if (!objFieldMap.containsKey(conFieldName) || !objFieldMap.containsKey(conDepFieldName))
    return pickListWrappers;

    //get the control values
    List ctrl_ple = objFieldMap.get(conFieldName).getDescribe().getPicklistValues();
    //get the dependent & control values
    List dep_ctrl_ple =
    objFieldMap.get(conDepFieldName).getDescribe().getPicklistValues();
    //get the dependent values
    List dep_ple = objFieldMap.get(depFieldName).getDescribe().getPicklistValues();

    //iterate through the values and get the ones valid for the controlling field name
    TStringUtils.Bitset objBitSet = new TStringUtils.Bitset();

    //set up the results for 1
    for(Integer pControllingIndex=0; pControllingIndex<ctrl_ple.size(); pControllingIndex++) {
    //get the pointer to the entry
    Schema.PicklistEntry ctrl_entry = ctrl_ple[pControllingIndex];
    //get the label
    String pControllingLabel = ctrl_entry.getValue();
    //create the entry with the label
    plWrapper.firstSetResults.put(pControllingLabel,new List());
    }
    //cater for null and empty
    plWrapper.firstSetResults.put(”,new List());
    plWrapper.firstSetResults.put(null,new List());

    //check the dependent values
    //For first set
    for(Integer pDependentIndex=0; pDependentIndex<dep_ctrl_ple.size(); pDependentIndex++) {
    //get the pointer to the dependent index
    Schema.PicklistEntry dep_entry = dep_ctrl_ple[pDependentIndex];
    //get the valid for
    String pEntryStructure = JSON.serialize(dep_entry);
    TStringUtils.TPicklistEntry objDepPLE = (TStringUtils.TPicklistEntry)JSON.deserialize(pEntryStructure, TStringUtils.TPicklistEntry.class);
    //if valid for is empty, skip
    if (objDepPLE.validFor==null || objDepPLE.validFor==''){
    continue;
    }
    //iterate through the controlling values
    for(Integer pControllingIndex=0; pControllingIndex<ctrl_ple.size(); pControllingIndex++){
    if (objBitSet.testBit(objDepPLE.validFor,pControllingIndex)){
    String pControllingLabel = ctrl_ple[pControllingIndex].getValue();
    plWrapper.firstSetResults.get(pControllingLabel).add(objDepPLE.Value);
    }
    }
    }

    //setup up the results for 2
    for(Integer pControllingIndex=0; pControllingIndex<dep_ctrl_ple.size(); pControllingIndex++) {
    //get the pointer to the entry
    Schema.PicklistEntry ctrl_entry1 = dep_ctrl_ple[pControllingIndex];

    //get the label
    String pControllingLabel1 = ctrl_entry1.getValue();
    //create the entry with the label
    plWrapper.secondSetResults.put(pControllingLabel1,new List());
    }
    //cater for null and empty
    plWrapper.secondSetResults.put(”,new List());
    plWrapper.secondSetResults.put(null,new List());

    //For second set
    for(Integer pDependentIndex=0; pDependentIndex<dep_ple.size(); pDependentIndex++) {
    //get the pointer to the dependent index
    Schema.PicklistEntry dep_entry1 = dep_ple[pDependentIndex];

    //get the valid for
    String pEntryStructure1 = JSON.serialize(dep_entry1);
    TStringUtils.TPicklistEntry objDepPLE1 = (TStringUtils.TPicklistEntry)JSON.deserialize(pEntryStructure1, TStringUtils.TPicklistEntry.class);

    //if valid for is empty, skip
    if(objDepPLE1.validFor==null || objDepPLE1.validFor==''){
    continue;
    }
    //iterate through the controlling values
    for(Integer pControllingIndex=0; pControllingIndex<dep_ctrl_ple.size(); pControllingIndex++){
    if (objBitSet.testBit(objDepPLE1.validFor,pControllingIndex)) {
    String pControllingLabel1 = dep_ctrl_ple[pControllingIndex].getValue();
    plWrapper.secondSetResults.get(pControllingLabel1).add(objDepPLE1.Value);
    }
    }
    }

    pickListWrappers.add(plWrapper);
    return pickListWrappers;
    }

    public class TPicklistEntry {
    public string active {get;set;}
    public string defaultValue {get;set;}
    public string label {get;set;}
    public string value {get;set;}
    public string validFor {get;set;}
    public TPicklistEntry() {}
    }

    public class Bitset {
    public Map AlphaNumCharCodes {get;set;}
    public Map Base64CharCodes {get; set; }
    public Bitset(){
    LoadCharCodes();
    }
    //Method loads the char codes
    private void LoadCharCodes() {
    AlphaNumCharCodes = new Map{
    ‘A’=>65,’B’=>66,’C’=>67,’D’=>68,’E’=>69,’F’=>70,’G’=>71,’H’=>72,’I’=>73,’J’=>74,
    ‘K’=>75,’L’=>76,’M’=>77,’N’=>78,’O’=>79,’P’=>80,’Q’=>81,’R’=>82,’S’=>83,’T’=>84,
    ‘U’=>85,’V’=> 86,’W’=>87,’X’=>88,’Y’=>89,’Z’=>90
    };
    Base64CharCodes = new Map();
    //lower case
    Set pUpperCase = AlphaNumCharCodes.keySet();
    for(String pKey : pUpperCase){
    //the difference between upper case and lower case is 32
    AlphaNumCharCodes.put(pKey.toLowerCase(),AlphaNumCharCodes.get(pKey)+32);
    //Base 64 alpha starts from 0 (The ascii charcodes started from 65)
    Base64CharCodes.put(pKey,AlphaNumCharCodes.get(pKey) – 65);
    Base64CharCodes.put(pKey.toLowerCase(),AlphaNumCharCodes.get(pKey) – (65) + 26);
    }
    //numerics
    for(Integer i=0; i<=9; i++){
    AlphaNumCharCodes.put(string.valueOf(i),i+48);
    //base 64 numeric starts from 52
    Base64CharCodes.put(string.valueOf(i), i + 52);
    }
    }

    public Boolean testBit(String pValidFor,Integer n) {
    //the list of bytes
    List pBytes = new List();

    //multiply by 6 since base 64 uses 6 bits
    Integer bytesBeingUsed = (pValidFor.length() * 6)/8;

    //will be used to hold the full decimal value
    Integer pFullValue = 0;

    //must be more than 1 byte
    if(bytesBeingUsed > bytesBeingUsed);
    //the number of bits to shift by until we find the bit to compare for true or false
    Integer shiftBits = (targetOctet * 8) + bit;

    //get the base64bytes
    for(Integer i=0;i<pValidFor.length();i++){
    //get current character value
    pBytes.Add((Base64CharCodes.get((pValidFor.Substring(i, i+1)))));
    }
    //calculate the full decimal value
    for(Integer i = 0; i < pBytes.size(); i++) {
    Integer pShiftAmount = (pBytes.size()-(i+1))*6;//used to shift by a factor 6 bits to get the value
    pFullValue = pFullValue + (pBytes[i] <> shiftBits;

    return tBitVal == 1;
    }
    }

    public class pickListWrapper {
    @AuraEnabled
    public Map<String,List> firstSetResults = new Map<String,List>();
    @AuraEnabled
    public Map<String,List> secondSetResults = new Map<String,List>();
    }
    }

    1. Glyn Anderson

      Liza, I recently posted a new solution for the dependent picklist value problem. It is a single self-contained method of about 30 lines of Apex. My post includes test code as well for 100% coverage with assertions.

      Please check out my blog here: https://glyntalkssalesforce.blogspot.com/2018/08/dependent-picklist-values-in-apex.html

      It should work for arbitrarily large datasets, and work even for Checkbox controlling fields.

      I hope it solves your problem!

  30. Sarvesh

    Hi all,
    Some time never gives the correct values from the controlling field may be failing due to validfor size. please help me to fix this algorithm to work for every values.

    1. Glyn Anderson

      Sarvesh, I recently posted a new solution for the dependent picklist value problem. It is a single self-contained method of about 30 lines of Apex. My post includes test code as well for 100% coverage with assertions.

      Please check out my blog here: https://glyntalkssalesforce.blogspot.com/2018/08/dependent-picklist-values-in-apex.html

      It should work for arbitrarily large datasets, and work even for Checkbox controlling fields.

      I hope it solves your problem!

  31. Rahul Goyal

    anyone has the updated code, it is not working for large data set Please provide with the component details

    1. Glyn Anderson

      Rahul, I recently posted a new solution for the dependent picklist value problem. It is a single self-contained method of about 30 lines of Apex. My post includes test code as well for 100% coverage with assertions.

      Please check out my blog here: https://glyntalkssalesforce.blogspot.com/2018/08/dependent-picklist-values-in-apex.html

      It should work for arbitrarily large datasets, and work even for Checkbox controlling fields.

      I hope it solves your problem!

Leave a Reply

Your email address will not be published. Required fields are marked *