A Golang module to specify edifact document formats and to read from io.Reader
into user-defined data structures. Inspired by json.Unmarshal
and xml.Unmarshal
.
Install with go get
go get github.com/shogg/edifact
Document specifications are written as go
source code: Msg
defines a message, S
a segment and SG
a segment group (a loop). "UNA"
is a segment tag. C
and M
stand for conditional or mandatory. The last number specifies maximal repetitions.
The notation is derived from here (example): https://service.unece.org/trade/untdid/d96a/trmd/desadv_s.htm
import "github.com/shogg/edifact/spec"
var desadv = spec.Msg("DESADV",
spec.S("UNA", spec.C, 1),
spec.S("UNB", spec.C, 1),
spec.S("UNH", spec.M, 1),
spec.S("BGM", spec.M, 1),
spec.S("DTM", spec.C, 10),
spec.S("ALI", spec.C, 5),
spec.S("MEA", spec.C, 5),
spec.S("MOA", spec.C, 5),
spec.SG("SG1", spec.C, 10,
spec.S("RFF", spec.M, 1),
spec.S("DTM", spec.C, 1),
),
[..]
A specification must be registered. This can be done anywhere. For instance in the main function or in an init function:
func init() {
spec.Add(desadv)
}
Currently only struct
is supported (and array, slice and pointer of struct). The tag of a struct field specifies where data is located in an edifact document. All annotated information must match to make segment data suitable (segment groups, segment tag, fixed data in elements). A question mark ?
annotates the relevant data element separated by :
. The type of the target value decides how to parse. Simple data types are implemented, more complex ones need to implement edifact.Marshaller
. A star *
annotates a composite of elements, an edifact.Marshaller
is needed to parse. If no ?
or *
is present, the edifact.Marshaller
will receive the full segment with tag and terminator.
Arrays and slices in a struct can specify a segment group path like this edifact:"SG10/SG17"
. Each time a segment group repeats in the edifact data a new array/slice element is inserted.
type Message struct {
Date time.Time `edifact:"DTM+17|18"`
DeliveryNr string `edifact:"SG1/RFF+VN|DQ+?"`
OrderNr int `edifact:"SG1/RFF+ON+?"`
Items []Item `edifact:"SG10/SG17"`
}
type Item struct {
ItemNr int `edifact:"SG10/SG17/LIN+?"`
Description string `edifact:"SG10/SG17/LIN+++?"`
Quantity int `edifact:"SG10/SG17/QTY+12:?"`
}
Out of the box only simple data types (string
, int
, bool
... ) and time.Time
are parseable. A user defined type can implement edifact.Unmarshaller
to provide its own parsing.
// Unmarshaller interface for custom data type parsing.
type Unmarshaller interface {
UnmarshalEdifact(data []byte) error
}
The top level API is edifact.Unmarshal
. You provide an io.Reader
and a pointer to your data structure to be filled. Here is a complete example. It uses the built-in document specification "DESADV"
so no doc spec here:
package main
import (
"fmt"
"strings"
"time"
"github.com/shogg/edifact"
)
var ediMessage = `
UNA:+.? '
UNB+UNOC:3+sender+receiver+060620:0931+1++1234567'
UNH+1+DESADV:D:96A:UN'
BGM+220+B10001'
DTM+17:20060620:102'
RFF+ON+1'
RFF+DQ+2'
NAD+BY+++name+street+city++23436+xx'
CPS+'
LIN+1++product A:SA'
QTY+12:10'
LIN+2++product B:SA'
QTY+12:20'
CNT+2:1'
UNT+9+1'`
type Message struct {
Date time.Time `edifact:"DTM+17|18"`
DeliveryNr string `edifact:"SG1/RFF+VN|DQ+?"`
OrderNr int `edifact:"SG1/RFF+ON+?"`
Items []Item `edifact:"SG10/SG17"`
}
type Item struct {
ItemNr int `edifact:"SG10/SG17/LIN+?"`
Description string `edifact:"SG10/SG17/LIN+++?"`
Quantity int `edifact:"SG10/SG17/QTY+12:?"`
}
func main() {
document := strings.NewReader(ediMessage)
var messages []*Message
if err := edifact.Unmarshal(document, &messages); err != nil {
panic(err)
}
fmt.Println("number of messages", len(messages))
}
- meta characters are fixed to
UNA:+.? '
- UNA, UNB don't get evaluated
time.Time
can only parse DTM formats 101, 102, 201-204- only ORDERS and DESADV are included atm but you can register your own specs
- multiple message format versions are not supported atm
- max. segment repetitions is part of the spec but is ignored atm
Contributions are welcome.