EDI Flat File parsing with C#?

Learn edi flat file parsing with c#? with practical examples, diagrams, and best practices. Covers c#, parsing, flat-file development techniques with visual explanations.

Parsing EDI Flat Files with C#: A Comprehensive Guide

Hero image for EDI Flat File parsing with C#?

Learn how to effectively parse Electronic Data Interchange (EDI) flat files, specifically X12, using C# for robust data processing and integration.

Electronic Data Interchange (EDI) is a standardized method for exchanging business documents between computer systems. While modern systems often use XML or JSON, many legacy systems, especially in supply chain and healthcare, still rely on flat files for EDI transactions. These files, particularly those conforming to the X12 standard, present unique parsing challenges due to their delimited, hierarchical, and often cryptic structure. This article will guide you through the process of parsing EDI flat files using C#, focusing on practical approaches and common pitfalls.

Understanding EDI Flat File Structure (X12)

Before diving into code, it's crucial to understand the structure of an EDI flat file. X12 files are typically composed of segments, elements, and sub-elements, separated by specific delimiters. A common X12 file uses a segment terminator (often a newline character \n or ~), an element separator (often *), and a sub-element separator (often :) to define its structure. Each segment has a unique identifier (e.g., ISA, GS, ST, BGM, N1, IT1, SE, GE, IEA).

flowchart TD
    A[EDI Flat File] --> B{Read Line by Line}
    B --> C{Identify Segment Terminator}
    C --> D[Split into Segments]
    D --> E{Process Each Segment}
    E --> F{Identify Element Separator}
    F --> G[Split into Elements]
    G --> H{Process Each Element}
    H --> I{Identify Sub-Element Separator}
    I --> J[Split into Sub-Elements]
    J --> K[Map to Data Model]
    K --> L[Structured Data]

General EDI Flat File Parsing Workflow

Defining Your EDI Data Model

To effectively parse an EDI file, you need a corresponding data model in C#. This model will represent the hierarchical structure of the EDI document. You can define classes for the entire transaction set, individual segments, and even elements if they require complex parsing or validation. For X12, you'll typically have a top-level class for the transaction (e.g., X12_850_PurchaseOrder), containing lists of segment classes (e.g., BGM_BeginningSegment, N1_PartyIdentification).

public class X12_850_PurchaseOrder
{
    public ISA_InterchangeControlHeader ISA { get; set; }
    public GS_FunctionalGroupHeader GS { get; set; }
    public ST_TransactionSetHeader ST { get; set; }
    public BGM_BeginningSegment BGM { get; set; }
    public List<N1_PartyIdentification> N1Segments { get; set; } = new List<N1_PartyIdentification>();
    public List<IT1_LineItem> IT1Segments { get; set; } = new List<IT1_LineItem>();
    // ... other segments
    public SE_TransactionSetTrailer SE { get; set; }
    public GE_FunctionalGroupTrailer GE { get; set; }
    public IEA_InterchangeControlTrailer IEA { get; set; }
}

public class BGM_BeginningSegment
{
    public string DocumentTypeCode { get; set; }
    public string PurchaseOrderNumber { get; set; }
    public string ReleaseNumber { get; set; }
    // ... other elements
}

public class N1_PartyIdentification
{
    public string EntityIdentifierCode { get; set; }
    public string Name { get; set; }
    public string IdentificationCodeQualifier { get; set; }
    public string IdentificationCode { get; set; }
}

Implementing the Parsing Logic in C#

The core parsing logic involves reading the file line by line, identifying segments, and then breaking down each segment into its constituent elements and sub-elements. You'll need to handle the various delimiters and potentially different segment structures based on the EDI standard and transaction type (e.g., 850 Purchase Order, 810 Invoice). A common approach is to use a StreamReader to read the file and then string manipulation methods like Split().

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;

public class EdiParser
{
    private char _segmentTerminator = '~';
    private char _elementSeparator = '*';
    private char _subElementSeparator = ':';

    public X12_850_PurchaseOrder Parse850(string ediFilePath)
    {
        var purchaseOrder = new X12_850_PurchaseOrder();
        string[] segments = File.ReadAllText(ediFilePath)
                                .Split(new char[] { _segmentTerminator }, StringSplitOptions.RemoveEmptyEntries);

        foreach (var segmentLine in segments)
        {
            string[] elements = segmentLine.Split(_elementSeparator);
            string segmentId = elements[0];

            switch (segmentId)
            {
                case "ISA":
                    purchaseOrder.ISA = ParseIsaSegment(elements);
                    break;
                case "GS":
                    purchaseOrder.GS = ParseGsSegment(elements);
                    break;
                case "ST":
                    purchaseOrder.ST = ParseStSegment(elements);
                    break;
                case "BGM":
                    purchaseOrder.BGM = ParseBgmSegment(elements);
                    break;
                case "N1":
                    purchaseOrder.N1Segments.Add(ParseN1Segment(elements));
                    break;
                case "IT1":
                    purchaseOrder.IT1Segments.Add(ParseIt1Segment(elements));
                    break;
                case "SE":
                    purchaseOrder.SE = ParseSeSegment(elements);
                    break;
                case "GE":
                    purchaseOrder.GE = ParseGeSegment(elements);
                    break;
                case "IEA":
                    purchaseOrder.IEA = ParseIeaSegment(elements);
                    break;
                default:
                    Console.WriteLine($"Unknown segment: {segmentId}");
                    break;
            }
        }
        return purchaseOrder;
    }

    private ISA_InterchangeControlHeader ParseIsaSegment(string[] elements)
    {
        // Example: ISA*00*          *00*          *ZZ*SENDERID       *ZZ*RECEIVERID     *200101*1200*U*00401*000000001*0*P*>~
        return new ISA_InterchangeControlHeader
        {
            AuthorizationInformationQualifier = elements.Length > 1 ? elements[1] : null,
            AuthorizationInformation = elements.Length > 2 ? elements[2] : null,
            // ... map other elements
        };
    }

    private BGM_BeginningSegment ParseBgmSegment(string[] elements)
    {
        return new BGM_BeginningSegment
        {
            DocumentTypeCode = elements.Length > 1 ? elements[1] : null,
            PurchaseOrderNumber = elements.Length > 2 ? elements[2] : null,
            ReleaseNumber = elements.Length > 3 ? elements[3] : null
        };
    }

    private N1_PartyIdentification ParseN1Segment(string[] elements)
    {
        return new N1_PartyIdentification
        {
            EntityIdentifierCode = elements.Length > 1 ? elements[1] : null,
            Name = elements.Length > 2 ? elements[2] : null,
            IdentificationCodeQualifier = elements.Length > 3 ? elements[3] : null,
            IdentificationCode = elements.Length > 4 ? elements[4] : null
        };
    }

    private IT1_LineItem ParseIt1Segment(string[] elements)
    {
        // Implement parsing for IT1 segment
        return new IT1_LineItem();
    }

    // Implement other ParseXxxSegment methods similarly
    private GS_FunctionalGroupHeader ParseGsSegment(string[] elements) => new GS_FunctionalGroupHeader();
    private ST_TransactionSetHeader ParseStSegment(string[] elements) => new ST_TransactionSetHeader();
    private SE_TransactionSetTrailer ParseSeSegment(string[] elements) => new SE_TransactionSetTrailer();
    private GE_FunctionalGroupTrailer ParseGeSegment(string[] elements) => new GE_FunctionalGroupTrailer();
    private IEA_InterchangeControlTrailer ParseIeaSegment(string[] elements) => new IEA_InterchangeControlTrailer();
}

// Placeholder classes for demonstration
public class ISA_InterchangeControlHeader { public string AuthorizationInformationQualifier { get; set; } public string AuthorizationInformation { get; set; } /* ... */ }
public class GS_FunctionalGroupHeader { /* ... */ }
public class ST_TransactionSetHeader { /* ... */ }
public class SE_TransactionSetTrailer { /* ... */ }
public class GE_FunctionalGroupTrailer { /* ... */ }
public class IEA_InterchangeControlTrailer { /* ... */ }
public class IT1_LineItem { /* ... */ }

Handling Delimiters and Configuration

EDI files can use different delimiters. The ISA segment (Interchange Control Header) often contains the actual delimiters used in the file. Specifically, the 16th character of the ISA segment (ISA16) defines the sub-element separator, and the character immediately following the ISA segment is typically the segment terminator. The element separator is usually the character after the first element of the ISA segment (ISA01). Your parser should be flexible enough to read these delimiters dynamically.

public class DynamicEdiParser
{
    private char _segmentTerminator;
    private char _elementSeparator;
    private char _subElementSeparator;

    public X12_850_PurchaseOrder Parse850Dynamic(string ediContent)
    {
        // Read the first line to determine delimiters
        string firstLine = ediContent.Split(new[] { '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries).FirstOrDefault();
        if (string.IsNullOrEmpty(firstLine) || !firstLine.StartsWith("ISA"))
        {
            throw new InvalidOperationException("Invalid EDI file format: ISA segment not found or file is empty.");
        }

        // ISA*00*          *00*          *ZZ*SENDERID       *ZZ*RECEIVERID     *200101*1200*U*00401*000000001*0*P*>~
        // The character after ISA[102] (ISA16) is the sub-element separator
        // The character after ISA[3] (ISA01) is the element separator
        // The character after the entire ISA segment is the segment terminator

        _elementSeparator = firstLine[3]; // Character after ISA01
        _subElementSeparator = firstLine[104]; // Character after ISA16
        _segmentTerminator = firstLine.Last(); // Last character of the ISA segment line

        // Now proceed with parsing using the identified delimiters
        var purchaseOrder = new X12_850_PurchaseOrder();
        string[] segments = ediContent.Split(new char[] { _segmentTerminator }, StringSplitOptions.RemoveEmptyEntries);

        foreach (var segmentLine in segments)
        {
            if (string.IsNullOrWhiteSpace(segmentLine)) continue;
            string[] elements = segmentLine.Split(_elementSeparator);
            string segmentId = elements[0];

            // ... (rest of the parsing logic similar to the previous example)
            // Make sure to handle sub-elements if present, e.g., elements[i].Split(_subElementSeparator)
        }
        return purchaseOrder;
    }
}