08. Mapping Data
Up until now, every time we've wanted to prepare a payload we've done it manually by creating tags (e.g. {somefield}
) in an XML or JSON document and then translating that out using either Custom Properties or the Formatter
Node.
In most real world scenarios, we want to go beyond simple text substitution and be able to map hierarchical data easily from one schema (shape) to another. We also want to be able to apply functions to individual fields. For example, we might want to trim a field to a specific length so it can be accepted by the target system.
We'll achieve this using the QuickMap
Node, our visual data mapper. QuickMap is provided a source and target schema and then allows mappings to be expressed as Flow Connectors. Where needed, inline functions can be used at the field level to convert data too.
Exercise 10. Mapping Data
In this exercise, we'll look at how to map a document containing orders we have received as JSON into a FreshBooks invoice. While we won't actually run the FreshBooks Node, we'll use its Node Samples to see how it requires an invoice document to be structured.
Add
QuickMap
to a new Workflow, connectStart.RunNow → QuickMap
.Add
FreshBooks
.Click the
Change/View
link onQuickMap.Mapping
. In the screen that displays, you'll see QuickMap requires an example of the source and the target document and indicates the different ways that a schema (shape of the data) can be acquired.Generally, QuickMap will infer a schema for the source data by reading a recent Workflow Log of the preceding step. The schema for the result will be inferred by reading data loaded from a sample of the subsequent step.
For our purposes, we'll hardcode an example JSON document for the source and use a Node sample for the result.
Set
QuickMap.Source
to the order JSON below:{ "Orders": { "Order": [ { "CustomerID": "ATA005", "EmployeeID": "6", "OrderDate": "2019-05-06T00:00:00", "RequiredDate": "2019-05-20T00:00:00", "Lines": [ { "InventoryCode": "INV001", "Quantity": "12", "TotalCostPrice": "100" }, { "InventoryCode": "INV002", "Quantity": "6", "TotalCostPrice": "40" }, { "InventoryCode": "INV001", "Quantity": "2", "TotalCostPrice": "120" } ] }, { "CustomerID": "ZXS002", "EmployeeID": "6", "OrderDate": "2019-05-06T00:00:00", "RequiredDate": "2019-05-20T00:00:00", "Lines": [ { "InventoryCode": "INV003", "Quantity": "12", "TotalCostPrice": "65" }, { "InventoryCode": "INV004", "Quantity": "6", "TotalCostPrice": "13" }, { "InventoryCode": "INV002", "Quantity": "2", "TotalCostPrice": "13" } ] }, { "CustomerID": "CASH", "EmployeeID": "6", "OrderDate": "2019-05-06T00:00:00", "RequiredDate": "2019-05-20T00:00:00", "Lines": [ { "InventoryCode": "INV002", "Quantity": "12", "TotalCostPrice": "210" }, { "InventoryCode": "INV002", "Quantity": "6", "TotalCostPrice": "30" }, { "InventoryCode": "INV006", "Quantity": "2", "TotalCostPrice": "15" } ] } ] } }
Click the
Change/View
link again onQuickMap.Mapping
. UnderData for Source Property
, you should now seeThe Source Property is set up correctly
. We'll now configure the Result Property.Add
FreshBooks
and choose theCreate Invoice
sample (clickv → Choose Sample
).Open the
FreshBooks.RequestXml
Property to see the sample invoice document that has been populated from the sample.Connect
QuickMap.Result → FreshBooks.RequestXml
.Click the
Change/View
link once more onQuickMap.Mapping
. This time, you should see both a source and a target data tree. We are now ready to begin mapping fields.In QuickMap, connect
Order → invoice
,CustomerID → client_id
andOrderDate → date
.As soon as you've created the mapping, a preview of the resulting document will be shown on the right hand side. At this point, the document should look like this:
<request> <invoice> <client_id>ATA005</client_id> <date>2019-05-06T00:00:00</date> </invoice> <invoice> <client_id>ZXS002</client_id> <date>2019-05-06T00:00:00</date> </invoice> <invoice> <client_id>CASH</client_id> <date>2019-05-06T00:00:00</date> </invoice> </request>
Focus the Map Expression textbox to the right of the
date
field and typeLEFT({OrderDate}, 10)
.The
LEFT
function behaves like the one in Excel - it trims a string to a certain number of characters measured from the left. In this case, we're using it to exclude the time component from theOrderDate
field.Like elsewhere in the platform, field references are enclosed in braces and as you start typing either a function or the name of a connected field, a suggestion list will be displayed for autocomplete. You can either click a suggestion or press TAB to have it applied.
Read the QuickMap Functions article for a complete list of available functions.
We'll now map the invoice detail line fields. You can use the
Search
textbox to quickly highlight matches and click the funnel icon to toggle between search and filter modes. Filter mode is useful where the tree is large and you don't want to scroll down to find the desired field.Just like in the Workflow Design, you can connect fields either by dragging from a source Flow Socket to a target Flow Socket or alternatively, click the source Flow socket, then click the target Flow Socket. This is an easier way to connect Flow Sockets that are not close together.
Connect
Line → line
,InventoryCode → name
andQuantity → quantity
.Notice that the same inventory item appears on multiple lines in some of the orders. We will use aggregation to collapse these to individual lines.
To do this, we specify which field or fields to group by and then specify an aggregator function for the remaining fields. In this case, we're going to group by
name
(the inventory item code) and sum thequantity
field.Click the key icon to the right of the
name
field.Set the Map Expression textbox to the right of the
quantity
field toSUM({Quantity})
.SUM
is a function similar to the SUM function from Excel - it returns the sum of a range of numbers, in this case the set of quantities that were collapsed in to a single container due to grouping on thename
field.We'll now look at how we can augment the result data using a separate source. Let's say we wanted to address the order to a specific contact at the company. The order data we have does not contain contact information so we'd need to separately query the list of contact information and then feed that in to QuickMap.
For this exercise, we'll just hardcode an example of that contact information directly into a QuickMap Custom Property. In a real world scenario, we would have queried it as an earlier step in the Workflow.
Click
✓
to close QuickMap, then addQuickMap.Contacts
and set the data type toXml
. Paste this value into the new Custom Property:<Customers> <Customer> <CustomerID>ATA005</CustomerID> <ContactName>Howard Snyder</ContactName> </Customer> <Customer> <CustomerID>ZXS002</CustomerID> <ContactName>Yoshi Latimer</ContactName> </Customer> </Customers>
Go back into the map by clicking
Change/View
on theMapping
Property.Connect
CustomerID → notes
.Set the Map Expression on
notes
toLOOKUP({CustomerID}, "Contacts", "CustomerID", "ContactName", "")
LOOKUP
works a little like an Excel VLOOKUP - it searches in a Custom Property namedContacts
for a field namedCustomerID
that matches the value of{CustomerID}
and when it finds it finds a match, returns the value of the fieldContactName
. The last parameter tells theLOOKUP
function what to return when there is no match - in our case, we just return an empty value.Take a minute to walk through this step by step, then write the expression by hand rather than copy and pasting it.
To finish off the mapping, prefix it with the text
Deliver To:
using theCONCAT
function -CONCAT("Deliver to: ", LOOKUP(...))
.For our final step, we'll look at how to filter data based on a condition. In our example payload, one of the customers is
CASH
, we'll create a filter to exclude cash customers.The filter will be specified at the container level (i.e. the
invoice
container) so we'll need to have access to theCustomerID
field (i.e. the field we're filtering on) from that container.Under the
Map Expressions
column, clickSwitch to Filter Expressions
.We are now in the Filter Expressions view and since these expressions can only be applied to containers, the expression textboxes only display next to containers.
Connect
CustomerID → invoice
(an error will be shown because we have multiple fields mapped with no expression specified).Set the Filter Expression for
invoice
to{CustomerID} <> "CASH"
.When the preview refreshes the order for the
CASH
customer should no longer be shown and should look like this:<request> <invoice> <client_id>ATA005</client_id> <date>2019-05-06</date> <notes>Deliver to: Howard Snyder</notes> <lines> <line> <name>INV001</name> <quantity>14</quantity> </line> <line> <name>INV002</name> <quantity>6</quantity> </line> </lines> </invoice> <invoice> <client_id>ZXS002</client_id> <date>2019-05-06</date> <notes>Deliver to: Yoshi Latimer</notes> <lines> <line> <name>INV003</name> <quantity>12</quantity> </line> <line> <name>INV004</name> <quantity>6</quantity> </line> <line> <name>INV002</name> <quantity>2</quantity> </line> </lines> </invoice> </request>
You have now completed the mapping. Run the Workflow before submitting the exercise for grading. Only the QuickMap Node should run, don't connect the FreshBooks Node.
Save and run your Workflow, then click Submit Exercise
to grade it.