[SalesForce] Cannot deserialize JSON as abstract type

While writing a class to help me manage @future jobs for a project, I stumbled onto an error message I can find very little help on.

Cannot deserialize JSON as abstract type

I used an interface named FutureHandler, which instances extend in order to provide functionality based on their needs. The caller passes an instance of FutureHandler to FutureManager, who defers the call to the correct context and ensures that the Handle method is called.

Heres some sample code:

Future Manager

public class FutureManager {

    public static void ExecuteInFuture(FutureHandler handle) {
        if (Utility.CanRunFuture()) {
            Execute(JSON.serialize(handle), handle.Name()); 
        } else {
            Execute_sync(handle);
        }
    }

    @future
    private static void Execute(String jsonHandle, String typeName) {
        FutureHandler handle = (FutureHandler)JSON.deserialize(jsonHandle, Type.forName(typeName)); 
        // Name required in order to deserialize ('Cannot deserialize to abstract type')
        Execute_sync(handle);
    }

    private static void Execute_sync(FutureHandler handle) {
        handle.Handle(); 
    }

}

Future Handler

public interface FutureHandler {
    void Handle(); 
    String Name(); 
}

Sample Implementation

public class UpdateSomeRecords_future implements FutureHandler {

    List<Id> targetRecordIds { get; set; }

    public CreateClonedProducts_future(List<Id> targetRecordIds) {
        this.targetRecordIds = targetRecordIds; 
    }

    public void Handle() {
        update [SELECT Id FROM Account WHERE Id IN :targetRecordIds];
    }

    public String Name() {
        return 'UpdateSomeRecords_future';
    }

}

The above code works as expected- the implementation is called correctly. The message I can't seem to get around comes from a refactor attempt to remove the Name() method from the interface.

@future
private static void Execute(String jsonHandle) {
    FutureHandler handle = (FutureHandler)JSON.deserialize(jsonContainer, Type.forName('FutureHandler')); 
    Execute_sync(container);
}

The above snippet was an attempt to get the value as a instance of the interface, as opposed to the implementation, to avoid calling Name(), and simply the implementations. This attempt nets me the error:

Cannot deserialize JSON as abstract type

Which makes sense – I am trying to deserialize some object as an interface. I figured, well, lets try wrapping the interface in a concrete class, to try to avoid casting directly to that type. This let me to make a FutureContainer class, which holds a reference to the FutureHandler.

public class FutureContainer {

    FutureHandler handler { get; set; }

    public FutureContainer(FutureHandler handler) {
        this.handler = handler; 
    }

    public void Handle() {
        handler.Handle(); 
    }

}

This also fails, with the same error message. I assume its due to the variable being a interface, and when the JSON class tries to decode the variable, it encounters the same error.


I'm kind of at a loss on how to deserialize an interface type without using a identify function built into the interface.

My next idea is to switch the type of FutureHandler to a virtual class, allowing implementations to extend it, and giving a concrete target for the JSON class. I'm not a fan of this method, as I don't have a default behavior to modify, which feels against the spirit of using a virtual class.

What are other methods I can use to deserialize abstract objects?

Best Answer

I tried to get a solution for your problem, but I think the best one is yours : let the customer implement a getter with the name of its class, and pass it to your method as you first did. Because at the end of the day, no matter what you want, JSON absolutely needs the class name to deserialize a String into that class.

If you really want to make it simpler, make an abstract class with the ready-to-use method

public String Name(){
    return String.valueOf(this).split(':')[0];
}

and let the customer extend the class, focusing on the handle method...

EDIT

If you really really want to remove the name parameter, you might make an actual getter instead of the Name() method I wrote :

public abstract class AbstractFutureHandler{
    public String className{ get {
        return String.valueOf(this).split(':')[0];
    }}
}

and in FutureManager class :

@future
private static void Execute(String jsonHandle) {
    String typeName = (String) ( (Map<String, Object) JSON.deserializeUntyped(jsonHandle) ).get('className'); 
    FutureHandler handle = (FutureHandler)JSON.deserialize(jsonHandle, Type.forName(typeName)); 
    Execute_sync(handle);
} 

And finally

public class UpdateSomeRecords_future extends AbstractFutureHandler implements FutureHandler 
{ /* ... */ }

Kind of heavy, makes JSON deserialize twice but hey, Salesforce have servers, let's make them work ;)