indice-co / EDI.Net

EDI Serializer/Deserializer. Supports EDIFact, X12 and TRADACOMS formats
MIT License
455 stars 171 forks source link

How to deal with possibility of SegmentGroup absence in the EDI file? #216

Open Qtemp123 opened 2 years ago

Qtemp123 commented 2 years ago

Hello :),

I am currently developing a solution for deserialization of IFCSUM D01B message using EDI.Net. The problem that I am facing has to do with the way that the library is handling SegmentGroups, which I probably just don't understand.

Given this sample D01B IFCSUM message:

UNA:+.? '
UNB+UNOY:3+Sample+Sample+211217:1234'
UNH+1+IFCSUM:D:01B:UN'
BGM+Sample+Sample+Sample'
DTM+Sample:Sample:Sample'
FTX+Sample+++Sample'
CNT+Sample:Sample'
CNT+Sample:Sample'
CNT+Sample:Sample'
CNT+Sample:Sample'
RFF+Sample:Sample'
RFF+Sample:Sample'
RFF+Sample:Sample'
NAD+Sample+Sample::Sample'
NAD+Sample+Sample'
TDT+Sample++Sample+Sample:Sample+Sample:Sample'
DTM+Sample:Sample:Sample'
DTM+Sample:Sample:Sample'
TSR+Sample+Sample'
TSR+Sample+Sample'
TSR+Sample+Sample'
CNI+Sample+Sample'
TPL+Sample'
MEA+Sample++Sample:Sample'
DTM+Sample:Sample:Sample'
DTM+Sample:Sample:Sample'
DTM+Sample:Sample:Sample'
DTM+Sample:Sample:Sample'
TSR+Sample+Sample'
TSR+Sample+Sample'
TSR+Sample+Sample'
TOD+Sample++Sample'
RFF+Sample:Sample'
RFF+Sample:Sample'
RFF+Sample:Sample'
RFF+Sample:Sample'
TDT+Sample++Sample'
NAD+Sample+Sample++Sample:Sample+Sample+Sample++Sample+Sample'
NAD+Sample+Sample++Sample:Sample+Sample+Sample++Sample+Sample'
NAD+Sample+Sample++Sample:Sample+Sample+Sample++Sample+Sample'
RFF+Sample:Sample'
NAD+Sample+Sample++Sample:Sample+Sample+Sample++Sample+Sample'
RFF+Sample:Sample'
NAD+Sample+Sample++Sample:Sample+Sample+Sample++Sample+Sample'
RFF+Sample:Sample'
GID+Sample+Sample:Sample:::Sample'
HAN+Sample:::Sample'
FTX+Sample+++Sample'
MEA+Sample+Sample+Sample:Sample'
MEA+Sample++Sample:Sample'
DIM+Sample+Sample:Sample:Sample:Sample'
RFF+Sample:Sample'
PCI+Sample+Sample'
UNT+52+1'
UNZ+1+1234'

Everything is working fine when I use SequenceEnd in the SegmentGroup attribute, and segment defined as the SequenceEnd acutally exists. Below are a few examples of my implementation of the message as of right now (passing child segments in the SegmentGroup attribute constructor):

public class D01BIFCSUM
{
    #region Interchange Header

    // S001/0001
    [EdiValue(Path = "UNB/0")]
    public string UNB_SyntaxIdentifier { get; set; }

    // S001/0002
    [EdiValue(Path = "UNB/0/1")]
    public string UNB_SyntaxVersionNumber { get; set; }

    // S002/0004
    [EdiValue(Path = "UNB/1")]
    public string UNB_InterchangeSenderIdentification { get; set; }

    // S003/0010
    [EdiValue(Path = "UNB/2")]
    public string UNB_InterchangeRecipient { get; set; }

    // S004/0017
    [EdiValue(Path = "UNB/3/0")]
    public string UNB_Date { get; set; }

    // S004/0019
    [EdiValue(Path = "UNB/3/1")]
    public string UNB_Time { get; set; }

    // 050/0020
    [EdiValue(Path = "UNB/4")]
    public string UNB_InterchangeControlReference { get; set; }

    #endregion Interchange Header

    // D01B IFCSUM
    public D01B_IFCSUM_MESSAGE Message { get; set; }

    #region Interchange Trailer

    // 0036
    [EdiValue(Path = "UNZ/0")]
    public int UNZ_InterchageControlCount { get; set; }

    // 0020
    [EdiValue(Path = "UNZ/1")]
    public string UNZ_InterchangeControlReference { get; set; }

    #endregion Interchange Trailer
}
[EdiMessage]
public class D01B_IFCSUM_MESSAGE
{
    #region Message Header Info

    // 010/0062 
    [EdiValue(Path = "UNH/0")]
    public string UNH_MessageReferenceNumber { get; set; }

    // S009/0065
    [EdiValue(Path = "UNH/1/0")]
    public string UNH_MessageType { get; set; }

    // S009/0052
    [EdiValue(Path = "UNH/1/1")]
    public string UNH_MessageVersionNumber { get; set; }

    // S009/0054
    [EdiValue(Path = "UNH/1/2")]
    public string UNH_MessageReleaseNumber { get; set; }

    // S009/0051
    [EdiValue(Path = "UNH/1/3")]
    public string UNH_ControllingAgency { get; set; }

    #endregion Message Header Info

    // x1
    public D01B_BGM_SEGMENT BGM { get; set; }

    // x0-9
    public D01B_DTM_SEGMENT[] DTM { get; set; }

    // x0-99
    public D01B_MOA_SEGMENT[] MOA { get; set; }

    // x0-99
    public D01B_FTX_SEGMENT[] FTX { get; set; }

    // x0-9
    public D01B_CNT_SEGMENT[] CNT { get; set; }

    // x0-9
    public D01B_RFF_SEGMENTGROUP1[] RFF_SEGMENT_GROUP_1 { get; set; }

    // x0-9
    public D01B_NAD_SEGMENTGROUP4[] NAD_SEGMENT_GROUP_4 { get; set; }

    // x0-9
    public D01B_TDT_SEGMENTGROUP9[] TDT_SEGMENT_GROUP_9 { get; set; }

    // x0-9999
    public D01B_EQD_SEGMENTGROUP21[] EQD_SEGMENT_GROUP_21 { get; set; }

    // x0-9999
    public D01B_CNI_SEGMENTGROUP25[] CNI_SEGMENT_GROUP_25 { get; set; }

    // 0074
    [EdiValue(Path = "UNT/0")]
    public string UNT_NumberOfSegments { get; set; }

    // 0062
    [EdiValue(Path = "UNT/1")]
    public string UNT_MessageReferenceNumber { get; set; }
}
[EdiSegment, EdiPath("BGM")]
public class D01B_BGM_SEGMENT
{
    // C002/1001
    [EdiValue(Path = "BGM/0")]
    public string BGM_MessageName { get; set; }

    // C106/1004
    [EdiValue(Path = "BGM/1")]
    public string BGM_MessageNumber { get; set; }

    // 030/1225
    [EdiValue(Path = "BGM/2")]
    public string BGM_MessageFunction { get; set; }
}
[EdiSegmentGroup("CNI", "TPL", "DTM", "TSR", "CUX", "MOA", "FTX", "GDS", "TOD", "RFF", "TDT", "NAD", "GID")]
public class D01B_CNI_SEGMENTGROUP25
{
    // 010/1490
    [EdiValue(Path = "CNI/0")]
    public string CNI_ConsolidationItemNumber { get; set; }

    // C503/1004
    [EdiValue(Path = "CNI/1")]
    public string CNI_DocumentIdentifier { get; set; }

    public D01B_TPL_SEGMENTGROUP28[] TPL { get; set; }

    public D01B_DTM_SEGMENT[] DTM { get; set; }

    public D01B_TSR_SEGMENT[] TSR { get; set; }

    public D01B_CUX_SEGMENT[] CUX { get; set; }

    public D01B_MOA_SEGMENT[] MOA { get; set; }

    public D01B_FTX_SEGMENT[] FTX { get; set; }

    public D01B_GDS_SEGMENT[] GDS { get; set; }

    public D01B_TOD_SEGMENTGROUP31[] TOD { get; set; }

    public D01B_RFF_SEGMENTGROUP32[] RFF { get; set; }

    public D01B_TDT_SEGMENTGROUP38[] TDT { get; set; }

    public D01B_NAD_SEGMENTGROUP43[] NAD { get; set; }

    public D01B_GID_SEGMENTGROUP50[] GID { get; set; }
}

I don't want to paste the whole code base for this because it would be massive, but I hope the picture of the situation is clear enough. Every other EdiSegment is nested like BGM in this example, and every other SegmentGroup is also nested like in this example, according to the structure of the EDI file above. When I assume that, for example, RFF SG1's SequenceEnd is "NAD" (NAD SG4), or NAD SG4's SequenceEnd is TDT SG9 etc. (even in far more nested SG's like CNI or GID it works perfectly fine) it's all good.

So after all this, what's the actual problem? I am in a situation when I can't be always sure what my EDI file will include, and there are some SegmentGroups that will not always be present, let's look at the example:

UNA:+.? '
UNB+UNOY:3+Sample+Sample+211217:1234'
UNH+1+IFCSUM:D:01B:UN'
BGM+Sample+Sample+Sample'
DTM+Sample:Sample:Sample'
FTX+Sample+++Sample'
CNT+Sample:Sample'
CNT+Sample:Sample'
CNT+Sample:Sample'
CNT+Sample:Sample'
RFF+Sample:Sample'
RFF+Sample:Sample'
RFF+Sample:Sample'
NAD+Sample+Sample::Sample'
NAD+Sample+Sample'
TDT+Sample++Sample+Sample:Sample+Sample:Sample'
DTM+Sample:Sample:Sample'
DTM+Sample:Sample:Sample'
TSR+Sample+Sample'
TSR+Sample+Sample'
TSR+Sample+Sample'
EQD+Sample+Sample'
CNI+Sample+Sample'
TPL+Sample'
MEA+Sample++Sample:Sample'
DTM+Sample:Sample:Sample'
DTM+Sample:Sample:Sample'
DTM+Sample:Sample:Sample'
DTM+Sample:Sample:Sample'
TSR+Sample+Sample'
TSR+Sample+Sample'
TSR+Sample+Sample'
TOD+Sample++Sample'
RFF+Sample:Sample'
RFF+Sample:Sample'
RFF+Sample:Sample'
RFF+Sample:Sample'
TDT+Sample++Sample'
NAD+Sample+Sample++Sample:Sample+Sample+Sample++Sample+Sample'
NAD+Sample+Sample++Sample:Sample+Sample+Sample++Sample+Sample'
NAD+Sample+Sample++Sample:Sample+Sample+Sample++Sample+Sample'
RFF+Sample:Sample'
NAD+Sample+Sample++Sample:Sample+Sample+Sample++Sample+Sample'
RFF+Sample:Sample'
NAD+Sample+Sample++Sample:Sample+Sample+Sample++Sample+Sample'
RFF+Sample:Sample'
GID+Sample+Sample:Sample:::Sample'
HAN+Sample:::Sample'
FTX+Sample+++Sample'
MEA+Sample+Sample+Sample:Sample'
MEA+Sample++Sample:Sample'
DIM+Sample+Sample:Sample:Sample:Sample'
RFF+Sample:Sample'
PCI+Sample+Sample'
UNT+53+1'
UNZ+1+1234'

The only difference between two provided EDIFACT's is the EQD SegmentGroup, after TDT SG9, it's there sometimes, sometimes it is not ;). So when I (wrongly) assume that TDT SG9's SequenceEnd is CNI, it fails when there is EQD segment present in the file, or when I assign EQD as SequenceEnd of TDT, and EQD is not present (ok otherwise). That's it, the only problem. I have looked at a lot of issues posted here, where providing child segments and SegmentGroups in the constructor of SegmentGroups would be the way to go, but then Deserialization fails, for example, the RFF SG1 which is defined in the EdiMessage attributed class collects all 10 RFF segments from the whole file, instead of 3 which are actually there in the main message context of EDIFACT.

I suspect that the solution for this problem is there, but I just can't quite find it, so I would very much appreciate some help with this examples. If needed, I will provide more implementation examples from the D01BIFCSUM class, and thanks in advance :).

Qtemp123 commented 2 years ago

I have debugged the EdiSerializer class and this is what I have found (reference documentation: https://www.stylusstudio.com/edifact/D01B/IFCSUM.htm):

When I enter the MEA SegmentGroup 29 (inside TPL SG28) in the outer CNI SegmentGroup 25 which is contained in the class with [EdiMessage] attribute it closed the CNI SG25 on one of the conditions inside TryCreateContainer method,

else if (level.GroupMembers.Length > 1 &&!level.GroupContains(readerSegment as string)) 
{
    level.Close(); // Close this level
    continue;
}

as CNI SG 25 didn't contain the possibility to have "MEA" segment inside it's SegmentGroup attribute parameters. After I changed it (added all possible segments from all the nested SegmentGroups inside CNI) it seems to work perfectly fine now, but my question is, is this actually a proper solution? Based on all issue suggestions that I have read, I should only be including Segments and SegmentsGroups that may exist on the same level, but it works with all nested Segments and SegmentGroups also added to the attribute in CNI.

The definition of CNI currently looks like this:

[EdiSegmentGroup("CNI", "TPL", "DTM", "TSR", "CUX", "MOA", "FTX", "GDS", "TOD", "RFF", "TDT", "NAD", "GID", "MEA", "CTA", "HAN", "LOC", "MOA", "PIA", "DIM", "PCI", "SGP", "DGS")]
public class D01B_CNI_SEGMENTGROUP25
{
    // 010/1490
    [EdiValue(Path = "CNI/0")]
    public string CNI_ConsolidationItemNumber { get; set; }

    // C503/1004
    [EdiValue(Path = "CNI/1")]
    public string CNI_DocumentIdentifier { get; set; }

    public D01B_TPL_SEGMENTGROUP28[] TPL { get; set; }

    public D01B_DTM_SEGMENT[] DTM { get; set; }

    public D01B_TSR_SEGMENT[] TSR { get; set; }

    public D01B_CUX_SEGMENT[] CUX { get; set; }

    public D01B_MOA_SEGMENT[] MOA { get; set; }

    public D01B_FTX_SEGMENT[] FTX { get; set; }

    public D01B_GDS_SEGMENT[] GDS { get; set; }

    public D01B_TOD_SEGMENTGROUP31[] TOD { get; set; }

    public D01B_RFF_SEGMENTGROUP32[] RFF { get; set; }

    public D01B_TDT_SEGMENTGROUP38[] TDT { get; set; }

    public D01B_NAD_SEGMENTGROUP43[] NAD { get; set; }

    public D01B_GID_SEGMENTGROUP50[] GID { get; set; }
}

Compared to previous:

[EdiSegmentGroup("CNI", "TPL", "DTM", "TSR", "CUX", "MOA", "FTX", "GDS", "TOD", "RFF", "TDT", "NAD", "GID")]
public class D01B_CNI_SEGMENTGROUP25
{
    // 010/1490
    [EdiValue(Path = "CNI/0")]
    public string CNI_ConsolidationItemNumber { get; set; }

    // C503/1004
    [EdiValue(Path = "CNI/1")]
    public string CNI_DocumentIdentifier { get; set; }

    public D01B_TPL_SEGMENTGROUP28[] TPL { get; set; }

    public D01B_DTM_SEGMENT[] DTM { get; set; }

    public D01B_TSR_SEGMENT[] TSR { get; set; }

    public D01B_CUX_SEGMENT[] CUX { get; set; }

    public D01B_MOA_SEGMENT[] MOA { get; set; }

    public D01B_FTX_SEGMENT[] FTX { get; set; }

    public D01B_GDS_SEGMENT[] GDS { get; set; }

    public D01B_TOD_SEGMENTGROUP31[] TOD { get; set; }

    public D01B_RFF_SEGMENTGROUP32[] RFF { get; set; }

    public D01B_TDT_SEGMENTGROUP38[] TDT { get; set; }

    public D01B_NAD_SEGMENTGROUP43[] NAD { get; set; }

    public D01B_GID_SEGMENTGROUP50[] GID { get; set; }
}
cleftheris commented 2 years ago

Hi @Qtemp123 and thank you for your interest in EDI.Net

question is, is this actually a proper solution? Based on all issue suggestions that I have read, I should only be including Segments and SegmentsGroups that may exist on the same level, but it works with all nested Segments and SegmentGroups also added to the attribute in CNI.

Usually this (mapping everything contained and not only same level) is useful only when developing or when some nested GegmentGroups are not present in the CLR classes (Mapped at all). I have not yet found the time to debug your case but if you would like it would be helpful to make a pull request with a failing test so that I can take a look.

Also generally speaking the sequenceEnd is discouraged and only works in the simplest of scenarios.