serde-er7
Serde support for er7, the
pipe-hat encoding that carries HL7 v2 messages between healthcare systems —
so a parsed message can flow through JSON, YAML, or any other Serde data
format, and come back out unchanged.
let text = "MSH|^~\\&|LAB|ACME|EHR|CLINIC|20260815120000||ORU^R01|MSG9|P|2.5\r\
PID|1||12345^^^ACME^MR||SMITH^JOHN^Q||19800101|M\r\
OBX|1|NM|2093-3^Cholesterol^LN||187|mg/dL|||||F";
let message = parse?;
let json = to_string?;
let back: Message = from_str?;
assert_eq!;
Contents
- Why this crate exists
- Install
- The shape each level serializes as
- What it does
- What it deliberately does not do
- Documentation
- Development
- License
Why this crate exists
er7 parses, queries, edits, and writes ER7 text with zero dependencies of
its own — a deliberate choice for code that sits at the bottom of a stack of
HL7 crates. That means er7::Message has no Serde support built in, and
adding it there would cost every user of er7 a dependency they may not
want.
This crate is the bridge instead: two dependencies, serde and er7, and
nothing else. Every Serialize/Deserialize impl is written by hand
against the low-level trait methods — the same pattern
serde's own documentation walks
through for a manual implementation — because the shapes below (a bare
array for a field's repetitions, a bare string for a leaf) are not what
#[derive(Serialize)] would produce on its own.
Install
The shape each level serializes as
| Level | Wrapper | Serializes as |
|---|---|---|
| Message | Message |
object: {"separators": ..., "segments": [...]} |
| Segment | Segment |
object: {"name": "PID", "fields": [...]} |
| Field | Field |
array of repetitions |
| Repetition | Repetition |
array of components |
| Component | Component |
array of subcomponent strings |
| Subcomponent | Subcomponent |
a bare string, raw (escape sequences intact, not decoded) |
| Separators | Separators |
object of six named fields, chars as one-character strings |
| Terminator | Terminator |
one of the strings "Cr", "Lf", "CrLf" |
So PID-5.1 (SMITH) is the bare JSON string "SMITH", PID-5
(SMITH^JOHN) is ["SMITH", "JOHN"], and a repeating field such as
555-1111~555-2222 is [["555-1111"], ["555-2222"]] — one array level per
level of the tree, all the way up to the two objects at the top:
Segment
and
Message
itself.
What it does
- Every level of the tree:
Message,Segment,Field,Repetition,Component,Subcomponent, plusSeparatorsandTerminator— a Serde-enabled wrapper for every public typeer7exposes. - Format-agnostic: nothing in this crate mentions JSON, YAML, or any
other format by name.
serde_jsonappears only as a dev-dependency, to test and demonstrate against. - Round trip, the same guarantee
er7makes: every subcomponent serializes as itsrawtext, escape sequences included, not decoded — soMessage::parse(text)?through any Serde format and back out through.to_er7()reproduces the same byteser7::parse(text)?.to_er7()would. - Absent, empty, and null stay distinct: a field that was never sent, a
field sent as
||, and a field holding the explicit""null serialize and deserialize as three different values, never collapsed into one. - Ergonomic wrappers, not just trait impls: every wrapper implements
Deref/DerefMutto itser7type, plusFromconversions both ways, somessage.query(...),message.segments, and the rest ofer7's API work directly on aMessagewithout unwrapping it first.
What it deliberately does not do
This crate adds exactly one thing to er7: Serde support for its existing
value tree. Like er7 itself, it is an encoding bridge, not a dictionary —
it does not know which fields a segment should have, what data type each
carries, or what any code table means. It does not validate, and it does
not pick a wire format for you: that choice — serde_json, serde_yaml,
anything else — is the caller's, every time.
For the HL7 v2.5 dictionary layer, see the sibling crates
hl7-2-5-to-xml-using-rust
and
hl7-2-5-to-json-using-rust.
Documentation
| Where | What |
|---|---|
docs/usage/ |
tutorial: parsing, JSON in both directions, and the tree shapes |
docs/api/ |
the complete public API surface |
docs/faq/ |
frequently asked questions |
examples/ |
runnable programs, one concept each |
spec/ |
the normative specification — source of truth for behaviour |
AGENTS.md |
conventions and required checks for anyone, human or agent, changing this code |
Rendered API docs are at https://docs.rs/serde-er7/, or locally with
cargo doc --no-deps --open.
Development
Behavioural changes start in spec/, not in the code —
see AGENTS/spec-driven-development.md.
License
Multi-licensed, so a downstream project can pick whichever fits: MIT, Apache-2.0, BSD-3-Clause, GPL-2.0-only, or GPL-3.0-only. See LICENSE.md.