2
Map<Id, Segment__c> noLockedRecordsMap = 
    (Map<Id, Segment__c>)new LockedRecordHandler().removeLockedRecords(newMap);

The above compiles fine. Get this error at runtime:

Invalid conversion from runtime type Map to Map Class

Here's the method it's calling:

public Map<Id, SObject> removeLockedRecords(Map<Id, SObject> newMap) {
     Map<Id, SObject> returnMap = new Map<Id, SObject>();
     for (SObject s : newMap.values()) {
         if (!(Boolean)s.get('Locked__c')) {
             returnMap.put((Id)s.get('Id'), s);
        }
     }
     return returnMap;
}

Anyone have experience with this?

2
  • Why are you trying to cast to a specific type? The code you have would work fine with generic SObject.
    – Adrian Larson
    Mar 22, 2019 at 19:44
  • Trying to keep the LockedRecordHandler generic so that any custom object can use it. The code in the trigger handler uses a map with the custom object in it. I would like to be able to cast it and not have to modify all of the methods within the trigger handler that use Map<Id, Segment__c>
    – mackmama
    Mar 22, 2019 at 19:50

3 Answers 3

4

If you open up the details of the error message, it probably says Invalid conversion from runtime type Map<Id, SObject> to Map<Id, Segment__c> Class. You can't convert Maps with a different type specialization, even if the changed types are convertible. In the particular case of converting a Map<Id, sObject> to a Map<Id, Specific_SObject__c> where the key is the Id of the sObject value, you could do this by taking advantage of the List constructor for Map:

Map<Id, Segment__c> noLockedRecordsMap = new Map<Id, Segment__c>(
           (List<Segment__c>)new LockedRecordHandler().removeLockedRecords(newMap).values()
       );

In all other cases, you should loop through the map and put its key-value pairs in a new map:

Map<Id, Segment__c> noLockedRecordsMap = new Map<Id, Segment__c>();
Map<Id, SObject> lockedRecordsSObject = new LockedRecordHandler().removeLockedRecords(newMap);
for(Id key : lockedRecordsSObject.keySet())
{
    noLockedRecordsMap.put(key, (Segment__c)lockedRecordsSObject.get(key));
}

This could probably also be done by doing round-trip JSON, but the only advantage to that is saving lines of code, most likely it won't be faster. Hopefully in the future Apex will support methods with generic arguments so that it can return a map of the correct type and this won't be necessary.

2
  • I think this worked. I'll test and get back to the thread ASAP. Thanks!
    – mackmama
    Mar 22, 2019 at 23:20
  • Creating the Map with the List constructor worked. I was able to cast the List from SObject to Segment__c. It would be nice to be able to cast the Map, but this is a slick workaround. Thank!
    – mackmama
    Mar 26, 2019 at 18:09
2

You should not try to put a generic type in to a concrete type. This can cause bugs. The ideal situation is to create a copy so you retain the concrete type:

public Map<Id, SObject> removeLockedRecords(Map<Id, SObject> newMap) {
     // Make a copy //
     Map<Id, SObject> returnMap = newMap.clone();
     // Clear out existing key/values in copy (not original) //
     returnMap.clear();
     for (SObject s : newMap.values()) {
         if (!(Boolean)s.get('Locked__c')) {
             returnMap.put((Id)s.get('Id'), s);
        }
     }
     return returnMap;
}

By using clone, you preserve the original type that was passed in, and then your cast won't fail in the end.

6
  • I was about to add this to my answer. No longer necessary, I guess.
    – Adrian Larson
    Mar 22, 2019 at 23:23
  • @AdrianLarson My apologies, I was in here to add a different perspective. TBH, you can have it if you want it. 😁
    – sfdcfox
    Mar 22, 2019 at 23:30
  • Nah it's not proprietary. I was on mobile anyway.
    – Adrian Larson
    Mar 23, 2019 at 2:11
  • Is there a way to avoid clone/clear approach to retain the concrete type? Oct 8, 2019 at 14:00
  • 1
    @OlehBerehovskyi You can choose to pass in the type, such as public Map<Id, sObject> removeLockedRecords(Map<Id, sObject> newMap, Type mapType) { Map<Id, sObject> returnMap = (Map<Id, sObject>)mapType.newInstance(); In Java, we would have public Map<Id, SObject> removeLockedRecords(Map<Id, SObject> newMap) { Map<Id, SObject> returnMap = (Map<Id, sObject>)newMap.getClass().newInstance(); ... but we don't have getClass() available to us. clone/clear basically emulates getClass().newInstance() without too much fuss.
    – sfdcfox
    Oct 8, 2019 at 14:41
1

Just change your signature to accept List<SObject> instead. You are only using the Map.values() result anyway.

public Map<Id, SObject> myMethod(List<SObject> records)
{
    Map<Id, SObject> unlocked = new Map<Id, SObject>();
    for (SObject record : records)
    {
        // same logic here
    }
    return unlocked;
}
3
  • The issue is that it returns an SObject map which needs to be cast to a specificCustomObject map. Passing in the specificCustomObject map to the method works fine.
    – mackmama
    Mar 22, 2019 at 20:34
  • Why do you need the output to be specifically cast? You are not providing enough context information to allow us to provide well grounded advice.
    – Adrian Larson
    Mar 22, 2019 at 21:47
  • The returned map is a parameter passed into other methods. If I don't convert it will break existing calls. The answer as to "why" I want to do this, is that the method can be called by many different trigger handlers (different custom objects). I can remove locked records from all of them with the same code.
    – mackmama
    Mar 22, 2019 at 23:19

You must log in to answer this question.

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