6

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?
3

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.

| improve this answer | |
2

Ok... I didn't test integrating with the Flow... but as per InvocableMethod Annotation documentation that can be found here, it states:

If the return type is not Null, the data type returned by the method must be one of the following:

A list of a primitive data type or a list of lists of a primitive data type – the generic Object type is not supported.

A list of an sObject type or a list of lists of an sObject type – the generic sObject type is not supported.

A list of a user-defined type, containing variables of the supported types and with the InvocableVariable annotation. Create a custom global or public Apex class to implement your data type, and make sure your class contains at least one member variable with the invocable variable annotation.

I have changed your code slightly to add the @InvocableVariable to the variable as below:

public class TestYesNo {
    @InvocableVariable 
    public String answer;
    @InvocableVariable 
    public Boolean forced;
    @InvocableVariable 
    public String image;
}

I was able to then save the TestInvocableCallout class without any problem.

If you take a quick look at the InvocableVariable Annotation documentation here they use a different approach where they create an internal class with the member variables tagged as @InvocableVariable and use it as the List return type.

Hope that helps and let me know if you were able to integrate with the Flow :).

| improve this answer | |
  • 1
    Hmmmm...so now I'm confused what Salesforce was even trying to demonstrate, then, with their Car class in the Release Notes. What exactly is an "Apex-Defined Data Type" supposed to be that an "Invocable Variable" is not? – k.. May 29 '19 at 15:34

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.