4

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?

  • 1
    +1 for a nice question - Finally a real life example of an interface that is not making a dog bark – Eric Sep 28 '17 at 23:48
3

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 ;)

  • Thats a pretty interesting way to identify a implementation- I'll have to do some testing based around the this value. – battery.cord Sep 28 '17 at 19:31
  • I really like using the getter to include a value in the json to identify the type of the handler. Since this will be in a future context, Im less concerned about multiple serialize/deserialize calls. I just wish there was a better way to get the name of a instance of a class at runtime, but that seems to be an overall apex limitation. – battery.cord Sep 29 '17 at 13:07
  • Well it's not a very nice way but it's a way... Note that you'll have to deal with null values in the getter, as I read in salesforce.stackexchange.com/questions/3385/… – Emmanuel BRUNO Sep 29 '17 at 14:07
  • I ended up not changing my implementation. While this answer did provide a method to avoid hard-coding a name method, it relied on a method which I considered to brittle to use in future facing code. The idea of using an abstract class, which implements an interface, and then only provides a single virtual method to override is a good idea, but I didn't like that it was providing the same information, while taking up more classes and headspace, and requiring an additional null check to prevent a default from being passed along. – battery.cord Oct 2 '17 at 20:15
  • That being said, its clear you put a lot of effort into this answer, and it definitely helped me approach my problem from other angles. – battery.cord Oct 2 '17 at 20:16
1

If you want to deserialize into an interface, you still have to pass in a concrete type (the actual implementation). In this case, the following should work just fine:

FutureManager.execute('{...}', UpdateSomeRecords_future.class);
//                             ^ Concrete implementation type
  • 1
    He doesn't know the class before runtime, so he won't harcode it... – Emmanuel BRUNO Sep 28 '17 at 19:13
  • More or less, this is what my original implementation does. The interface's Name() method is used to require the value, then Type.forName(handle.Name()) is used to generate the type to cast the value too. I'm looking for a way to exclude this information, but judging from the lack of responses, information available on the internet, and the missing type information from the generated json, I'm starting to think this isn't possible. – battery.cord Sep 28 '17 at 19:29

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.