All updates

Tediware's JSON Format Explained, Part 2: Sensible Structure

Adrian Duyzer

Adrian Duyzer

This is the second of three posts about Tediware’s JSON format, i.e. the JSON Tediware produces when you use it to convert EDI to JSON.

The first post in this series focused on keys and values, i.e. the smallest pieces of the format.

This one is about how those pieces are arranged: how Tediware builds a logical JSON structure that faithfully represents what the author of an X12 document had in mind.

Tables

Every X12 transaction set is divided into a heading, a detail area, and a summary. Those become the three top-level keys of a translated transaction set:

{
  "heading": { ... },
  "detail": { ... },
  "summary": { ... },
  "set": "850"
}

set is the transaction set code, which is included so that anything routing on document type does not have to dig into the ST segment to find it.

What you receive from an inbound flow is one of these objects per transaction set. Although it is possible to use the EDI to JSON conversion tool to produce a JSON version of the full interchange including an array of all transaction sets (each adhering to the structure I just described), in normal practice, Tediware splits interchanges into separate transaction sets.

For instance, if a partner sends an interchange (a single document) with four purchase orders in it, you get four deliveries, each with its own heading, detail, and summary.

The envelope details (sender, receiver, control numbers) are handled as metadata, and can be retrieved as such from the Results & Artifacts API, rather than being nested inside the transaction set object.

Segments become objects or arrays

I’ll now discuss how segments are translated. Consider the following EDI from the heading of an 850:

BEG*00*SA*536213576~
REF*X9*3747044980~
REF*IA*70003548~
REF*CO*0616459171~

Some segments can appear once at a given position in the standard, and some can repeat. A BEG appears once in an 850 heading. A REF can appear any number of times. Tediware emits the first as an object and the second as an array:

"beginning_segment_for_purchase_order_BEG": {
  "transaction_set_purpose_code_01": "original_00",
  "purchase_order_type_code_02": "stand_alone_order_SA",
  "purchase_order_number_03": "536213576"
},
"reference_identification_REF": [
  {
    "reference_identification_qualifier_01": "internal_control_number_X9",
    "reference_identification_02": "3747044980"
  },
  {
    "reference_identification_qualifier_01": "internal_vendor_number_IA",
    "reference_identification_02": "70003548"
  },
  {
    "reference_identification_qualifier_01": "customer_order_number_CO",
    "reference_identification_02": "0616459171"
  }
]

The decision comes from the standard’s repeat count, never from how many the partner happened to send. For instance, a REF is an array even when there is exactly one, because the standard says there could be more. This shape is decided by the spec and is the same for every document.

Here is the same rule producing different results for two segments in the same loop. This is a ship-to party from an 856:

N1*ST*NORTHSIDE RECEIVING~
N3*18 KING STREET EAST*SUITE 1400~
N4*TORONTO*ON*M5C1C4*CA~

In an 856, the N3 address segment can appear twice under an N1, and the N4 can appear once:

"name_N1": {
  "entity_identifier_code_01": "ship_to_ST",
  "name_02": "NORTHSIDE RECEIVING"
},
"address_information_N3": [
  {
    "address_information_01": "18 KING STREET EAST",
    "address_information_02": "SUITE 1400"
  }
],
"geographic_location_N4": {
  "city_name_01": "TORONTO",
  "state_or_province_code_02": "ON",
  "postal_code_03": "M5C1C4",
  "country_code_04": "CA"
}

One N3 was sent, and it’s in an array, whereas the N4 is an object. If you know the standard, that is exactly what you would predict from it - if you don’t, you just have to look at one document to learn the shape and from then on, your understanding (and your code!) will hold.

When partners break the standard

Trading partners send things the standard does not allow. The most common case is repeating a segment that the release says appears once. Here is an example from a real 990 sent by a carrier. The 990 in release 005010 allows one L11 at this position:

B1*LTPL*144884*20251126*A~
L11*LILY15190*CN~
L11*46182690*TN~

The strict answer is to keep the first and discard the second, which is the tracking number. However, this would cause data loss, which is never advisable when dealing with production data! As such, Tediware keeps both:

"business_instructions_and_reference_number_L11": [
  {
    "reference_identification_01": "LILY15190",
    "reference_identification_qualifier_02": "carriers_reference_number_pro_invoice_CN"
  },
  {
    "element_01": "46182690",
    "element_02": "TN"
  }
]

The segment became an array even though the standard says object, because two of them showed up and an object cannot hold two. And the second entry has positional keys, element_01 and element_02, with undecoded values.

The parser accepted the first L11, which complies with the standard, and rejected the second, which does not, because it has no schema to name its elements from. The generic keys tell you the segment was not validated, but at least the data is still present.

You can read the tracking number; you also know the partner sent something they should not have.

A discussion of how to deal with this is out of scope for this blog post, but options include inbound mappings so you can map incorrect inbound data into a correct shape, or handling these edge cases as conditionals in your code.

Loops

A loop in X12 is a group of segments that appear together, and can repeat as a group.

The N1 loop is an extremely common example: the N1 names a party, then N3 and N4 provide its address, then optional REF and PER appear for references and contacts. A purchase order has one of these per party, such as one for the shipper and one for the consignee.

Tediware emits a loop as an array with one self-contained entry per occurrence, keyed by the loop’s first segment:

"name_N1_loop": [
  {
    "name_N1": {
      "entity_identifier_code_01": "ship_from_SF",
      "name_02": "TEDIWARE DISTRIBUTION"
    },
    "address_information_N3": [
      {
        "address_information_01": "1 INDUSTRIAL WAY"
      }
    ],
    "geographic_location_N4": {
      "city_name_01": "PORTLAND",
      "state_or_province_code_02": "OR"
    }
  },
  {
    "name_N1": {
      "entity_identifier_code_01": "ship_to_ST",
      "name_02": "NORTHSIDE RECEIVING"
    },
    "address_information_N3": [
      {
        "address_information_01": "18 KING STREET EAST",
        "address_information_02": "SUITE 1400"
      }
    ],
    "geographic_location_N4": {
      "city_name_01": "TORONTO",
      "state_or_province_code_02": "ON"
    }
  }
]

Everything about the ship-to party is inside the ship-to entry.

To find it, filter the array on the qualifier, and then everything you read from that entry belongs to that party.

Loops nest the same way: a PO1 loop in the detail contains its own PID loop, its own DTM segments, its own N1 loop if the line has one, each one an array of self-contained entries.

Composites

Some X12 elements are composites: several components in one element position, joined by a component separator, usually a colon. The MEA segment’s unit of measure is one of the most common examples:

MEA*PD*WT*127.5*LB:1:1~

LB:1:1 is a composite with a unit code, an exponent, and a multiplier. Tediware emits it as an object:

"measurements_MEA": [
  {
    "measurement_reference_id_code_01": "physical_dimensions_PD",
    "measurement_qualifier_02": "weight_WT",
    "measurement_value_03": 127.5,
    "composite_unit_of_measure_04": {
      "_composite": true,
      "unit_or_basis_for_measurement_code_01": "pound_LB",
      "exponent_02": 1.0,
      "multiplier_03": 1.0
    }
  }
]

The components get the same treatment as elements: named from the standard, numbered by position, decoded if they are codes, typed if they are numeric, as discussed in detail in the first post of this series.

The _composite marker is there so that anything traversing the tree can tell a composite apart from a nested segment without consulting the standard.

The alternative to Tediware’s approach, which I’ve seen more than once with other converters, is to leave the composite as the string "LB:1:1" and let the consumer split it.

That works right up until a partner uses a different component separator, which the ISA envelope allows them to do, and it means the components never get names or types.

The rule

The rule for all of this: the standard decides the shape, and the document decides the values. A segment is an array because the spec says it can repeat, and a loop entry holds everything that belongs to one occurrence.

When a partner violates the standard, the data is kept and the keys show the violation.

The one place the flat-segment-list nature of X12 needs more help than this is the hierarchical documents, such as the 856, where the structure the author intended is encoded in parent pointers between segments. That’s coming up next!