Reading json Payload as part of a CSV file in dataweave - json

I have a web service which exports a CSV file which has one of the columns as a JSON payload. After the execution of the web service, I store the values in a local variable for transformation. Every time when I read the values from that column I am missing the values and only "}" is returned. Not sure why this is happening. I want to preserve the JSON payload as is and persist to a file after some processing. Please advise
I am using the code below to get the value of the attribute column and it always returns a "}". The rest of the contents are ignored
CSV Fragment
-------------
id,name,attributes
1,name1,{"Choice Number":"0","Campaign Run Id":"234"}
2,name2,{"Choice Number":"1","Campaign Run Id":"235"}
3,name3,{"Choice Number":"2","Campaign Run Id":"236"}
4,name4,{"Choice Number":"3","Campaign Run Id":"236"}
Code
----
%dw 1.0
%output application/java
---
flowVars.activityData map ((actData) -> {
"playerId": actData.id,
"name": actData.name,
"attributes": actData.attributes
})
I was expecting that the full JSON payload from the attributes column will be returned and that is not the case. One thing that I noticed here is that there is no escaping of characters in the JSON payload in the input. But I don't have any control on that as well. How do I extract the information from the attribute column in this case
Since I cannot share the whole project, created a sample project and using the inputs from #machaval with http object receiving the csv file. Marked the mimetype as text/csv and sending the payload
<?xml version="1.0" encoding="UTF-8"?>
<mule xmlns:http="http://www.mulesoft.org/schema/mule/http" xmlns:tracking="http://www.mulesoft.org/schema/mule/ee/tracking" xmlns:file="http://www.mulesoft.org/schema/mule/file" xmlns:dw="http://www.mulesoft.org/schema/mule/ee/dw" xmlns="http://www.mulesoft.org/schema/mule/core" xmlns:doc="http://www.mulesoft.org/schema/mule/documentation"
xmlns:spring="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-current.xsd
http://www.mulesoft.org/schema/mule/core http://www.mulesoft.org/schema/mule/core/current/mule.xsd
http://www.mulesoft.org/schema/mule/file http://www.mulesoft.org/schema/mule/file/current/mule-file.xsd
http://www.mulesoft.org/schema/mule/ee/dw http://www.mulesoft.org/schema/mule/ee/dw/current/dw.xsd
http://www.mulesoft.org/schema/mule/http http://www.mulesoft.org/schema/mule/http/current/mule-http.xsd
http://www.mulesoft.org/schema/mule/ee/tracking http://www.mulesoft.org/schema/mule/ee/tracking/current/mule-tracking-ee.xsd">
<http:listener-config name="HTTP_Listener_Configuration" host="0.0.0.0" port="8081" doc:name="HTTP Listener Configuration"/>
<flow name="CSVFlow">
<http:listener config-ref="HTTP_Listener_Configuration" path="/process" doc:name="HTTP"/>
<object-to-string-transformer doc:name="Object to String"/>
<dw:transform-message doc:name="Transform Message">
<dw:set-payload><![CDATA[%dw 1.0
%output application/json
---
//First remove the header
(payload splitBy "\n")[1 to -1]
map ((item, index) -> using(commas = item find ",")
{
id: item[0 to commas[0] - 1],
name: item[commas[0] + 1 to commas[1] - 1],
attributes: item[commas[1] + 1 to -1]
}
)]]></dw:set-payload>
</dw:transform-message>
</flow>
</mule>

Hi Rajeev the problem with your data is that is not a CSV the json needs to be escaped somehow. So the way I solved your problem was by handling your input as a String (this can be easily done by using the object to string mp) and parsing it by hand. My assumption is that there are not more tricks on your format.
%dw 1.0
output application/json
---
//First remove the header
(payload splitBy "\n")[1 to -1]
map ((item, index) -> using(commas = item find ",")
{
id: item[0 to commas[0] - 1],
name: item[commas[0] + 1 to commas[1] - 1],
attributes: item[commas[1] + 1 to -1]
}
)

there is one problem - special characters in csv(, ") are surrounded by quotes. if there are more than one columns with multiple quotes and commas in the values the above solution will fail. i took the liberty to modify the solution and add some tweaks to it:
%dw 2.0
output application/java
import * from dw::core::Arrays
var headerRow = (data) -> ((data splitBy "\n")[0]) splitBy ","
var dataRows = (data) -> (data splitBy "\n")[1 to -1]
---
dataRows(payload) map (dataRow,index) ->
do
{
var commas = dataRow find ","
var indices = flatten(dataRow find /(?<!")"(?!")/)
var quoteposition =
indices map
(
(item, index) ->
(
{
(start:item) if isEven(index),
(end:indices[index + 1]) if isEven(index)
}
)
) filter $ != null and $ != {}
fun removeCommasinQuotes(c: Array<Number>, q: Array<{|start?: Number, end?: Number|}>) = c filter (item,index) -> !(q some (item > $.start and item < $.end))
var separator = removeCommasinQuotes(commas,quoteposition)
---
{
(headerRow(payload)[0]): dataRow[0 to separator[0] - 1],
(headerRow(payload)[1]):dataRow[separator[0] + 1 to separator[1] - 1],
(headerRow(payload)[2]):dataRow[separator[1] + 1 to separator[2] - 1],
(headerRow(payload)[3]):dataRow[separator[2] + 1 to separator[3] - 1],
(headerRow(payload)[4]):dataRow[separator[3] + 1 to separator[4] - 1],
(headerRow(payload)[5]):dataRow[separator[4] + 1 to separator[5] - 1],
(headerRow(payload)[6]):dataRow[separator[5] + 1 to separator[6] - 1],
(headerRow(payload)[7]):dataRow[separator[6] + 1 to separator[7] - 1],
(headerRow(payload)[8]):dataRow[separator[7] + 1 to -1]
}
}

Related

Convert Json objects Collection to Array

I have a json file which contains a collection of numerous JSON objects. A sample format is given below:
{"ID": 123,"Name": "TEST-1","val11": {},"url": "test1.com","val12": []}
{"ID": 456,"Name": "TEST-2","val21": {},"url": "test2.com","val22": []}
{"ID": 789,"Name": "TEST-3","val31": {},"url": "test3.com","val32": []}
As you see, it is not an array ([ ] and commas missing). I need to convert this into a valid JSON array.
The code that I tried is:
%dw 2.0
output application/json
var PayloadSplit = payload splitBy('\n')
var PayloadArray = (PayloadSplit map (value, index) -> read(value, 'application/json'))
---
PayloadArray
This works fine for a small sized payload. However, if I try to perform this on the entire file (size about 320 MB with ~20k JSON objects), it fails with a java.lang.OutOfMemoryError: Java heap space error. Is there a way to overcome this? Or can I split the main file into multiple files and then try this (in a ForEach Loop perhaps?). Please advise
Edit1 - Attaching the mule flow below:
<flow name="convert-object-to-array-test1Flow" doc:id="0645e9bd-7f77-4b1e-93d0-dedd9d154ef7" >
<http:listener doc:name="Listener" doc:id="551cd3b6-e4c8-4b7a-aff3-305effbe8a8b" config-ref="HTTP_Listener_config" path="/file"/>
<file:read doc:name="Read" doc:id="21a310c1-5887-4bc0-83b9-b8968e145f0d" path="C:\Desktop\NDJsonSample.json" outputMimeType="application/ndjson" />
<ee:transform doc:name="Transform Message" doc:id="95235c56-2f5a-4f39-ba96-8be7c4e501b5" >
<ee:message >
<ee:set-payload ><![CDATA[%dw 2.0
output application/json
---
payload]]></ee:set-payload>
</ee:message>
</ee:transform>
<logger level="INFO" doc:name="Logger" doc:id="935530dd-17fd-41c9-8fe0-1561ba3de703" />
</flow>
DW already have support for this format. It is called ndjson. Please visit the documentation. You just need to set application/ndjson to the payload.

Cannot coerce Array error while using map operator with XML data in mule 4?

I am getting the following error while using the map operator:
org.mule.runtime.core.internal.message.ErrorBuilder$ErrorImplementation
{
description="Cannot coerce Array (org.mule.weave.v2.model.values.ArrayValue$IteratorArrayValue#22af825a) to String
Trace:
at main (Unknown), while writing Xml
Payload:
%dw 2.0
output application/xml
ns cc someUrl
---
(vars.products*product map {
cc #productDetails: {
cc #productCategory: $.productCategory,
cc #productName: $.productName,
cc #productImageData: $.productImageData
}
})
Products:
[
product:{productCategory= "A", productName="name", productImageData=base64 string},
product:{productCategory= "B", productName="name2", productImageData=base64 string},
product:{productCategory= "C", productName="name3", productImageData=base64 string}
]
There are no arrays in XML. I resolved that by using reduce() to concatenate the objects in the array. Also I added a root element, which is required in XML.
For simplicity, I just added products as a variable inside the script:
%dw 2.0
output application/xml
ns cc someUrl
var products=[
product:{productCategory: "A", productName:"name", productImageData:"base64 string"},
product:{productCategory: "B", productName:"name2", productImageData:"base64 string"},
product:{productCategory: "C", productName:"name3", productImageData:"base64 string"}
]
---
result: ( products.*product map {
cc #productDetails: {
cc #productCategory: $.productCategory,
cc #productName: $.productName,
cc #productImageData: $.productImageData
}
} ) reduce ((item, accumulator={}) -> item ++ accumulator )
Output:
<?xml version='1.0' encoding='UTF-8'?>
<result>
<cc:productDetails xmlns:cc="someUrl">
<cc:productCategory>C</cc:productCategory>
<cc:productName>name3</cc:productName>
<cc:productImageData>base64 string</cc:productImageData>
</cc:productDetails>
<cc:productDetails xmlns:cc="someUrl">
<cc:productCategory>B</cc:productCategory>
<cc:productName>name2</cc:productName>
<cc:productImageData>base64 string</cc:productImageData>
</cc:productDetails>
<cc:productDetails xmlns:cc="someUrl">
<cc:productCategory>A</cc:productCategory>
<cc:productName>name</cc:productName>
<cc:productImageData>base64 string</cc:productImageData>
</cc:productDetails>
</result>

Multiple JSON payload to CSV file

i have a task to generate CSV file from multiple JSON payloads (2). Below are my sample data providing for understanding purpose
- Payload-1
[
{
"id": "Run",
"errorMessage": "Cannot Run"
},
{
"id": "Walk",
"errorMessage": "Cannot Walk"
}
]
- Payload-2 (**Source Input**) in flowVars
[
{
"Action1": "Run",
"Action2": ""
},
{
"Action1": "",
"Action2": "Walk"
},
{
"Action1": "Sleep",
"Action2": ""
}
]
Now, i have to generate CSV file with one extra column to Source Input with ErrorMessage on one condition basis, where the id in payload 1 matches with sourceInput field then errorMessage should assign to that requested field and generate a CSV file as a output
i had tried with the below dataweave
%dw 1.0
%output application/csv header=true
---
flowVars.InputData map (val,index)->{
Action1: val.Action1,
Action2: val.Action2,
(
payload filter ($.id == val.Action1 or $.id == val.Action2) map (val2,index) -> {
ErrorMessage: val2.errorMessage replace /([\n,\/])/ with ""
}
)
}
But, here im facing an issue with, i'm able to generate the file with data as expected, but the header ErrorMessage is missing/not appearing in the file with my real data(in production). Kindly assist me.
and Expecting the below CSV output
Action1,Action2,ErrorMessage
Run,,Cannot Run
,Walk,Cannot Walk
Sleep,
Hello the best way to solve this kind of problem is using groupBy. The idea is that you groupBy one of the two parts to use the join by and then you iterate the other part and do a lookup. This way you avoid O(n^2) and transform it to O(n)
%dw 1.0
%var payloadById = payload groupBy $.id
%output application/csv
---
flowVars.InputData map ((value, index) ->
using(locatedError = payloadById[value.Action2][0] default payloadById[value.Action1][0]) (
(value ++ {ErrorMessage: locatedError.errorMessage replace /([\n,\/])/ with ""}) when locatedError != null otherwise value
)
)
filter $ != null
Assuming "Payload-1" is payload, and "Payload-2" is flowVars.actions, I would first create a key-value lookup with the payload. Then I would use that to populate flowVars.actions:
%dw 1.0
%output application/csv header=true
// Creates lookup, e.g.:
// {"Run": "Cannot run", "Walk": "Cannot walk"}
%var errorMsgLookup = payload reduce ((obj, lookup={}) ->
lookup ++ {(obj.id): obj.errorMessage})
---
flowVars.actions map ((action) -> action ++ errorMsgLookup[action.Action1])
Note: I'm also assuming flowVars.action's id field is unique across the array.

json key iteration in DW mule

I have the following requirement need to interate the dynamic json key
need to use this json key and iterate through it
This is my input
[
{
"eventType":"ORDER_SHIPPED",
"entityId":"d0594c02-fb0e-47e1-a61e-1139dc185657",
"userName":"educator#school.edu",
"dateTime":"2010-11-11T07:00:00Z",
"status":"SHIPPED",
"additionalData":{
"quoteId":"d0594c02-fb0e-47e1-a61e-1139dc185657",
"clientReferenceId":"Srites004",
"modifiedDt":"2010-11-11T07:00:00Z",
"packageId":"AIM_PACKAGE",
"sbsOrderId":"TEST-TS-201809-79486",
"orderReferenceId":"b0123c02-fb0e-47e1-a61e-1139dc185987",
"shipDate_1":"2010-11-11T07:00:00Z",
"shipDate_2":"2010-11-12T07:00:00Z",
"shipDate_3":"2010-11-13T07:00:00Z",
"shipMethod_1":"UPS Ground",
"shipMethod_3":"UPS Ground3",
"shipMethod_2":"UPS Ground2",
"trackingNumber_3":"333",
"trackingNumber_1":"2222",
"trackingNumber_2":"221"
}
}
]
I need output like following
{
"trackingInfo":[
{
"shipDate":"2010-11-11T07:00:00Z",
"shipMethod":"UPS Ground",
"trackingNbr":"2222"
},
{
"shipDate":"2010-11-12T07:00:00Z",
"shipMethod":"UPS Ground2",
"trackingNbr":"221"
},
{
"shipDate":"2010-11-13T07:00:00Z",
"shipMethod":"UPS Ground3",
"trackingNbr":"333"
}
]
}
the shipdate, shipmethod ,trackingnumber can be n numbers.
how to iterate using json key.
First map the array to iterate and then use pluck to get a list of keys.
Then as long as there is always the same amount of shipDate to shipMethod etc fields. filter the list of keys to only iterate the amount of times those field combinations exist.
Then construct the output of each object by dynamically looking up the key using 'shipDate__ concatenated with the index(incremented by 1 because your example starts at 1 and dw arrays start at 0):
%dw 2.0
output application/json
---
payload map ((item, index) -> item.additionalData pluck($$) filter ($ contains 'shipDate') map ((item2, index2) ->
using(incIndex=(index2+1 as String)){
"shipDate": item.additionalData[('shipDate_'++ incIndex)],
"shipMethod": item.additionalData[('shipMethod_'++ incIndex)],
"trackingNbr": item.additionalData[('trackingNumber_'++ incIndex)],
}
)
)
In DW 1.0 syntax:
%dw 1.0
%output application/json
---
payload map ((item, index) -> item.additionalData pluck ($$) filter ($ contains 'shipDate') map ((item2, index2) ->
using (incIndex = (index2 + 1 as :string))
{
"shipDate": item.additionalData[('shipDate_' ++ incIndex)],
"shipMethod": item.additionalData[('shipMethod_' ++ incIndex)],
"trackingNbr": item.additionalData[('trackingNumber_' ++ incIndex)]
}))
It's mostly the same, except:
output => %output
String => :string

How to iterator over a JSON Array in Mule 3

Mule 3.8.3 Studio 6.4.4
I am receiving a XML payload that is a collection of customer numbers. I need to end up pulling each number out and sending it to a messaging queue.
Sample Incoming Data:
<request func="">
<data>
<transactions time='1539262470'>
<transaction set='customers' notifyid='WMS_NADC_CUSTOMERS' type='update'>
<customers>
<customer id="CIT_1113-11" t="1539257721" y="U" w="WebUser"></customer>
<customer id="C42998-2" t="1539261561" y="N" w="WebUser"></customer>
<customer id="C42998" t="1539262040" y="U" w="WebUser"> </customer>
</customers>
</transaction>
</transactions>
</data>
</request>
After receiving this I use weave to transform into json in an attempt to more easily access the id's.
%dw 1.0
%output application/json
---
{
customers: payload.request.data.transactions.transaction.customers.*customer map (cust, indexOfCustomer) ->{
customer: cust.#id as :string
}
}
The transformed payload now looks like
{
"customers": [
{
"customer": "CIT_1113-11"
},
{
"customer": "C42998-2"
},
{
"customer": "C42998"
}
]
}
At this point, I am trying to loop through the payload. Setting the for each to payload.get('customers') takes me into a jackson.node.ArrayNode.
I haven't been able to figure out how to access each individual object inside the list. Can one of you please tell me how to do that?
I want to end up placing a payload onto a message queue that looks like
{
"customer": "C42998"
}
The simplest way would be to use a combination of DataWeave to get a Java Iteratable, and a for-each scope. Check out this example (assuming the incoming payload is your XML).
<dw:transform-message doc:name="Transform Message">
<dw:set-payload><![CDATA[%dw 1.0
%output application/java
%var customers = payload.request.data.transactions.transaction.customers.*customer
---
customers map (customer) ->{
customer: customer.#id as :string
}]]></dw:set-payload>
</dw:transform-message>
<foreach doc:name="For Each">
<json:object-to-json-transformer doc:name="Object to JSON"/>
<logger message="#[payload]" level="INFO" doc:name="Logger"/>
</foreach>
In Mule 3, the for-each won't accept JSON or XML, even if they obviously represent a structure that could be iterated over (like a JSON array, for example). This is why we need %output application/java in the DataWeave transformer. Later, within the for-each scope, we can transform this back to JSON. In your case, just replace the logger with your queue connector to send the messages where needed.