1

As a Salesforce developer, I am frequently working with Maps and am looking to abstract the process of generating maps from SObject lists.

I've created the following helper class:

private class MapBySharedKeyExtractor {
    SObjectField keyField;
    XAP_PRED_SObjectPredicateIntf predicate;

    MapBySharedKeyExtractor(SObjectField keyField, XAP_PRED_SObjectPredicateIntf predicate) {
        this.keyField = keyField;
        this.predicate = predicate;
    }

    public Map<Object, List<SObject>> extractFrom(List<SObject> homogeneousSObjectList) {
        if (homogeneousSObjectList == null || homogeneousSObjectList.isEmpty()) {
            return null;
        }

        Map<Object, List<SObject>> sObjectListByKeyMap = new Map<Object, List<SObject>>();
        for (SObject sObj : homogeneousSObjectList) {
            if (this.predicate.isTrueFor(sObj)) {
                Object key = sObj.get(this.keyField);
                if (!sObjectListByKeyMap.containsKey(key)) {
                    sObjectListByKeyMap.put(key, new List<SObject>());
                }
                sObjectListByKeyMap.get(key).add(sObj);
            }
        }
        return sObjectListByKeyMap;
    }
}

This is the prediate interface:

public interface XAP_PRED_SObjectPredicateIntf {
    Boolean isTrueFor(SObject sObj);
}

However, as the final step of this journey, I would like to be able to change the generated Map into concrete types, so I would like (for example) to have a helper method:

  public static Map<Id, List<SObject>> mapBySpecifiedIdField(List<SObject> homogeneousSObjectList, SObjectField idField) {
        Map<Object, List<SObject>> sObjectListBySpecifiedObjectMap
                = new MapBySharedKeyExtractor(idField, new XAP_PRED_SObjectFieldHasNonNullValue(idField))
                .extractFrom(homogeneousSObjectList);

        String mapType = 'Map<Id, List<' + homogeneousSObjectList[0].getSObjectType() + '>>';
        Map<Id, List<SObject>> concreteSObjectListByIdMap = (Map<Id, List<SObject>>) Type.forName(mapType).newInstance();
        concreteSObjectListByIdMap.putAll(sObjectListBySpecifiedObjectMap);
        return concreteSObjectListByIdMap;
    }

However this fails because this is invalid:

 concreteSObjectListByIdMap.putAll(sObjectListBySpecifiedObjectMap);

Is there any way I can make this work without iterating through sObjectListBySpecifiedObjectMap and casting each key individually?

5
  • @Sebastian Kessel, how did you manage to edit my post only 24 seconds after I asked it!? :-) May 1, 2019 at 15:42
  • That's how fast I am. :D May 1, 2019 at 15:55
  • this might be relevant GitHub SObjectIndex to allow indexing lists of sobjects by any sobjectField
    – cropredy
    May 1, 2019 at 17:24
  • @cropredy, care to be more specific with how this helps with the above? It looks like it strives to achieve a similar goal, but (1) without filtering by predicate and (2) forcing the consumer to live without type safety during the life of the SObjectIndex (since SFDC doesn't have parameterized types) May 1, 2019 at 17:33
  • Brian -- more in the way of an alternative approach to pivoting sobjects by values; not necessarily as strictly addressing your OP
    – cropredy
    May 1, 2019 at 18:17

2 Answers 2

5

No. You can't convert key types without a loop. The Map structure is not flexible in this way. The List type is a bit more flexible, however, and so you can make this minor simplification, as concretely typing the List<SObject> offers few tangible benefits unless you plan to upsert it.

Map<Id, List<SObject>> concreteSObjectListByIdMap = new Map<Id, List<SObject>>();

No need for dynamic Type.forName instantiation.

1
0

This isn't really the answer I am looking for because it is computationally expensive, but this will work without a loop:

public Map<Id, List<SObject>> mapBySpecifiedIdField(List<SObject> sObjectList, SObjectField idField) {
    Map<Object, List<SObject>> sObjectListBySpecifiedObjectMap
            = new MapExtractor(idField, new XAP_PRED_SObjectFieldHasNonNullValue(idField))
                    .extractFrom(sObjectList);
    return (Map<Id, List<SObject>>) JSON.deserialize(
            JSON.serialize(sObjectListBySpecifiedObjectMap), Map<Id, List<SObject>>.class
    );
}

While this could be useful in contexts where the Map needs to be passed to another function where the signature is important, it is worth noting:

  1. The serialize/deserialize process will break all object references. So, this will not be as useful as it might otherwise seem in a Trigger context (for example).

  2. If it is not necessary to pass the Map or keySet() onto another method, it may not be necessary to fix key Type, as get() will work as expected.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .