javaspring-bootspring-integrationspring-expression-language

spring integration spel expression to extract fields from message payload


Not able to get my head over SPEL for Message payloads. I want to extract data from certain fields of my message payload which is essentially the following JSON converted to Map<String, Object> and passed to a @Transformer

{
  "expand":"renderedFields,names,schema,transitions,operations,editmeta,changelog,versionedRepresentations",
  "id":"14730",
  "self":"https://jira.foo.com/rest/api/2/issue/14730",
  "key":"SDP-145",
  "fields":{
    "issuetype":{
      "self":"https://jira.foo.com/rest/api/2/issuetype/10200",
      "id":"10200",
      "description":"gh.issue.epic.desc",
      "iconUrl":"https://jira.foo.com/ghanghor/viewkaka?size=xsmall&kakaId=10501&kakaType=issuetype",
      "name":"Epic",
      "subtask":false,
      "kakaId":10501
    },
    "priority":{
      "self":"https://jira.foo.com/rest/api/2/priority/3",
      "iconUrl":"https://jira.foo.com/images/icons/priorities/major.svg",
      "name":"Major",
      "id":"3"
    },
    "labels":[
      "Lizzy",
      "kanban",
      "rughani"
    ],
    "updated":"2021-01-21T10:33:38.000+0000",
    "status":{
      "self":"https://jira.foo.com/rest/api/2/status/1",
      "description":"The issue is open and ready for the assignee to start work on it.",
      "iconUrl":"https://jira.foo.com/images/icons/statuses/open.png",
      "name":"Open",
      "id":"1",
      "statusCategory":{
        "self":"https://jira.foo.com/rest/api/2/statuscategory/2",
        "id":2,
        "key":"new",
        "colorName":"blue-gray",
        "name":"To Do"
      }
    },
    "summary":"new epic for Tazzy",
    "creator":{
      "self":"https://jira.foo.com/rest/api/2/user?username=skadmin",
      "name":"skadmin",
      "key":"skadmin",
      "emailAddress":"Lizzy.t@foo.com",
      "displayName":"Lizzy Rughani",
      "active":true,
      "timeZone":"Asia/Kolkata"
    },
    "subtasks":[

    ],
  }
}

I'm interested in three nested values here which I'm trying to fetch via following expressions

issueDataMap = {LinkedHashMap@4867}  size = 3
 "name" -> "#payload['fields']['summary']"
 "description" -> "#payload['description']"
 "text3" -> "#payload['key']"

I get this error when the expression is applied

org.springframework.expression.spel.SpelEvaluationException: EL1012E: Cannot index into a null value

Here's how I get the payload in the as argument to my transformer

@Transformer
public Map<String, Object> generateCardData(Map<String, Object> payload,
                                            @Header("X-UPSTREAM-WEBHOOK-SOURCE") String projectId) {

followed by

StandardEvaluationContext evaluationContext = evaluationContextFactory.getObject();

and here's how I evaluate it

new SpelExpressionParser().parseExpression(issueDataMap.get(key)).getValue(
                            evaluationContext, payload, String.class)));

I have the app annotated with @SpringBootApplication and @EnableInegrationand I autowire an instance of IntegrationEvaluationContextFactoryBean to get the StandardEvaluationContext

I also tried the variant

issueDataMap = {LinkedHashMap@4867}  size = 3
 "name" -> "payload['fields']['summary']"
 "description" -> "payload['description']"
 "text3" -> "payload['key']"

but then I get

EL1008E: Property or field 'payload' cannot be found on object of type 'java.util.LinkedHashMap' - maybe not public or not valid?

Solution

  • First of all it is not clear why would one use SpEL in the code manually, when you have full access to the object. Plus you should keep in mind that create StandardEvaluationContext, parse an expression and evaluate it on every single call is kinda an overhead by performance. You probably just need to change your generateCardData() signature to accept a result of the expression instead of the whole map. See @Payload.expression attribute.

    Anyway this is not what you would like to hear for your problem. And it is here: getValue(evaluationContext, payload, String.class))). The root evaluation object is your payload - a Map. So, what you just need to assume in your expression definition that you get access to that root object. Therefore expressions must be like this: fields.summary, description, key.

    You typically see in the docs and samples a payload (or header) as a first token in the expression. That is just because Spring Integration uses a Message as a root object for expressions to evaluate.

    Now in regards to performance. Even if your logic to select an expression by some key at runtime (issueDataMap.get(key)), you still could parse it only once.