# EDIFACT Tutorial - Parse, route, and serialize EDI messages

Parse EDIFACT interchanges into Python objects, route messages by type and serialize them back byte-for-byte.

EDIFACT is the EDI standard that connects trading partners in commerce, logistics, customs, and healthcare. Zato parses EDIFACT interchanges into Python objects, routes messages by type, and serializes them back byte-for-byte.

Parse and process your first EDIFACT interchange in under 5 minutes.

The examples in this tutorial use standard UN/EDIFACT supply chain messages - a bookshop ordering titles from a supplier. Every message type is handled by the same API, whether it comes from the standard directories or an industry-specific dialect.

## In this tutorial {#in-this-tutorial}

1. [Parse your first interchange](https://zato.io/tutorials/edifact.html#parse-your-first-interchange)
2. [Navigate messages by segment](https://zato.io/tutorials/edifact.html#navigate-messages-by-segment)
3. [Route by message type](https://zato.io/tutorials/edifact.html#route-by-message-type)
4. [Serialize byte-for-byte](https://zato.io/tutorials/edifact.html#serialize-byte-for-byte)
5. [Convert between formats](https://zato.io/tutorials/edifact.html#convert-between-formats)
6. [Typed access with dialects](https://zato.io/tutorials/edifact.html#typed-access-with-dialects)

> **Remember:** you can [connect your AI copilot](https://zato.io/docs/ai/connect-your-copilot.html) to Zato documentation.

## Parse your first interchange {#parse-your-first-interchange}

**Step 1.** If you do not have Zato running yet, install it via [Docker](https://zato.io/docs/admin/guide/install/docker.html) - it takes under 5 minutes.

**Step 2.** Open the Zato IDE at **http://localhost:8183**, create a new file called `edifact_api.py`, paste this code, and click **Deploy**:

```python
# -*- coding: utf-8 -*-

# Zato
from zato.server.service import Service
from zato.edifact import parse_edifact

# ################################################################################################################################
# ################################################################################################################################

class MessageHandler(Service):
    """ Processes incoming EDIFACT interchanges.
    """
    name = 'edifact-api.message-handler'

    def handle(self) -> 'None':

        # The raw interchange, as delivered by any channel -
        # REST, file transfer, a mailbox poller or a test invocation
        raw = self.request.input

        # One call parses the envelope and all the messages in it
        interchange = parse_edifact(raw)

        # The UNB header identifies the trading partners
        sender = interchange.header.sender.identification

        for msg in interchange.messages:

            # The UNH message header holds the type identifier, e.g. ORDERS:D:03B:UN
            unh = msg.segments('UNH')[0]
            message_type = unh.e_2[0]

            self.logger.info('Received %s from %s', message_type, sender)
```

**Step 3.** Invoke the service with a test interchange - a purchase order in which a bookshop's supplier is asked for four Robert Louis Stevenson titles:

```python
raw = (
    "UNB+UNOA:4+ORDERAPP:1+BOOKSHOP:1+20070312:1042+7104'\n"
    "UNH+MSGA1+ORDERS:D:03B:UN:EAN008'\n"
    "BGM+220+PORD42+9'\n"
    "DTM+137:20070312:102'\n"
    "NAD+BY+5410738000251::9'\n"
    "NAD+SU+4021456000082::9'\n"
    "LIN+1+1+0140329560:IB'\n"
    "QTY+1:30'\n"
    "FTX+AFM+1++Treasure Island'\n"
    "LIN+2+1+0140383441:IB'\n"
    "QTY+1:20'\n"
    "FTX+AFM+1++Kidnapped'\n"
    "UNT+12+MSGA1'\n"
    "UNZ+1+7104'"
)

response = self.invoke('edifact-api.message-handler', raw)
```

The log confirms the parse:

```text
INFO - Received ORDERS from ORDERAPP
```

Note what just happened - the UNA-less interchange used default separators, the UNB envelope resolved both trading partners, and the message type was read from the `ORDERS:D:03B:UN:EAN008` identity in UNH.

## Navigate messages by segment {#navigate-messages-by-segment}

Any message parses without prior configuration - no message definitions, no schemas to install. Segments are reachable by tag, and their elements by position:

```python
msg = interchange.message

# The document number from BGM - e_2 is the second element
bgm = msg.segments('BGM')[0]
order_number = bgm.e_2                     # PORD42

# Repeating segments come back as lists, in wire order
for line_item in msg.segments('LIN'):

    # A multi-component element is a list of its components
    item_number = line_item.e_3            # ['0140329560', 'IB']

    self.logger.info('Line %s: %s', line_item.e_1, item_number[0])
```

Elements with a single component collapse to their scalar value, so `bgm.e_2` is the string `PORD42`, while `line_item.e_3` keeps both the item number and its `IB` (ISBN) qualifier.

The platform provides fully typed classes for the service segments of the envelope:

```python
# UNB - the interchange header
sender    = interchange.header.sender.identification       # ORDERAPP
recipient = interchange.header.recipient.identification    # BOOKSHOP
prepared  = interchange.header.prepared_at.date            # 20070312

# UNH - the message header
unh = msg.segments('UNH')[0]
reference = unh.e_1                                        # MSGA1
```

The full access model is described in [EDIFACT field access](https://zato.io/docs/dev/healthcare/edifact/field-access/index.html).

## Route by message type {#route-by-message-type}

An interchange can include different message types - orders, despatch advices, invoices - and the UNH identity tells you which is which, so route on it:

```python
class Router(Service):
    """ Routes each message of an interchange to its handler.
    """
    name = 'edifact-api.router'

    handlers = {
        'ORDERS': 'edifact-api.handle-order',
        'DESADV': 'edifact-api.handle-despatch-advice',
        'INVOIC': 'edifact-api.handle-invoice',
    }

    def handle(self) -> 'None':

        interchange = parse_edifact(self.request.input)

        for msg in interchange.messages:

            unh = msg.segments('UNH')[0]
            message_type = unh.e_2[0]

            if message_type in self.handlers:
                service_name = self.handlers[message_type]
                self.invoke(service_name, msg=msg)
            else:
                # Unknown types are still parsed into fully navigable messages
                self.logger.info('Skipping %s', message_type)
```

## Serialize byte-for-byte {#serialize-byte-for-byte}

Parsed interchanges serialize back exactly as they arrived, which makes pass-through, store-and-forward, and audit flows safe:

```python
interchange = parse_edifact(raw)

# The wire text, reproduced exactly, one segment per line
wire = interchange.serialize()

# A second parse-serialize cycle is byte-stable
assert parse_edifact(wire).serialize() == wire
```

Escape sequences, custom separators from UNA, and repetition characters all survive the round trip untouched.

## Convert between formats {#convert-between-formats}

Interchanges, messages, and segments convert to dicts and JSON - handy for handing EDIFACT content to REST APIs, queues, or storage:

```python
interchange = parse_edifact(raw)

data = interchange.to_dict()
json_text = interchange.to_json(indent=2)

# Skip empty elements for compact output
compact = interchange.to_json(include_empty=False)
```

Each message's dictionary records its type under `_message_type` and its segments as positional dictionaries, so downstream consumers never need to know EDIFACT syntax.

## Typed access with dialects {#typed-access-with-dialects}

When you work with the same message types every day, positional access like `e_3` gives way to named fields. A dialect is a Python package that declares message classes with named segments, elements, and components:

```python
# Importing a dialect package registers its message classes -
# here, the Dutch healthcare dialect shipped as an example
import zato.edifact.nl

interchange = parse_edifact(raw)
msg = interchange.message

# Now fields have names instead of positions
family_name = msg.pid.patient_name.married_name
born_year   = msg.pid.date_of_birth.year
```

Read more:

- [Dialects and profiles](https://zato.io/docs/dev/healthcare/edifact/dialects/index.html) - define and register your own message types
- [EDIFACT in healthcare tutorial](https://zato.io/tutorials/healthcare/edifact.html) - a complete worked example with a real dialect

## Where to go next {#where-to-go-next}

- [EDIFACT overview](https://zato.io/healthcare/edifact/) - the landing page with features and examples
- [Message parsing](https://zato.io/docs/dev/healthcare/edifact/parsing/index.html) - envelopes, separators, and round trips in depth
- [Transport patterns](https://zato.io/docs/dev/healthcare/edifact/transport/index.html) - mailboxes, file drops, and REST carriers
- [EDIFACT in healthcare tutorial](https://zato.io/tutorials/healthcare/edifact.html) - typed access with the Dutch healthcare dialect

- - -

## More about EDIFACT

- [EDIFACT overview](https://zato.io/healthcare/edifact/)

  Landing page with code examples and features
- [Message parsing](https://zato.io/docs/dev/healthcare/edifact/parsing/index.html)

  Interchanges, envelopes, separators and round trips
- [Field access](https://zato.io/docs/dev/healthcare/edifact/field-access/index.html)

  Typed segments, composites and repeating groups

- [Dialects and profiles](https://zato.io/docs/dev/healthcare/edifact/dialects/index.html)

  Define your own message types and register them
- [Transport patterns](https://zato.io/docs/dev/healthcare/edifact/transport/index.html)

  Mailboxes, file drops, REST and SOAP carriers
- [EDIFACT in healthcare](https://zato.io/integrations/healthcare/edifact-healthcare-messaging/)

  Where EDIFACT still runs healthcare and how to integrate with it
