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

LevelShapeExample
Messageobject, fields "separators" and "segments"{"separators": {…}, "segments": […]}
Segmentobject, fields "name" and "fields"{"name": "PID", "fields": […]}
Fieldarray of repetitions[["555-1111"], ["555-2222"]]
Repetitionarray of components[["SMITH"], ["JOHN"]]
Componentarray of subcomponent strings["ACME", "1.2.3", "ISO"]
Subcomponenta bare string, exactly as sent"SMITH"
Separatorsobject, six named character fields{"field": "|", "component": "^", …}
Terminatorone 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 own char handling 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.

ExampleShows
round_trip_via_jsonParse ER7, serialize to JSON, deserialize, and write the original text back out.
build_message_from_jsonThe direction most tutorials skip: JSON in, ER7 text out.
inspect_a_segment_as_jsonSerializing one segment, to see the shape each level chooses for itself.
cargo run --example round_trip_via_json

What 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 its er7 type, so this crate adds a capability rather than a second surface.

Where to go next