Subproject · Tool
serde-er7
Serde support for the ER7 message tree — so a parsed message can flow through JSON, YAML, or any other Serde format, and come back out unchanged.
What it is
Once a message is an er7::Message, you often want to hand it to something that
only speaks Serde: a document database driver, a web framework's JSON response type, a
structured logger, a snapshot-testing library. serde-er7 gives every er7 type a hand-written Serialize and Deserialize implementation, and that is the whole feature surface.
It is a separate crate because er7 guarantees zero dependencies — adding Serde there would impose it on every
consumer, including the ones that never touch a data format. Here the choice is the
caller's: two dependencies, serde and er7, and nothing else.
Install
cargo add serde-er7 Plus whichever Serde format you actually use — serde_json, serde_yaml, a binary format. This crate never names one: it is format-agnostic on purpose, and JSON
appears below only because it is the easiest to read on a page.
Tutorial A message, out to JSON and back
1. Parse into the wrapper type
use serde_er7::Message;
let text = "MSH|^~\\&|LAB|ACME\r\
PID|1||444333222^^^ACME^MR||EVERYWOMAN^EVE^E";
let message = Message::parse(text)?; Message::parse is a thin wrapper over er7::parse and returns the
same er7::Error, so everything you already know about handling it applies
unchanged.
2. Serialize it
let json = serde_json::to_string_pretty(&message)?; {
"separators": {
"field": "|",
"component": "^",
"repetition": "~",
"escape": "\\",
"subcomponent": "&",
"truncation": null
},
"segments": [
{ "name": "MSH", "fields": [[[["|"]]], [[["^~\\&"]]], [[["LAB"]]], [[["ACME"]]]] },
{
"name": "PID",
"fields": [
[[["1"]]],
[],
[[["444333222"], [""], [""], ["ACME"], ["MR"]]],
[],
[[["EVERYWOMAN"], ["EVE"], ["E"]]]
]
}
]
} Positions, not names. One array level per level of the ER7 tree — field, repetition,
component, subcomponent — so [[["1"]]] is a field holding one repetition, one
component, one subcomponent. PID-2 is [] because it was sent empty,
which is how “no value” stays distinct from a value that is blank.
3. Deserialize, and write ER7 back out
let back: Message = serde_json::from_str(&json)?;
assert_eq!(back.to_er7(), text); // byte for byte The round trip holds because a subcomponent serializes its raw text, exactly
as the sender wrote it, escape sequences included — never the decoded form. Decoding is lossy:
a formatting escape such as \.br\ has no plain-text form to decode back to, so a
wire format built on decoded values could not be turned back into the original ER7.
Note the Deref: back.to_er7() and back.query("PID-5.1") are er7's own methods, reached straight
through the wrapper. There is no unwrapping step, and no parallel API to learn.
4. Or start from JSON
The direction most tutorials skip: you have JSON — from a web form, a document store, a test fixture — and a legacy receiver that needs ER7.
let message: serde_er7::Message = serde_json::from_str(json)?;
assert_eq!(message.query("PID-5.1")?.as_deref(), Some("SMITH"));
println!("{}", message.to_er7());Help Reference
The shape each level serializes as
| Level | Shape | Example |
|---|---|---|
Message | object, fields "separators" and "segments" | {"separators": {…}, "segments": […]} |
Segment | object, fields "name" and "fields" | {"name": "PID", "fields": […]} |
Field | array of repetitions | [["555-1111"], ["555-2222"]] |
Repetition | array of components | [["SMITH"], ["JOHN"]] |
Component | array of subcomponent strings | ["ACME", "1.2.3", "ISO"] |
Subcomponent | a bare string, exactly as sent | "SMITH" |
Separators | object, six named character fields | {"field": "|", "component": "^", …} |
Terminator | one of three strings | "Cr", "Lf", "CrLf" |
This table is a compatibility surface: a change to any shape in it is a breaking change, because somebody's stored documents are written that way.
Three choices in it are worth the explanation:
- Bare arrays below a segment. A field, a repetition, and a component each hold exactly one thing — the list of the level below — so an object wrapper would add a key carrying no information, at every one of the many nodes at that level.
- Objects for message and segment. Each carries two different kinds of information, which an array position alone would not distinguish without an undocumented convention about which index means what.
- Delimiters as six named characters, not the packed
^~\&string. A reader can identify each one by name, and every format's owncharhandling stays in play.
Deserializing
An unknown field in an object is ignored rather than rejected, so a document
that carries extra keys still loads. A missing required field is an error naming that field.
Anything that parses is accepted — the same posture er7 takes below its header.
Examples
Three runnable programs in the crate's examples/ directory, each asserting its own results.
| Example | Shows |
|---|---|
round_trip_via_json | Parse ER7, serialize to JSON, deserialize, and write the original text back out. |
build_message_from_json | The direction most tutorials skip: JSON in, ER7 text out. |
inspect_a_segment_as_json | Serializing one segment, to see the shape each level chooses for itself. |
cargo run --example round_trip_via_jsonWhat it does not do
- It is not a dictionary. The JSON above is positional, exactly like the ER7
it came from. For
"PID.5": {"XPN.1": …}— names rather than indices — you want a dictionary crate; see the ecosystem, where the two outputs sit side by side. - It names no format. JSON, YAML, and the binary formats are all just Serde implementations; none of them appears in this crate's dependencies or public API.
- It is not a validator. Structurally malformed Serde input is rejected by
Deserialize; anything that parses is accepted. - It is not a parallel API. Every wrapper
Derefs to itser7type, so this crate adds a capability rather than a second surface.