[SalesForce] How to use an Apex-Defined Data Type to pass an HTTP response to a Flow

I was playing with the code in the release notes under Manipulate Complex Internet Data in Flows Without Code Using Apex-Defined Data Types, hoping I could develop an example for showing colleagues what's new, and got stuck.

The documentation says:

With the new Apex-defined resources in flows, a flow no longer needs to use Apex code to process complex JSON returned from web calls. A developer defines an Apex class to serve as a pattern for automatic conversion from web to a flow, and then full manipulation of the resulting objects can be carried out in a flow using declarative approaches and no additional code.

So I thought, "All right … I suppose this public class Car example they gave must be some sort of skeleton structure for representing the response from a web call. I'll get it actually working once I get a skeleton compiling."

I figured I'd test against https://yesno.wtf/api, whose JSON responses look like this:

{
    "answer":"yes", 
    "forced":false,
    "image":"https://yesno.wtf/assets/yes/15-3d723ea13af91839a671d4791fc53dcc.gif"
}

So I wrote an Apex class called TestYesNo meant to be along the lines of Car:

public class TestYesNo {
    @AuraEnabled public String answer {get;set;}
    @AuraEnabled public Boolean forced {get;set;}
    @AuraEnabled public String image {get;set;}
}

And then I thought, "All right, they didn't give a lot of help, and talked about using one of these more like an input parameter to an invocable method than as a return value from one like they implied these were for, but let's ignore that for a moment and just see if I can even get things to compile" and wrote this:

public class TestInvocableCallout {
    @InvocableMethod(label='Get YesNo' description='Returns a response from the public API YesNo.wtf')
    public static List<TestYesNo> getYesNo() {
        List<TestYesNo> yesNo = new List<TestYesNo>();
        Http http = new Http();
        HttpRequest request = new HttpRequest();
        request.setEndpoint('https://yesno.wtf/api');
        request.setMethod('GET');
        HttpResponse response = http.send(request);
        // If the request is successful, parse the JSON response.
        if (response.getStatusCode() == 200) {
            // Something cool will likely go here to populate yesNo
        }
        return yesNo;
    }
}

Unfortunately, that resulted in a compilation error: InvocableMethod methods do not support return type of List<TestYesNo>.

So although I vaguely remembered InvocableMethods needing to return lists, I decided to try returning a simple TestYesNo instead. No luck: InvocableMethod methods do not support return type of TestYesNo

However, the class definitely compiles without error when I return a List<String> from the InvocableMethod, so I don't think I made a glaring typo.

The example in the release notes isn't terribly helpful, because it doesn't actually include any sort of method signature to imitate (also, it's confusing because the comments imply it uses a property of Car as input, rather than returning a Car as output — to be honest, it seems that none of the release-notes documentation for this new feature helps elaborate upon the advertised use case of returning data from a callout to a flow):

global class FindCars {
    @InvocableMethod(label='Find Cars')
    //Perform logic to return car results based on the model. 
    Return results;
}
  • Has anyone here gotten a chance to see proper documentation about, as Salesforce promises, "processing complex JSON returned from web calls to a flow," and would therefore be able to get me started?
  • Or is this just going to remain shrouded in secrecy until Trailhead catches up? 🙁
    • (If the latter, and if anyone bumps into a dev from Salesforce at TrailheaDX, maybe hint-hint them for me … and have a good time!)

Additional clarification of question:

  • It's all very nice that when you create a new variable called myVar1 in Salesforce Flow and specify its data type, in addition to things like Text, Number, and Record, you can specify TestYesNo as the variable's data type.
  • But that still leaves open the question: how do you get Apex to write any data into to myVar1, considering there isn't an obvious way to make TestYesNo valid as a return-type for invocable Apex?

Best Answer

Thanks so much, @rodrigo -- I still needed additional help in another forum before I could make use of your answer; I'd like to "explain it like I'm five" and add some additional details for others who come to this post as confused as me and can't quite get going with that answer alone.

Here are a few important things to know about Invocable Apex and Apex-Defined Data Types.

  1. The {get; set;} from the Car class example in the release notes isn't necessary, and fights with @InvocableVariable. Try leaving it off.
  2. Only standalone "outer" Apex classes seem to be recognized by the Flow Builder as allowable Apex-Defined Data Types -- not "inner" classes.
  3. Although Salesforce's official documentation about using @InvocableVariable adds this annotation to the attributes of an "inner class" hosted within an outer class possessing an @InvocableMethod-annotated method, this doesn't have to be the case.
    • Any "invocable" method is perfectly capable of returning a List<SomeStandaloneApexClass> as long as SomeStandaloneApexClass has at least one @InvocableVariable-annotated attribute.

This code allowed me to create a Flow Variable myVar1 whose data type was TestYesNoTwo. When I created a Flow Action that summoned getYesNo(), myVar1 was readily available for writing the action's Output to, and was easy to display on a Screen.

Screenshot: https://i.imgur.com/r9hVUBz.png

public class TestYesNoTwo {
    @AuraEnabled @InvocableVariable public String answer;
    @AuraEnabled @InvocableVariable public Boolean forced;
    @AuraEnabled @InvocableVariable public String image;

    public TestYesNoTwo() {}

    public TestYesNoTwo(String ans, Boolean frcd, String img) {
        answer = ans;
        forced = frcd;
        image = img;
    }
}
public class TestInvocableCallout {
    @InvocableMethod(label='Get YesNo' description='Returns a response from the public API YesNo.wtf')
    public static List<TestYesNoTwo> getYesNo() {
        List<TestYesNoTwo> yesNo = new List<TestYesNoTwo>();
        yesNo.add(new TestYesNoTwo('yes',False,'https://yesno.wtf/assets/yes/5-64c2804cc48057b94fd0b3eaf323d92c.gif'));
        return yesNo;
    }
}

Full tutorial for "admins and the devs who love them" here, if it helps people with a bit more context.

Related Topic