AVIO Consulting

Hierarchical X12 EDI Data Mapping with DataWeave

Mar 24, 2022 | Uncategorized

Electronic Data Interchange (EDI) formats are commonly used in many industries. They have been in use for a long time and have well-defined data structures to transfer business-to-business (B2B) transaction data across systems. In earlier B2B posts, we discussed how using the Mulesoft Anypoint Partner Manager Can Accelerate Your Business.

In this blog, I will explore an approach for transforming an 856 Advanced Shipment notice document into a JSON format and vice-versa.

X12 and other EDI formats define document types such as 850 Purchase Order, 836 Procurement Notices, and many more. This list of predefined document types include industry-specific documents such as HIPAA documents for the healthcare industry. In our previous blog, How to Do B2B Integrations Using MuleSoft EDI X12 Module, we demonstrated how to transform X12 810 Invoice documents into JSON for processing within your APIs.

When working with X12 documents in MuleSoft, we leverage Mule’s X12 Connector that allows us to read and write documents in and out of X12 format. The connector consumes a standard X12 text-based document and converts it into a DataWeave-friendly data structure for transforming it into other API-friendly formats such as JSON or XML.

Some documents such as 850 Purchase Order are easy to map into an API-friendly format, but documents like 856 Advanced Shipment notices can be tricky due to the hierarchical data presented in a plain-text format. Although the X12 connector can convert it into DataWeave friendly structure, the challenge of transforming this hierarchical data into an API-friendly structure may require some advanced DataWeave usage.

 

X12 856 Advanced Shipment Notice

Let’s consider an example of an 856 document that contains Shipment notices and details which are presented in a hierarchy. The Hierarchical Level (HL) segments describe four types of data elements: Shipment, Orders, Packages, and Items.


ST - 856
    (1-to-N) → HL - Shipment
        (1-to-N) → HL - Order
            (1-to-N) → HL - Package
                 (1-to-N)→ HL - Item

HL Segment has three elements to define the hierarchy:
     –     HL01: Defines the current level in the hierarchy
     –     HL02: Defines the parent hierarchy level
     –     HL03: Type of the hierarchy item – Shipment (S), Order (O), Package (P), Item (I).

ISA|00| |00|   |ZZ|123456X |ZZ|9999999
|201229|1539|U|00400|012291501|0|P|< GS|SH|123456X|9999999|20201229|1539|1399|X|004010 ST|856|000000001 BSN|00|020201229153948475|20201229|153948|0001 HL|1||S TD1|CTN|1||||G|19.72|LB TD5||2|UPSN REF|BX|11942132941 DTM|011|20201229|0839|GM DTM|017|20201231|0839|GM FOB|CC N1|SF|A. VENDOR INC.|15|123456X N3|12345 West 100th Street N4|BOCA RATON|FL|12345|US N1|ST|A. CUSTOMER|15|99999861 N3|123 ANY ROAD|WAREHOUSE N4|NEW YORK|NY|12345|USA HL|2|1|O PRF|4NY1IIZE HL|3|2|P REF|CN|R75E580A0301138041 MAN|GM|00008827693741579888 HL|4|3|I LIN|0|EN|9780815345558|||PO|123ZZ456 SN1||1|EA HL|5|1|O PRF|123ZZ456 HL|6|5|P REF|CN|R75E580A0301138050 MAN|GM|00008827693741579895 HL|7|6|I LIN|0|EN|9780415718981|||PO|123ZZ456 SN1||4|EA HL|8|1|O PRF|123ZZ456 HL|9|8|P REF|CN|R75E580A0301138069 MAN|GM|00008827693741579918 HL|10|9|I LIN|0|EN|9781596671997|||PO|123ZZ456 SN1||16|EA CTT|10|16 SE|41|000000001 GE|1|1399 IEA|1|012291501

For the purpose of this use case, let’s assume that our backend API accepts Shipment data in the JSON format. The JSON structure contains a Shipment object with the hierarchy of Shipment → Orders → Packages → Items.


{
   "shipment": {
       //Shipment Info goes here
   },
   "orders": [
       //Collection of all orders in this Shipment
       {
           "order": {
               //Order Info goes here
           },
           "packages": [
               //Collection of all packages in this order
               {
                   "package": {
                       //Package info goes here
                   },
                   "items": [
                       //Collection of all items in this package
                       {
                           "item": {
                               //Item info goes here
                           }
                       }
                   ]
               }
           ]
       }
   ]
}

X12 Connector and DataSense

When working with the X12 connector in Anypoint Studio, the DataSense feature of the studio can greatly simplify the mapping process. A properly configured X12 connector in Anypoint Studio can show the output structure of x12:read operation using DataSense.

In our use case, as we are working on v004010/856 documents, we see the structure of the message in the Transform Message’s Input section. The 856 structure contains a “Detail → 010_HL_Loop” element that will hold an array of all HL segments parsed from the input X12 document.

Blog Image - Hierachal x12

The Loop is a flat array that we need to translate into the hierarchical shipments data. Each HL segment has fields representing hierarchical level information.

 

X12 to JSON Transformation with DataWeave

In most of the array iteration scenarios, the `map` function is sufficient for transforming data.

map(Array<T>, (item: T, index: Number) -> R): Array<R>

While `map` allows us to iterate through a collection, it doesn’t let us access the collection being built.

In our case, we need a stateful iteration so that we know the position of the current record in the hierarchy when compared to previous records and to accommodate that in the final output. The other way to iterate through a collection is to use the `reduce` function.

reduce(Array<T>, (item: T, accumulator: T) -> T): T | Null

Unlike the `map` function, the `reduce` function allows accessing the accumulated values during the iteration. Every iteration can prepare a new accumulator which replaces the earlier one. This makes it much more powerful than `map` as we not only get a way to access previously transformed elements but also to modify them as needed.

Here is a DataWeave script that will iterate over 856 transactions in the X12 document and prepare the required JSON structure.


%dw 2.0
output application/json
//Example shipment object structure
var shipment = {
  shipment: {},
  orders: [
      packages:[
          items: []
      ]
  ]
}
---
//Iterate over each 856.
payload.TransactionSets.v004010."856" map (st, index) -> (
  //Reduce 010 HL Loop
  st.Detail."010_HL_Loop" reduce ((item, accumulator = []) -> do {
      //If new Shipment to new object otherwise get last shipment from accumulator
      var lastShipment = if (item."010_HL".HL03 == "S") { //<1>
          shipment: {
              hierarchyId: item."010_HL".HL01
          },
          orders: [
              packages: [
                  items: []
              ]
          ]
          } else (accumulator[-1] default {}) //<2>
      //Use update operator to add HL to appropriate last parent
      var updatedShipment = lastShipment update {
          case orders at .orders if(item."010_HL".HL03 == "S") -> [] //remove placeholder empty order
          case orders at .orders if(item."010_HL".HL03 == "O") -> orders << { //<3> this is a new order, add in shipment orders.
                  order: {
                           hierarchyId: item."010_HL".HL01,
                           parentId: item."010_HL".HL02,
                           purchaseOrderNumber: item."050_PRF".PRF01
                       }, packages: []
              }
          case packages at .orders[-1].packages if(item."010_HL".HL03 == "P") -> packages << { //<4> new package in previous order, add with empty items
                  package: {
                               hierarchyId: item."010_HL".HL01,
                               parentId: item."010_HL".HL02
                           }, items: []
              }
          case items at .orders[-1].packages[-1].items if(item."010_HL".HL03 == "I") -> items << { //<5< new item to previous package
                  item: {
                       hierarchyId: item."010_HL".HL01,
                       parentId: item."010_HL".HL02,
                       itemNumber: item."020_LIN".LIN03
                  }
              }
      }
      ---
      //replace last shipment object with updated one
      (accumulator - lastShipment) << updatedShipment //<6>
  })
)

<1>: This is where we build our Shipment Object with whatever information needed from X12 document. In the above script, we just initialized an empty skeleton object for demo.
<2>: When iterating over a non-shipment entry in the loop, we just pick the last shipment so we can add order/package/item into it.
<3>: We reached a new Order for the last Shipment. Here we map the loop item to a required Order structure and add it into the Orders collection on the last Shipment.
<4>: We reached a new Package for the last Order. We map the loop item into a Package and add it into the packages collection for last Order.
<5>: A leaf of our hierarchy, we reached a new Package Item. We map the loop item to a Package Item and add it into the last Package of the last Order.
<6>: Finally, we remove the previously built last Shipment instance from the accumulator and replace it with the Shipment modified to add orders, packages, or items. The end result of the reduce operation will be this modified accumulator.

A sample output for above script may look like this:


[
    {
        "shipment": {
            "hierarchyId": "1"
          },
        "orders": [
            {
                "order": {
                  "hierarchyId": "2",
                  "parentId": "1",
                  "purchaseOrderNumber": "4NY1IIZE"
                },
                "packages": [
                  {
                    "package": {
                      "hierarchyId": "3",
                      "parentId": "2"
                    },
                    "items": [
                      {
                        "item": {
                          "hierarchyId": "4",
                          "parentId": "3",
                          "itemNumber": "9780815345558"
                        }
                      }
                    ]
                  }
                ]
            }
        ]
    }
]

With that, we built our hierarchical data structure in JSON. It should be easy to complete the data mapping for individual components and accomplish what your business requires.

 

JSON to X12 Transformation with DataWeave

Consider that the same JSON file is sent by your internal APIs for delivering to your trading partners in X12 format. How can we rebuild the X12 850 transaction set from it?

The following DataWeave script should let us rebuild that X12 structure.


%dw 2.0
output application/json

fun toShipment(rec) = {
                            "010_HL": {
                                "HL01": rec.shipment.hierarchyId,
                                "HL03": "S"                         
                            }
                            // Any other Shipment related segment mapping goes here                     
                        }

fun toOrder(s, o) = {
                   "010_HL": {
                       "HL01": o.order.hierarchyId,
                       "HL02": o.order.parentId,
                       "HL03": "O"
                   },
                   "050_PRF": {
                       "PRF01": o.order.purchaseOrderNumber
                   }
                   // Any other Order related segment mapping goes here
                  
               }

fun toPackage(o, p) = {
                   "010_HL": {
                       "HL01": p.package.hierarchyId,
                       "HL02": o.order.hierarchyId,
                       "HL03": "P"
                   }
                   // Any other package related segment mapping goes here
               }
fun toItem(p, i) = {
                   "010_HL": {
                       "HL01": i.item.hierarchyId,
                       "HL02": p.package.hierarchyId,
                       "HL03": "I"
                   }
                  
                   // Any other Item related segment mapping goes here
               }
---
{
    "TransactionSets": {
        "v004010": {
            "856": payload map (st, index) -> {
                "Heading": {
                       "020_BSN": {
                           "BSN01": "00",
                           "BSN02": "020201229153948475",
                            "BSN03": now(),
                           "BSN04": 56388000,
                           "BSN05": "0001"
                       }
                   },
                "Detail": {
                    "010_HL_Loop": st reduce (ship, accShip=[]) -> do{
                       var shipUpd = accShip << toShipment(ship)

                       var ord  = ship.orders reduce (order, accOrd = shipUpd) -> do{
                           var ordUpd = accOrd
                                           << toOrder(ship, order)
                          
                           var pack = order.packages reduce (package, accPkg = ordUpd) -> do {
                               var packUpd = accPkg << toPackage(order, package)

                               var items = package.items reduce (item, accItems = packUpd) ->
                                                       accItems << toItem(package, item)
                               ---
                               items
                           }
                           ---
                           pack
                       }
                       ---
                       ord
                   }
                },
                Summary: {
                    "010_CTT": {
                        //Sum of all shipments, orders, packages, items
                        CTT01: sizeOf(st.shipment) + sizeOf(flatten(st..order)) + sizeOf(flatten(st..package)) + sizeOf(flatten(st..item))
                    }
                }
            }
        }
    }
}

Summary

X12 EDI formats may have complex data structures. Transforming data between X12 format and your internal system supported format may look challenging. DataWeave 2.0 provides many functions and modules that can help achieve the required transformation.

In the above use case, we used functions such as map, reduce, flatten, sizeOf, in addition to the update operator with pattern matching for Hierarchical transformations between X12 and JSON. Your JSON structure may not look like the one assumed in this use case, but the above DataWeave scripts should give you a good idea about how to approach building your structure.

Would you like to learn more about Hierarchical X12 EDI data mapping with DataWeave? Contact sales@avioconsulting.com to schedule a one hour consultation.