Crates
serde-hl7-v3
Serde for hl7-3: the envelope, the RIM, the data types, the elements
What it is
A Serde wrapper for every public `hl7-3` type — the three-level envelope, the six RIM backbone classes, the data types, and the raw XML element tree beneath them — with keys that are HL7 v3's own attribute and element names. Every value round-trips to an equal value; `Strict<T>` catches an unknown key at any depth. Two dependencies, `serde` and `hl7-3`.
cargo add serde-hl7-v3HL7 v3's own serialization is XML, and hl7-3 deliberately reads it without writing
it back — so once an interaction is decoded, the value has no way out except code you write.
This crate is that way out, for any Serde format. Most callers reach it as serde_hl7::v3 through serde-hl7.
The message
use serde_hl7_v3::Message;
let message = Message::parse(xml)?;
let json = serde_json::to_string_pretty(&message)?;
// {
// "id": {"root": "2.16.840.1.113883.19.5", "extension": "MSG00001"},
// "creationTime": "20260101120000",
// "interactionId": {"root": "2.16.840.1.113883.1.6", "extension": "QUQI_IN000001UV01"},
// "sender": {"name": "device", "attributes": {"classCode": "DEV", ...}, "text": "", "children": []},
// "receiver": ...,
// "controlAct": {"classCode": "CACT", "moodCode": "EVN", "code": {...}, "domain": {...}}
// }
let back: Message = serde_json::from_str(&json)?;
assert_eq!(back, message);Keys are the hl7-3 field names in lowerCamelCase — which, wherever a field
corresponds to an HL7 v3 attribute or element, is that name: classCode, codeSystem, interactionId. Every key is serialized every time, so the
schema a consumer sees is fixed; on the way back in, only the keys that name a thing are
required and everything else defaults, matching hl7-3's rule that a missing
wrapper reads as absent rather than failing.
RIM classes and data types
use serde_hl7_v3::{Act, Role};
let domain = message.control_act.as_ref()?.domain.as_ref()?;
let patient = Role(hl7_3::rim::Role::from_element(domain));
let json = serde_json::to_string(&patient)?;
// {"classCode":"PAT","id":[{"root":"2.16.840.1.113883.19.5","extension":"444333222"}],
// "code":null,"statusCode":{"code":"active","codeSystem":null,"displayName":null},
// "effectiveTime":"20260101"}The payload's shape is the interaction's business, not this crate's: hl7-3 reads it
into whichever RIM class you say it is, and the wrapper serializes the typed value. All six
backbone classes and the six data types — Ii, Cd, Ivl, Pq, Ed, and NullFlavor as its bare code — have one. The
fourteen object types are written by one macro_rules!, so their shapes and their
strictness cannot drift; that is a local macro, not a derive, and adds no proc-macro dependency.
The element tree
use serde_hl7_v3::Element;
let element = Element(hl7_3::xml::parse(r#"<id root="1.2.3" extension="7"/>"#)?);
let json = serde_json::to_string(&element)?;
// {"name":"id","attributes":{"extension":"7","root":"1.2.3"},"text":"","children":[]}
let back: Element = serde_json::from_str(r#"{"name":"device"}"#)?; // the rest defaultsThe raw tree beneath everything: name, attributes as an object in name order, text, children.
This is what sender, receiver, and the domain payload serialize as,
and it round-trips exactly.
Strict deserialization
use serde_hl7_v3::{Message, Strict};
// "extention" — three levels down, inside the payload's id element.
let fixture = r#"{"controlAct":{"classCode":"CACT","moodCode":"EVN",
"domain":{"name":"patient","children":[{"name":"id","attrs":{"root":"1.2.3"}}]}}}"#;
let plain: Message = serde_json::from_str(fixture)?; // Ok — "attrs" ignored
let strict: Result<Strict<Message>, _> = serde_json::from_str(fixture);
assert!(strict.is_err()); // "unknown field `attrs`"Almost every key is optional, which is right for reading what a sender sent and exactly the
situation in which a typo in a fixture is silent. Strict<T> exists for every
object type and nests to any depth — into identifiers, codes, and every element of the payload
tree. It leaves attributes alone: attribute names are the document's data, and no
crate can know which ones an element should have.
What this crate does not do
It does not write XML, pick a wire format, validate codes against a vocabulary domain, or give
struct mode's FromElement types Serde. Every rule is in the crate's spec,
with a coverage table cargo test checks.
Related crates
serde-hl7
Serde support for HL7, one module per standard
hl7-3
HL7 v3: the RIM backbone, the data types, the message envelope
hl7-2-xml-lite-helper
The small, dependency-free XML reader this family shares