When the new Custom API component was introduced it made a lot of people happy, but the initial reaction was short-lived when I assumed that objects need to be linked to an entity in Dataverse – which kind of makes it a single-level JSON schema. But what if we want to have nested objects and how do we make that happen. I brought this as a discussion item on the Power Platform LEVEL UP community on Discord. And soon awesome community members and MSFT team members chimed in on this issue and I came to know that Custom API supports “expando” entity. So first let’s see what is this entity.

What is “expando” entity?

It is an Entity type object that does not have a name and members can be dynamically added and removed at will. This entity is not an actual table in Dataverse but a dynamic object.

The “expando” entity is not currently supported if you are invoking Custom API from SDK. Hopefully it will be supported in the next release.

Next, let’s see how to configure a Custom API that supports this type of entity.

How to configure Custom API requests & responses with “expando” entity?

When you create a request parameter and select Type as Entity during that process do not populate anything in Entity Logical Name. This is what creates an expando entity type.

Similarly, when you create response property and if Type is Entity; do not populate anything in Entity Logical Name.

This is shown in the following diagram:

The newer version of Custom API Manager in XrmToolBox now supports expando entity. More details can be found on this GitHub issue.

How would JSON schema look like?

Once you have configured your Custom API with expando entity in request and response; let us see how the JSON schema for both of them would look like.

The important thing to remember is when using expando type you will have to provide that type during a Web API call as shown below:

Sample Request JSON Payload

{
    "Payload": {
        "@odata.type": "#Microsoft.Dynamics.CRM.expando",
        "SuperHero": {
            "@odata.type": "#Microsoft.Dynamics.CRM.expando",
            "name": "Batman",
            "Powers": {
                "@odata.type": "#Microsoft.Dynamics.CRM.expando",
                "strength": 90,
                "durability": 100
            },
            "Publisher": {
                "@odata.type": "#Microsoft.Dynamics.CRM.expando",
                "name": "DC",
                "Address": {
                    "@odata.type": "#Microsoft.Dynamics.CRM.expando",
                    "city": "LA"
                }
            }
        }
    }
}

Sample Response JSON Payload

{
    "@odata.context": "https://demo.crm.dynamics.com/api/data/v9.2/$metadata#expando/$entity",
    "@odata.type": "#Microsoft.Dynamics.CRM.expando",
    "ResponseType": "success",
    "ResponseMessage": {
        "@odata.type": "#Microsoft.Dynamics.CRM.expando",
        "SuperHeroDetails": "Batman has 100 durability",
        "PublisherDetails": "Batman is featured in DC comics"
    }
}

As you can observe we have not defined any of the objects inside the “Payload” attribute of the Request JSON and neither did we define the Response structure. Do remember when passing the request you have to provide "@odata.type": "#Microsoft.Dynamics.CRM.expando" to the dynamic object as highlighted in the code above.

Now, let’s look at the code to understand how the parsing happens.

Parsing the JSON in Plugin Code

Once we have configured our Custom API, it is time to look at how the plugin is written:

if (context.MessageName.Equals("powm_DemoDynamic")) {
  try {
    Entity payload = (Entity) context.InputParameters["Payload"];

    Entity sh = (Entity) payload["SuperHero"];
    Entity shPowers = (Entity) sh["Powers"];
    Entity shPublisher = (Entity) sh["Publisher"];
    Entity shPublisherAddress = (Entity) shPublisher["Address"];

    Entity dynamicResponse = new Entity();
    dynamicResponse["ResponseType"] = "success";

    Entity respMessage = new Entity();
    respMessage["SuperHeroDetails"] = $ "{sh["name "]} has {shPowers["durability "]} durability";
    respMessage["PublisherDetails"] = $ "{sh["name "]} is featured in {shPublisher["name "]} comics";

    dynamicResponse["ResponseMessage"] = respMessage;

    context.OutputParameters["ResponsePayload"] = dynamicResponse;

  } catch (Exception ex) {
    tracer.Trace("SuperHero: {0}", ex.ToString());
    throw new InvalidPluginExecutionException("An error occurred in SuperHeroTest.", ex);
  }
} else {
  tracer.Trace("SuperHero plug-in is not associated with the expected message or is not registered for the main operation.");
}

As you can see on line 3 we retrieve the Payload request parameter as an Entity. But this entity object is not specific to any table in Dataverse but is a dynamic object. All the nested objects under this Payload request parameter is also of type Entity (as can be seen from line 5 to 8).

Similarly, when we want to create an expando type response; we have to create an Entity object without any logical name and any nested objects will follow the same process. This is highlighted in the code from lines 10 to 17.

Next, let us look at the execution from Postman.

Executing the Custom API

For more content subscribe to my blogs and follow me on:

Don’t forget to subscribe to my Power Platform ProDev Newsletter

Become one of my sponsors on GitHub

8 comments

    1. You are not constructing your payload correctly. Please check one more time or your Custom API configuration is not matching with what you are providing to the API

      Like

  1. Hi Danish,

    Thank you for the blog on ‘expando’ very informative, can these be used in the Cloud Flows?
    I’m trying to call the Custom API from a cloud flow that I’m creating and it fails with the following error message

    “URL was not parsed due to an ODataUnrecognizedPathException. Resource not found for the segment provided in the URL.”

    Thanks

    Like

      1. I managed to call a Custom API with an expando input from a cloud flow.

        – Requires prebuilding the JSON (expando entity as JSON string) payload before you can call the Custom API.

        My view of JSON Payload is,
        – Works well for a simple JSON object where you do not have too much depth in the JSON object complexity.

        – Where you have a complex JSON, you will end up with a lot of string handling on a predefined JSON, so I did not particularly feel it was worth the effort manipulating string with complex JSON in a Cloud Flow, but it works well with Plugins, AZ Functions etc.

        Do let me know if you have a more straightforward approach.

        Like

Leave a comment