Skip to main content
Version: 5.1

EDI

Electronic Data Interchange (EDI) is a set of standards that enables organizations to electronically exchange business documents such as purchase orders, invoices, and shipping notices in a structured, computer-readable format. Common EDI standards include X12 and UN/EDIFACT. The ballerina/edi module provides schema-driven, bidirectional conversion between EDI text and JSON.

EDI data format​

An EDI message is a sequence of segments. Each segment begins with a couple of character segment identifiers (e.g., HDR, ITM) followed by one or more data elements separated by a data element separator. A segment ends with a segment terminator. For example:

HDR*ORDER_1201*ABC_Store*2008-01-01~
ITM*A-250*12~

In this example, * is the data element separator and ~ is the segment terminator. A composite data element contains multiple component elements separated by a component element separator (e.g., :). Data elements can also repeat within a segment using a repetition separator.

Getting started​

Using prebuilt EDIFACT packages​

The fastest path for standard EDIFACT documents is to import a prebuilt package — no schema writing required. Prebuilt packages for common EDIFACT D03A message types are available under the ballerinax organization.

import ballerina/io;
import ballerinax/edifact.d03a.finance.mINVOIC;

public function main() returns error? {
string ediText = check io:fileReadString("resources/invoice.edi");
mINVOIC:EDI_INVOIC_INVOIC invoice = check mINVOIC:fromEdiString(ediText);
// Process the typed invoice data
}

See prebuilt EDIFACT packages for the full list.

Generating types from a schema​

For EDIFACT or X12 documents not covered by prebuilt packages, use the EDI Tool to generate Ballerina types and parser functions from the standard spec. This gives you a typed API without writing a schema by hand.

Module​

ballerina/edi (v1.6.0)

Usage​

Parse EDI to JSON​

import ballerina/edi;
import ballerina/io;

public function main() returns error? {
edi:EdiSchema schema = check edi:getSchema(check io:fileReadJson("path/to/schema.json"));
string ediText = check io:fileReadString("path/to/order.edi");
json orderData = check edi:fromEdiString(ediText, schema);
io:println(orderData.toJsonString());
}

Sample EDI input:

HDR*ORDER_1201*ABC_Store*2008-01-01~
ITM*A-250*12~
ITM*A-45*100~
ITM*D-10*58~

Sample JSON output:

{
"header": {
"code": "HDR",
"orderId": "ORDER_1201",
"organization": "ABC_Store",
"date": "2008-01-01"
},
"items": [
{"code": "ITM", "item": "A-250", "quantity": 12},
{"code": "ITM", "item": "A-45", "quantity": 100},
{"code": "ITM", "item": "D-10", "quantity": 58}
]
}

Serialize JSON to EDI​

import ballerina/edi;
import ballerina/io;

public function main() returns error? {
json newOrder = {
"header": {
"code": "HDR",
"orderId": "ORDER_1201",
"organization": "ABC_Store",
"date": "2008-01-01"
},
"items": [
{"code": "ITM", "item": "A-250", "quantity": 12},
{"code": "ITM", "item": "B-250", "quantity": 10}
]
};
edi:EdiSchema schema = check edi:getSchema(check io:fileReadJson("path/to/schema.json"));
string ediText = check edi:toEdiString(newOrder, schema);
io:println(ediText);
}

Output:

HDR*ORDER_1201*ABC_Store*2008-01-01~
ITM*A-250*12~
ITM*B-250*10~

Load a schema​

import ballerina/edi;
import ballerina/io;

public function main() returns error? {
// Load from a JSON file
edi:EdiSchema schema = check edi:getSchema(check io:fileReadJson("path/to/schema.json"));

io:println("Schema 01:", schema);

// Or load from an inline JSON string
edi:EdiSchema orderSchema = check edi:getSchema(string `{
"name": "SimpleOrder",
"delimiters": {"segment": "~", "field": "*", "component": ":"},
"segments": []
}`);

io:println("Schema 02:", orderSchema);
}

Parse a full interchange​

When the schema declares an envelope (schemas generated from an X12 or EDIFACT spec do), interchangeFromEdiString parses the whole interchange hierarchy into typed records. Each transaction body is fail-safe — a malformed body is captured as an error on EdiTransaction.body instead of aborting the parse, so you can process what you can and quarantine the rest.

import ballerina/edi;
import ballerina/io;

public function main() returns error? {
edi:EdiSchema schema = check edi:getSchema(check io:fileReadJson("path/to/schema.json"));
string ediText = check io:fileReadString("path/to/interchange.edi");

edi:EdiInterchange interchange = check edi:interchangeFromEdiString(ediText, schema);
foreach edi:EdiTransaction txn in interchange.transactions ?: [] {
json|error body = txn.body;
if body is error {
io:println("Quarantined: ", body.message());
continue;
}
io:println(body);
}
}

To route or filter messages without a schema, x12HeadersFromEdiString and edifactHeadersFromEdiString extract just the envelope headers (X12 ISA/GS, EDIFACT UNB/UNH).

Functions​

FunctionSignatureDescription
fromEdiStringfromEdiString(string ediText, EdiSchema schema) returns json|ErrorParse a transaction body into JSON using a schema.
toEdiStringtoEdiString(json msg, EdiSchema schema) returns string|ErrorSerialize JSON into EDI text using a schema.
x12HeadersFromEdiString / x12HeadersFromEdiFile... returns X12Headers|ErrorSchema-free parse of X12 ISA/GS headers for routing and partner identification.
edifactHeadersFromEdiString / edifactHeadersFromEdiFile... returns EdifactHeaders|ErrorSchema-free parse of EDIFACT UNB/UNH headers.
headersFromEdiString / headersFromEdiFile... returns json|ErrorSchema-driven parse of just the envelope header segments.
interchangeFromEdiStringinterchangeFromEdiString(string ediText, EdiSchema schema) returns EdiInterchange|ErrorParse the full envelope hierarchy into typed records, with fail-safe per-transaction bodies.
interchangeToEdiStringinterchangeToEdiString(EdiInterchange msg, EdiSchema schema) returns string|ErrorSerialize a full interchange back to EDI text (recomputes envelope counts).
getSchemagetSchema(string|json schema) returns EdiSchema|errorLoad and validate an EDI schema from a JSON string or object.

For full signatures, parameters, error types (InvalidEnvelopeError, SchemaCompatibilityError, SerializationError), and envelope processing semantics, see the Module Specification.

Custom EDI schemas​

For a proprietary or non-standard format — one with no X12 or EDIFACT specification to convert from — define the structure directly as a JSON schema. The following JSON schema defines the structure of the simple order EDI format shown above. To handle a trading partner's variations on a standard format, start from the converted schema instead: see Adapting the schema to a trading partner.

{
"name": "SimpleOrder",
"delimiters": {
"segment": "~",
"field": "*",
"component": ":",
"repetition": "^"
},
"segments": [
{
"code": "HDR",
"tag": "header",
"minOccurances": 1,
"fields": [
{ "tag": "code" },
{ "tag": "orderId" },
{ "tag": "organization" },
{ "tag": "date" }
]
},
{
"code": "ITM",
"tag": "items",
"maxOccurances": -1,
"fields": [
{ "tag": "code" },
{ "tag": "item" },
{ "tag": "quantity", "dataType": "int" }
]
}
]
}

Each segments entry maps a segment code to a set of ordered field definitions. The tag value becomes the JSON property name in the parsed output, and maxOccurances: -1 allows a segment to repeat any number of times.

Schema reference​

EDI schemas are JSON documents that define how to parse and serialize EDI data. A schema specifies delimiters, segment structures, field types, and occurrence constraints.

Schema structure​

FieldTypeDefaultDescription
namestringRequiredName of the schema.
tagstring"Root_mapping"Tag for the root element.
delimitersrecordRequiredDelimiter characters for segments, fields, components, and sub-components.
ignoreSegmentsstring[][]Segment codes to skip during processing.
preserveEmptyFieldsbooleantrueWhether to include empty fields in output.
includeSegmentCodebooleantrueWhether to include the segment code in output JSON.
segmentsEdiUnitSchema[][]Array of segment and segment group definitions.
segmentDefinitionsmap{}Reusable segment definitions referenced by code.

Delimiters​

FieldTypeDefaultDescription
segmentstringRequiredSegment terminator that marks the end of a segment (e.g., ~).
fieldstringRequiredData element separator between data elements within a segment (e.g., *).
componentstringRequiredComponent element separator between components within a composite data element (e.g., :).
subcomponentstring"NOT_USED"Sub-component element separator, for further subdivision within a component.
repetitionstring"NOT_USED"Repetition separator for data elements that repeat within a segment (e.g., ^).
decimalSeparatorstring—Custom decimal separator for numeric fields.

Segment definition​

FieldTypeDefaultDescription
codestringRequiredSegment identifier code (e.g., "HDR", "ITM").
tagstringRequiredJSON property name for this segment.
truncatablebooleantrueWhether trailing empty fields can be omitted.
minOccurancesint0Minimum number of times this segment must appear.
maxOccurancesint1Maximum occurrences (-1 for unlimited).
fieldsEdiFieldSchema[][]Array of field definitions.

Field definition​

FieldTypeDefaultDescription
tagstringRequiredJSON property name for this field.
repeatbooleanfalseWhether the field can be repeated.
requiredbooleanfalseWhether the field is mandatory.
truncatablebooleantrueWhether trailing empty components can be omitted.
dataTypeEdiDataTypeSTRINGData type: "string", "int", "float", or "composite".
startIndexint-1Starting index for fixed-length fields.
lengthint|Range-1Length constraint (fixed integer or min/max range).
componentsEdiComponentSchema[][]Sub-elements within this field (for composite types).

Example schema​

{
"name": "SimpleOrder",
"delimiters": {
"segment": "~",
"field": "*",
"component": ":"
},
"segments": [
{
"code": "HDR",
"tag": "header",
"minOccurances": 1,
"fields": [
{"tag": "code", "required": true},
{"tag": "orderId", "required": true},
{"tag": "organization"},
{"tag": "date"}
]
},
{
"code": "ITM",
"tag": "items",
"maxOccurances": -1,
"fields": [
{"tag": "code", "required": true},
{"tag": "item", "required": true},
{"tag": "quantity", "required": true, "dataType": "int"}
]
}
]
}

Prebuilt EDIFACT packages​

The following prebuilt EDIFACT D03A packages provide ready-made EDI schemas and Ballerina types for common business document standards. Each package has a root module exposing getEDINames(), hasEnvelope(), and message-name-dispatched conversion functions, plus one submodule per message type (for example mINVOIC) whose fromEdiString(), toEdiString(), headersFromEdiString(), interchangeFromEdiString(), and interchangeToEdiString() functions are typed to that message's records.

These packages are published under the ballerinax organization and can be imported directly into your integration project.

PackageDescription
ballerinax/edifact.d03a.financeFinancial messages — credit/debit advices, payment orders, invoices, ledger entries, and tax declarations.
ballerinax/edifact.d03a.logisticsLogistics messages — cargo summaries, transport instructions, booking confirmations, and dangerous goods notifications.
ballerinax/edifact.d03a.manufacturingManufacturing messages — metered services consumption, quality data, safety hazards, and waste disposal information.
ballerinax/edifact.d03a.retailRetail messages — product data, price history, rebate orders, retail account settlements, and product inquiries.
ballerinax/edifact.d03a.servicesService-industry messages — insurance, healthcare, job applications, berth management, and claims processing.
ballerinax/edifact.d03a.shippingShipping and customs messages — container operations, customs declarations, vessel departures, and cargo reports.
ballerinax/edifact.d03a.supplychainSupply chain messages — purchase orders, order responses, delivery forecasts, inventory reports, and despatch advices.

Prebuilt package usage​

fromEdiString() reads a single message. To walk a full interchange, call interchangeFromEdiString() — each transaction's body is typed Record|error, so one malformed message can be quarantined without failing the batch.

import ballerina/io;
import ballerinax/edifact.d03a.finance.mINVOIC;

public function main() returns error? {
string ediText = check io:fileReadString("resources/invoice.edi");
mINVOIC:EDI_INVOIC_INVOICInterchange interchange = check mINVOIC:interchangeFromEdiString(ediText);
foreach mINVOIC:EDI_INVOIC_INVOICTransaction txn in interchange.transactions {
mINVOIC:EDI_INVOIC_INVOIC|error invoice = txn.body;
if invoice is error {
io:println("quarantined: ", invoice.message());
continue;
}
// Process the typed invoice data
}
}