For anyone interested, this is my mild reworking of Benj's code that works out the dependencies in Apex code. These changes are mainly aimed at performance (reducing CPU time from over a second to around 100ms for the default country/state dependency):
- Caches conversion of
validFor
to controlling field picklist indexes so instead of calculating a few hundred of those only 9 calculations are needed for the default county/state picklists
- Finds the "1"'s without checking every one of the "0" values in the bits string
- Only deserializes the picklist entry values needed for the code
- Ignores the picklist entries that are inactive
Outputs the values (code) not the labels so the resulting data is smaller Turns out that the state codes are not unique so the state label does need to be output
The output is of this form:
{
...
"CV" : [ ],
"CA" : [ "AB", "BC", "MB", "NB", "NL", "NT", "NS", "NU", "ON", "PE", "QC", "SK", "YT" ],
"CM" : [ ],
...
}
and the code is:
public class DependentPicklists {
private static final String BASE_64_CHARS = ''
+ 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
+ 'abcdefghijklmnopqrstuvwxyz'
+ '0123456789+/';
private class PicklistEntryBean {
public Boolean active {get; set;}
public String value {get; set;}
public String validFor {get; set;}
}
private Map<String, Set<Integer>> cache;
public Map<String, List<String>> controllingToDependentValues(SObjectField controlling, SObjectField dependent) {
Map<String, List<String>> results = new Map<String, List<String>>();
if (controlling != null && dependent != null) {
cache = new Map<String, Set<Integer>>();
List<String> controllingValues = new List<String>();
for (PicklistEntry pe : controlling.getDescribe().getPicklistValues()) {
if (pe.isActive()) {
results.put(pe.getValue(), new String[] {});
controllingValues.add(pe.getValue());
}
}
for (PicklistEntryBean pe : picklistEntryBeans(dependent.getDescribe().getPicklistValues())) {
if (pe.active) {
for (Integer index : validForToIndices(pe.validFor)) {
results.get(controllingValues.get(index)).add(pe.value);
}
}
}
}
return results;
}
private Set<Integer> validForToIndices(String validFor) {
Set<Integer> indices = cache.get(validFor);
if (indices == null) {
indices = new Set<Integer>();
String validForBits = base64ToBits(validFor);
for (Integer i = validForBits.indexOf('1'); i >= 0; i = validForBits.indexOf('1', i + 1)) {
indices.add(i);
}
cache.put(validFor, indices);
}
return indices;
}
private static String decimalToBinary(Integer val) {
String bits = '';
while (val > 0) {
Integer remainder = Math.mod(val, 2);
val = Integer.valueOf(Math.floor(val / 2));
bits = String.valueOf(remainder) + bits;
}
return bits;
}
private static String base64ToBits(String validFor) {
if (String.isEmpty(validFor)) return '';
String validForBits = '';
for (Integer i = 0; i < validFor.length(); i++) {
String thisChar = validFor.mid(i, 1);
Integer val = BASE_64_CHARS.indexOf(thisChar);
String bits = decimalToBinary(val).leftPad(6, '0');
validForBits += bits;
}
return validForBits;
}
private static PicklistEntryBean[] picklistEntryBeans(List<PicklistEntry> pes) {
return (List<PicklistEntryBean>) JSON.deserialize(JSON.serialize(pes), List<PicklistEntryBean>.class);
}
}