All updates

Tediware's JSON Format Explained, Part 3: The 856 And Its Siblings

Adrian Duyzer

Adrian Duyzer

This is the third and final post about Tediware’s JSON format, i.e. the JSON Tediware produces when you use it to convert EDI to JSON.

The first two posts in this series covered keys and values and structure.

Everything in the first two posts applies to every transaction set. This post is about a specific and uniquely hard to understand family of documents: the ones built around the Hierarchical Level segment, or HL.

The most common of these is the 856 Ship Notice/Manifest, but it’s far from the only one.

The specifics of how HL documents work will be addressed in a future post, but in short, the HL segment permits the creation of nested hierarchies. It does this with unique identifiers that are assigned to the levels of the hierarchy. The below examples will help clarify this.

What the X12 for an HL document looks like

Here is the detail section of a small 856, which I will use as the EDI to JSON conversion example for the rest of the post.

This describes a shipment that has orders that have items, i.e. it is an “SOI” HL structure. The first order has two items, the second order has one. Both orders belong to the shipment. The EDI looks like this:

HL*1**S*1~
TD1*CTN25*4****G*127.5*LB~
REF*BM*BOL-77213~
N1*SF*TEDIWARE DISTRIBUTION*92*DC-04~
N1*ST*NORTHSIDE RECEIVING*92*STORE-118~
HL*2*1*O*1~
PRF*PO-55010***20260130~
HL*3*2*I~
LIN*1*UP*012345678905*VN*WIDGET-A~
SN1**24*EA~
HL*4*2*I~
LIN*2*UP*012345678912*VN*WIDGET-B~
SN1**6*EA~
HL*5*1*O*1~
PRF*PO-55011***20260131~
HL*6*5*I~
LIN*3*UP*012345678929*VN*WIDGET-C~
SN1**12*EA~

Every HL segment starts a level. HL01 is the level’s ID, HL02 is the ID of its parent, and HL03 is the level code: S for shipment, O for order, I for item (others can exist, e.g. T for tare, P for pack).

So HL*3*2*I says “this is item level, number 3, and it belongs to number 2”, and number 2 is HL*2*1*O, the first order, which belongs to number 1, the shipment.

Reading it means rebuilding the hierarchy. Everyone who consumes 856s has written the code to do that at least once.

What a naive translation gives you

Most tools that convert EDI to JSON treat HL like any other loop. Here’s a diagram of their typical output:

HL loop
├── HL 1              S  shipment  BOL-77213
├── HL 2  (parent 1)  O  order     PO-55010
├── HL 3  (parent 2)  I  item      WIDGET-A
├── HL 4  (parent 2)  I  item      WIDGET-B
├── HL 5  (parent 1)  O  order     PO-55011
└── HL 6  (parent 5)  I  item      WIDGET-C

The JSON looks like this:

"hierarchical_level_HL_loop": [
  {
    "hierarchical_level_HL": {
      "hierarchical_id_number_01": "1",
      "hierarchical_level_code_03": "S"
    },
    "reference_information_REF": [ ... ]
  },
  {
    "hierarchical_level_HL": {
      "hierarchical_id_number_01": "2",
      "hierarchical_parent_id_number_02": "1",
      "hierarchical_level_code_03": "O"
    },
    "purchase_order_reference_PRF": {
      "purchase_order_number_01": "PO-55010"
    }
  },
  {
    "hierarchical_level_HL": {
      "hierarchical_id_number_01": "3",
      "hierarchical_parent_id_number_02": "2",
      "hierarchical_level_code_03": "I"
    },
    "item_identification_LIN": {
      "product_service_id_05": "WIDGET-A"
    }
  },
  ...
  {
    "hierarchical_level_HL": {
      "hierarchical_id_number_01": "5",
      "hierarchical_parent_id_number_02": "1",
      "hierarchical_level_code_03": "O"
    },
    "purchase_order_reference_PRF": {
      "purchase_order_number_01": "PO-55011"
    }
  },
  {
    "hierarchical_level_HL": {
      "hierarchical_id_number_01": "6",
      "hierarchical_parent_id_number_02": "5",
      "hierarchical_level_code_03": "I"
    },
    "item_identification_LIN": {
      "product_service_id_05": "WIDGET-C"
    }
  }
]

That is correct, in the sense that it is what the standard’s grammar describes: an HL loop that repeats.

The problem is that it’s no more useful than the original EDI file. Nothing about the hierarchy appears in this translation. WIDGET-C sits at the same depth as WIDGET-A and both purchase orders.

The only thing tying WIDGET-C to PO-55011 is the 5 in its parent ID. To answer “which items were on PO-55011” you index the array by HL01, walk each entry’s HL02 up until you hit an order level, and compare.

Tediware’s approach

Tediware translates the loop the same way and then rebuilds the hierarchy from the parent IDs. Here’s a diagram of the end result:

shipment_S_HL_loop
└── shipment  BOL-77213
    └── order_O_HL_loop
        ├── order  PO-55010
        │   └── item_I_HL_loop
        │       ├── item  WIDGET-A
        │       └── item  WIDGET-B
        └── order  PO-55011
            └── item_I_HL_loop
                └── item  WIDGET-C

The JSON looks like this:

"detail": {
  "shipment_S_HL_loop": [
    {
      "hierarchical_level_HL": {
        "hierarchical_id_number_01": "1",
        "hierarchical_level_code_03": "shipment_S"
      },
      "carrier_details_quantity_and_weight_TD1": [
        {
          "packaging_code_01": "CTN25",
          "lading_quantity_02": 4
        }
      ],
      "reference_information_REF": [
        {
          "reference_identification_qualifier_01": "bill_of_lading_number_BM",
          "reference_identification_02": "BOL-77213"
        }
      ],
      "party_identification_N1_loop": [
        {
          "party_identification_N1": {
            "entity_identifier_code_01": "ship_from_SF",
            "name_02": "TEDIWARE DISTRIBUTION"
          }
        },
        {
          "party_identification_N1": {
            "entity_identifier_code_01": "ship_to_ST",
            "name_02": "NORTHSIDE RECEIVING"
          }
        }
      ],
      "order_O_HL_loop": [
        {
          "hierarchical_level_HL": {
            "hierarchical_id_number_01": "2",
            "hierarchical_parent_id_number_02": "1",
            "hierarchical_level_code_03": "order_O"
          },
          "purchase_order_reference_PRF": {
            "purchase_order_number_01": "PO-55010"
          },
          "item_I_HL_loop": [
            {
              "hierarchical_level_HL": {
                "hierarchical_id_number_01": "3",
                "hierarchical_parent_id_number_02": "2",
                "hierarchical_level_code_03": "item_I"
              },
              "item_identification_LIN": {
                "product_service_id_05": "WIDGET-A"
              },
              "item_detail_shipment_SN1": {
                "number_of_units_shipped_02": 24.0,
                "unit_or_basis_for_measurement_code_03": "each_EA"
              }
            },
            ...
          ]
        },
        {
          "hierarchical_level_HL": {
            "hierarchical_id_number_01": "5",
            "hierarchical_parent_id_number_02": "1",
            "hierarchical_level_code_03": "order_O"
          },
          "purchase_order_reference_PRF": {
            "purchase_order_number_01": "PO-55011"
          },
          "item_I_HL_loop": [
            {
              "hierarchical_level_HL": {
                "hierarchical_id_number_01": "6",
                "hierarchical_parent_id_number_02": "5",
                "hierarchical_level_code_03": "item_I"
              },
              "item_identification_LIN": {
                "product_service_id_05": "WIDGET-C"
              },
              "item_detail_shipment_SN1": {
                "number_of_units_shipped_02": 12.0,
                "unit_or_basis_for_measurement_code_03": "each_EA"
              }
            }
          ]
        }
      ]
    }
  ]
}

(I have trimmed some elements from the segments, and the WIDGET-B item under PO-55010, to keep the example readable. Nothing structural is missing.)

This structure is both human-readable and easily traversable in code. Its hierarchy matches the physical shipment.

A few additional details about how this is built.

The level keys come from the level code. HL03 is a coded element, so it gets the decoration described in the first post: S becomes shipment_S. The loop key is that decorated code plus _HL_loop.

A pack level would be pack_P_HL_loop, a tare level tare_T_HL_loop. You do not need to be told in advance which levels a partner uses. The keys name them, and the nesting shows their hierarchy.

Many partners use shipment, order, and item (SOI). Some insert pack between order and item (SOPI), or tare instead of pack (SOTI), or something else entirely. Whatever they do, the JSON is structurally correct, with each level named.

The HL segment is still there. Each entry keeps its hierarchical_level_HL with the original ID, parent ID, and code. The nesting makes them redundant for reading, but they are the partner’s data and a consumer that wants to log or reconcile against the original document should have them.

Segments stay with their level. The TD1, REF, and N1 loop that followed HL*1 in the EDI are inside the shipment entry. The PRF that followed HL*2 is inside the first order. The LIN and SN1 after HL*3 are inside the first item. This is what the sender intended.

The standard’s grammar for the 856 puts all of those segments as optional children of the one HL loop, because it has no way to say “PRF goes with orders and LIN goes with items”, so a naive translation gives every level the same set of possible keys and leaves you to know which apply. The tree does not fix that by itself, but it means the segments you find inside an order entry are the ones that were sent for that order.

Multiple roots are handled. An 856 normally has one shipment at the top, but the nesting does not assume it. Every level whose parent ID is empty or unresolvable is a root, roots are grouped by their level code, and each group gets its own key in the detail section. Two shipments in one transaction set produce a shipment_S_HL_loop with two entries.

Where the tree comes from

This is done after the ordinary translation, as a pass over the detail section. The parser produces the flat HL loop the grammar describes, the writer turns it into the array shown in the naive example, and then a nesting step reads each entry’s HL01 and HL02, attaches children to parents, and replaces the flat key with the named root keys. The rest of the document is not touched.

Try it

That’s it for Tediware’s JSON format: readable keys and values, a structure that follows the X12 standard, and HL documents nested the way the sender intended.

To see it with a document of your own, paste it into the EDI Translator or run it through a sandbox account. I think you’ll like what you see.