Mule 4: Replace recurring node value in original payload - mule

I have a recurring node/array in the JSON payload. I need to use value of the wonumber to call another service and the value from this service response has to be used to replace a code value on original message.
{
"element1": "text1",
"element2": "text2",
"element3": "text3",
"InvoiceItemDTO": [
{
"code": "",
"itemNo": "1",
"wonumber": 123
},
{
"code": "",
"itemNo": "2",
"wonumber": 456
}
]
}
The service response will give value for code field, which has to be copied back on original payload code value.
I have applied for each loop on the recurring node to make the call to service for each block in ItemDTO. But I am concerned about putting service response value field back on the original payload at the same index for which the service call was made.
If service call returns code value as abc1, def2 for above payload, then expected final payload is:
{
"element1": "text1",
"element2": "text2",
"element3": "text3",
"InvoiceItemDTO": [
{
"code": "abc1",
"itemNo": "1",
"wonumber": 123
},
{
"code": "def2",
"itemNo": "2",
"wonumber": 456
}
]
}
Any recommendations on how this can be achieved?

the for-each scope does not change the payload. If you want to retain some information gathered during execution of for-each you will need to store that data in a variable. As per the official doc
Every execution of the For Each scope starts with the variables and values from the previous execution of the block. New variables or modifications to existing variables that take place when processing one element are visible during the processing of another element. These changes to variables continue to be available outside the For Each scope.
For your use case you can do the following (Just a pseudo code)
Collect all unique wonumber in an array and store it in a variable
<set-variable variableName="wonumbers" value="#[payload.InvoiceItemDTO.wonumber distinctBy $]"/>
Loop through this variable in the for-each scope
After collecting the code for the wonumber store that as a map in a variable so that you can access it later.
<foreach collection="#[vars.wonumbers]">
<set-variable variableName="currentWonumber" value="#[payload]" />
<do-your-thing.... />
<set-variable variableName="wonumbersToCodeMapping" value="#[(vars.wonumbersToCodeMapping default {}) ++ (vars.currentWonumber): payload.code //however you are getting the code]"/>
</foreach>
Using the wonumbersToCodeMapping you can update the payload using update operator
%dw 2.0
output json
---
payload update {
case .InvoiceItemDTO ->
$ map ((item) -> item update {
case .code -> vars.wonumbersToCodeMapping[item.wonumber as String]
})
}

Related

Dataweave - how to accumulate json response from http request connector in for each component (Mule 4)

I consume a HTTP request and I need to save and accumulate the JSON response with out any transformation in a variable, I do that but I dont know why the accumulate it correctly, could you please tell me how can I solve that problem.
Json Response By Iteration:
Iteration 1:
{
"orderId": "11111",
"status": "false",
"receivedAt": "2022-07-28T22:45:12.175Z",
"createdAt": "2022-07-28T22:45:12.175Z",
}
Iteration 2:
{
"orderId": "22222",
"status": "false",
"receivedAt": "2022-07-28T22:45:27.907Z",
"createdAt": "2022-07-28T22:45:27.907Z"
}
Dataweave: (csv Payload is the name of the variable where the values accumulated)
%dw 2.0
output application/json
---
if ( vars.counter == 1)
( payload )
else
( vars.csvPayload ) ++ payload
Variable Result:
{
"orderId": "11111",
"status": "false",
"receivedAt": "2022-07-28T22:45:12.175Z",
"createdAt": "2022-07-28T22:45:12.175Z",
"orderId": "22222",
"status": "false",
"receivedAt": "2022-07-28T22:45:27.907Z",
"createdAt": "2022-07-28T22:45:27.907Z"
}
Variable Expected:
[
{
"orderId": "11111",
"status": "false",
"receivedAt": "2022-07-28T22:45:12.175Z",
"createdAt": "2022-07-28T22:45:12.175Z",
},
{"orderId": "22222",
"status": "false",
"receivedAt": "2022-07-28T22:45:27.907Z",
"createdAt": "2022-07-28T22:45:27.907Z"
}
]
NOTE: I don't know why the JSON responses of each iteration are joined in the same object and not as a different object in an array.
The incorrect output is easy. You are concatenating an object to another object. In that case the operator ++ "extracts all the key-values pairs from each object, then combines them together into one result object." according to the documentation. The parenthesis are totally unneeded.
Setting the output to JSON in each iteration -I'm assuming this is inside a foreach- is inefficient since it requires formatting the output to JSON in each iteration only to parse it again in the following one. I recommend to use application/java -which doesn't require parsing/formatting- inside a loop and after the loop convert the entire output to JSON in one go.
You should use an array to hold the values, so assign an empty array ([]) to the variable before the foreach loop to initialize it. Then the counter is unneeded since you can just add elements to the array:
%dw 2.0
output application/java
---
vars.allOrders ++ payload
Then after the foreach just transform the array to JSON:
%dw 2.0
output application/json
---
vars.allOrders
Your requirement is a very common scenario in integrations.
Assuming you are using HTTP Requester inside a for-each loop and initialized the variable to hold the final Payload (lets say var finalPayload = []) before the loop, you have to keep on updating the same variable (append the new data in the same array variable like below) with the response payload received from HTTP Requester for each iteration.
var finalPayload << payload
Ultimately check the finalPayload outside for-each loop, that will give you your desired result.

Filter Payload with Dataweave for a Set Variable component

For the Mulesoft 4.2 Set Variable component, I want to assign a simple String value pulled from a single specific record from an incoming JSON payload.
In my case, taking just the value from the 'country' field from below example:
[
{
"salesID": "4404Bob Builder210032011-02-18T15:52:21+0.00",
"id": "4404",
"firstName": "Bob",
"lastName": "Builder",
"address": "181 Construction Road, Adelaide, NSW",
"postal": "21003",
"country": "New Zealand",
"creationDate": "2011-02-18T15:52:21+0.00",
"accountType": "personal",
"miles": 17469
}
]
A non-dynamic way to do this is like:
payload[0].country
I believe the best way to do this is with the filter function. The below option gives me the entire object, but I just want the country field.
payload filter ($.id == "4404")
Map function seems to be overkill for this since I only want the value, itself. I must be missing the syntax to get at the country field.
I did some more investigating, and this solution got me close enough to what I wanted:
https://stackoverflow.com/a/43488566/11995149
For my code example using filter, I had to surround the whole expression in parenthesis, and then I can access the field with a dot reference,
but below code gives String value as a single record within an array:
(payload filter ($.id == "4404")).country
[
"New Zealand"
]
In my case, I know that just one result will be returned from the filtered payload, so I could get just the String value with:
(payload filter ($.id == "4404"))[0].country
enter image description here
Can you try any of below options:
1) (payload groupBy ((item, index) -> item.id))["4404"][0].country
OR
2) (payload map ((item, index) -> if(item.id == "4404") item.country else ""))[0]
Thanks,
Ashish

Use each element of var to make a http request in Mule

I have a collection of matches saved to a var. Each match has a gameId. I want to use this gameId to make an API call.
I set up a for each scope using matches with a counter var.
<set-variable value="#[payload]" doc:name="Set Matches" doc:id="bb1093b0-3dd9-4515-8f42-dd496f00477b" variableName="matches"/>
<set-variable value="#[[]]" doc:name="Deaths" doc:id="a7341e19-07f8-418d-b39a-5bc415e75fba" variableName="deaths"/>
<foreach doc:name="For each match" doc:id="506fa944-d46f-4684-9c75-73088265ca80" collection="vars.matches" rootMessageVariableName="matches">
<http:request method="GET" doc:name="GetTimeline" doc:id="63708489-4d10-4e79-b5aa-c8fc723e80c6" config-ref="HTTP_Request_configuration" path="/match/v4/timelines/by-match/{matchId}">
<http:uri-params ><![CDATA[#[output application/java
---
{
"matchId" : vars.matches[vars.counter + 1].gameId
}]]]></http:uri-params>
</http:request>
<ee:transform doc:name="MapDeaths" doc:id="a6e22968-2229-4574-8cae-44225013b9cf">
<ee:message>
<ee:set-payload><![CDATA[%dw 2.0
output application/json
---
{
frames: payload.frames map ( frame , indexOfFrame ) -> {
events: frame.events map ( event , indexOfEvent ) -> vars.deaths + event
}
}]]></ee:set-payload>
</ee:message>
</ee:transform>
</foreach>
Yet matchId from "matchId" : vars.matches[vars.counter + 1].gameId always evalutes to null. What am I missing there?
Edit:
I'm using Mule 4.2 and the json structure of matches looks like this:
[
{
"matches": [
{
"key": "val",
"key": val,
...
},
{
"key": "val",
"key": val,
...
},
...
Set {matchId} as Query string parameter and define request a Target variable, use that in dataweave
The problem seems to be that your payload and the expression you are using doesn't match (pun not intended). The payload is an array that contains objects, with a matches attribute. The expression apparently assumes that the payload contains a matches attribute, which contains a gameId attribute. That is the reason it returns null.
Also the intention is not clear, given that you didn't show were gameId is in your example.
Let's try with a more detailed example.
For the input:
[
{
"matches": [
{
"keyA": "val1",
"keyB": "val2"
},
{
"keyC": "val3",
"keyD": "val4"
}
]
}
]
The expression payload.matches.keyA returns null.
You could do payload[0].matches[0].keyA to obtain "val1".
This assumes there data is from the first element of the array and you want to use only one key from one of the matches. Of course if you need to iterate on the different arrays levels you will need to use map or group, but it is a different use case.

Looking for a particular data in JSON hashmap

I need to look for AccountNo field value in different json payloads. AccountNo can exist at any level in json payload. I need to check if AccountNo exists and then print value in logger.
I am using below enricher but I want to iterate through the Hashmap in mule to check if AccountNo key exists anywhere and then get the value.
Also please suggest if any other way to parse json itself.
In xpath "//AccountNo" will look for AccountNo in whole xml document. I am after something similar.
<enricher target="#[flowVars.myJsonMap]" doc:name="Message Enricher">
<json:json-to-object-transformer returnClass="java.util.HashMap" doc:name="JSON to Object"/>
</enricher>
<logger message="#[flowVars.myJsonMap.employees[0].AccountNo]" level="INFO" doc:name="Logger"/> </flow>
Please find below example json payloads
{
"Account": {
"AccountName": "John",
"AccountNo": "4234324"
}
}
{
"Order": {
"OrderId": "34234242",
"ServiceOrder": [
{
"AccountNo": "231232",
"ServiceOrderId": "54654698787"
},
{
"AccountNo": "231232",
"ServiceOrderId": "78979879797"
}
]
}
Since its a hashmap
you can use yourmap.get(keyvalue) to get value.
<logger message="#[flowVars.myJsonMap.yourkey?]" level="INFO" doc:name="Logger"/>
From your question, I assume the following:
Your payload contains nested Maps and Lists along with other Objects
AccountNo may exists at any level of your payload
You can use a Groovy transformer to recursively parse your payload and collect all existing AccountNo in a List:
//recursively parse any Map or List in myObject
//and store AccountNo objects accountNoList
def parseObject(myObject, accountNoList) {
if (myObject instanceof java.util.Map) {
//if Map, check for AccountNo field
if(myObject.AccountNo != null){
accountNoList.add(myObject.AccountNo)
}
//seek for more AccountNo in Map
for(e in myObject) {
parseObject(e.value, accountNoList)
}
} else if (myObject instanceof java.lang.Iterable) {
//if Iterable, parse each values
for (value in myObject) {
parseObject(value, accountNoList)
}
}
}
myJsonPayload = message.getInvocationProperty('myJsonPayload')
myAccountNoList = [] //init an empty list which will be filled
parseObject(myJsonPayload, myAccountNoList)
return myAccountNoList
Then simply use a Logger.
if you are not dealing with large set of json data, just convert the json to XMl using JSONToXMLTransformer and then, use XPATH to look for the data anywhere in the document. This way, you could avoid writing any additional code to parse the json file.

Dataweave script works for preview, fails in actual use

Using Studio 6.1.0
Runtime 3.8.2ee
I've written a dataweave script that works fine when I preview the results in the Transform Message window, but when I actually run my application I get an error which suggests a problem with the dataweave script.
This is the error:
Invalid input "[\n \t\t({\n \t\t\teffectiveTerm:
template.effectiveTerms.effectiveFromTerm,\n \t\t\tattributeCode: "VARU"\n \t\t} as :object
{ class: "edu.mit.courseleaf.domain.backfill.OldAttribute" })\n \t\twhen
template.units.variableUnits == "Y",", expected flatten, typeOf, using, unzip, capitalize,
avg, min, lower, upper, max, singularize, not, dasherize, ordinalize, camelize, trim, sizeOf,
value, pluralize, sum or underscore (line 16, column 24)
(java.util.concurrent.ExecutionException). Message payload is of type: HashMap
Flow with dataweave script, input for preview, json for actual execution are included below. A couple of comments:
The part of the dataweave script referenced in the error is for the attributes property. The goal here is to set the property to a list of OldAttribute objects. The contents of the list depend on two separate element in the source data: the presence of a subjectTemplate.Units.variableUnits == "Y" and a subset of values in the attributes element.
I know we could go directly from JSON to POJO. In the full application, we're doing additional processing on the intermediary HashMap so we want to retain the two step conversion.
edu.mit.courseleaf.domain.backfill.OldAttribute and edu.mit.courseleaf.domain.backfill.SourceSubject are POJOs with default constructors and the proper getters and setters for the properties we're using.
Flow with dataweave script:
<flow name="temp-json-to-map-to-pojo-example-for-so">
<json:json-to-object-transformer returnClass="java.util.HashMap" doc:name="JSON to Object"/>
<set-payload value="#[payload]" mimeType="*/*" doc:name="Set Payload"/>
<dw:transform-message doc:name="Transform Message">
<dw:input-payload doc:sample="sample_data/map.dwl"/>
<dw:set-payload><![CDATA[%dw 1.0
%input payload application/java
%output application/java
%function passThroughAttributes(attributeList) attributeList - "RPT" - "SWE" - "FSEM"
---
using (template = payload.subjectTemplate
, attributes = payload.subjectTemplate.attributes
)
{
subjectCode:template.mainSubjectCode,
subjectNumber:template.mainSubjectNumber,
attributes: flatten [
({
effectiveTerm: template.effectiveTerms.effectiveFromTerm,
attributeCode: "VARU"
} as :object { class: "edu.mit.courseleaf.domain.backfill.OldAttribute" })
when template.units.variableUnits == "Y",
passThroughAttributes(attributes) map (
{
attributeCode: $,
effectiveTerm: template.effectiveTerms.effectiveFromTerm
}
) as :object { class: "edu.mit.courseleaf.domain.backfill.OldAttribute" }
]
}as :object{class : "edu.mit.courseleaf.domain.backfill.SourceSubject"
]]></dw:set-payload>
</dw:transform-message>
</flow>
Sample input for preview:
{
subjectTemplate: {
subjectTemplateId: "43C9C99A88B76AFBE053282701126517",
mainSubjectCode: "HAA",
mainSubjectNumber: "T101",
subjectLevel: "U",
effectiveTerms: {
effectiveFromTerm:"2017SP",
effectiveThruTerm: "999999"
},
units: {
variableUnits: "Y",
lectureUnits: 9,
labUnits: 3,
prepUnits: 0,
totalCreditLimitUnits: 0
},
attributes: ["RPT", "XLIS"]
}
}
Screenshot of preview showing attributes property correctly populated
Actual JSON input for flow
{
"subjectTemplate": {
"subjectTemplateId": "43C9C99A88B76AFBE053282701126517",
"mainSubjectCode": "HAA",
"mainSubjectNumber": "T101",
"subjectLevel": "U",
"effectiveTerms": {
"effectiveFromTerm": "2017SP",
"effectiveThruTerm": "999999"
},
"units": {
"variableUnits": "Y",
"lectureUnits": 9,
"labUnits": 3,
"prepUnits": 0,
"totalCreditLimitUnits": 0
},
"attributes": ["RPT", "XLIS"]
}
}
Please try with group by in dataweave for grouping all the same category product in one array then do further clean as per your requirement. You can refer here for the details.