https://github.com/singl3focus/hl7-converter
Go toolkit for converting HL7/ASTM-style lab messages via declarative JSON mappings. Validated configs, positional or input-driven generation, optional JS post-processing, and aliases for easy routing. Ideal for LIS connectors, ETL bridges, and quick vendor-to-vendor integrations
https://github.com/singl3focus/hl7-converter
astm converter go hl7 medtech
Last synced: about 1 month ago
JSON representation
Go toolkit for converting HL7/ASTM-style lab messages via declarative JSON mappings. Validated configs, positional or input-driven generation, optional JS post-processing, and aliases for easy routing. Ideal for LIS connectors, ETL bridges, and quick vendor-to-vendor integrations
- Host: GitHub
- URL: https://github.com/singl3focus/hl7-converter
- Owner: singl3focus
- License: mit
- Created: 2024-06-18T14:20:29.000Z (almost 2 years ago)
- Default Branch: main
- Last Pushed: 2026-04-08T11:08:56.000Z (2 months ago)
- Last Synced: 2026-04-08T13:12:05.845Z (2 months ago)
- Topics: astm, converter, go, hl7, medtech
- Language: Go
- Homepage:
- Size: 233 KB
- Stars: 11
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# HL7 Converter
`hl7-converter` is a config-driven Go toolkit for transforming HL7, ASTM, and other row-based lab messages with declarative JSON mappings.
It is built for teams that need a small, embeddable mapping engine inside LIS bridges, ETL adapters, middleware services, or device integrations, without committing to a heavyweight integration platform.
## What Problem It Solves
Lab and device integrations usually fail in the same place: the message structure is close to HL7, but never close enough.
One analyzer emits ASTM-like rows, another vendor expects HL7-ish segments, and the integration layer ends up full of one-off string parsing code. That code is hard to review, hard to test, and expensive to change when a new device or customer mapping appears.
`hl7-converter` moves that logic into JSON configuration:
- define separators and row structure once
- map input tags to output tags
- describe field templates with links like `` or ``
- add aliases for downstream routing or logging
- optionally post-process the converted result with trusted JavaScript
## When To Use It
- You need a small Go library, not a full integration server.
- Message transformation rules should live in config instead of hand-written code.
- The source and target formats are row-oriented and field-position driven.
- You need to embed conversion into an existing Go service or adapter.
- You want repeatable test coverage around mappings and templates.
## When Not To Use It
- You need a visual integration designer, queues, retries, persistence, or orchestration.
- Your mappings require deep business workflows rather than deterministic field transformation.
- Your payloads are not fundamentally row/tag based.
- You need sandboxed user scripting. JavaScript hooks here are trusted-code only.
## 30-Second Quick Start
```bash
go get github.com/singl3focus/hl7-converter/v2@latest
```
```go
package main
import (
"log"
hl7converter "github.com/singl3focus/hl7-converter/v2"
)
func main() {
params, err := hl7converter.NewConverterParams("./examples/config.json", "astm_hbl", "mindray_hbl")
if err != nil {
log.Fatal(err)
}
converter, err := hl7converter.NewConverter(
params,
hl7converter.WithUsingPositions(),
hl7converter.WithUsingAliases(),
)
if err != nil {
log.Fatal(err)
}
input := []byte("H|\\^&|||sireAstmCom|||||||P|LIS02-A2|20220327\n" +
"P|1||||^||||||||||||||||||||||||||||\n" +
"O|1|142212||^^^Urina4^screening^|||||||||^||URI^^||||||||||F|||||\n" +
"R|1|^^^Urina4^screening^^tempo-analisi-minuti|180|||||F|||||\n" +
"R|2|^^^Urina4^screening^^tempo-analisi-minuti|90|||||F|||||")
result, err := converter.Convert(input)
if err != nil {
log.Fatal(err)
}
log.Print(result.String())
}
```
Repository sample config: `examples/config.json`.
## Input / Output Example
Input:
```text
H|\^&|||sireAstmCom|||||||P|LIS02-A2|20220327
P|1||||^||||||||||||||||||||||||||||
O|1|142212||^^^Urina4^screening^|||||||||^||URI^^||||||||||F|||||
R|1|^^^Urina4^screening^^tempo-analisi-minuti|180|||||F|||||
R|2|^^^Urina4^screening^^tempo-analisi-minuti|90|||||F|||||
```
Output:
```text
MSH|^\&|Manufacturer|Model|||20220327||ORU^R01||P|2.3.1||||||ASCII|
PID||142212|||||||||||||||||||||||||||
OBR||142212|||||||||||||URI|||||||||||||||||||||||||||
OBX|||Urina4^screening^tempo-analisi-minuti|tempo-analisi-minuti|180||||||F|||||
OBX|||Urina4^screening^tempo-analisi-minuti|tempo-analisi-minuti|90||||||F|||||
```
## Mapping Config Example
```json
{
"mindray_hbl": {
"component_separator": "^",
"component_array_separator": " ",
"field_separator": "|",
"line_separator": "\r",
"aliases": {
"Header": "MSH-9.2",
"PatientID": "PID-3",
"Key": "OBR-16"
},
"tags_info": {
"positions": {
"1": "MSH",
"2": "PID",
"3": "OBR",
"4": "OBX"
},
"tags": {
"MSH": {
"linked": "H",
"options": ["autofill"],
"fields_number": 19,
"template": "MSH|??^\\&|??Manufacturer|??Model|||||??ORU^R01|||??2.3.1||||||??ASCII|"
},
"PID": {
"linked": "P",
"fields_number": 30,
"template": "PID|||||||||||||||||||||||||||||"
}
}
}
}
}
```
## Architecture Overview
### Conversion Flow
```mermaid
flowchart LR
A["Incoming message"] --> B["Parse rows by line separator"]
B --> C["Split fields by field separator"]
C --> D["Apply input tag options"]
D --> E["Build parsed tag map"]
E --> F["Resolve output templates and links"]
F --> G["Generate rows by input order or configured positions"]
G --> H["Apply aliases"]
H --> I["Optional JS post-processing"]
I --> J["Result"]
```
### Config Structure
```mermaid
flowchart TB
A["Config file"] --> B["Modification block"]
B --> C["Separators"]
B --> D["Types"]
B --> E["Aliases"]
B --> F["tags_info"]
F --> G["positions"]
F --> H["tags"]
H --> I["linked"]
H --> J["fields_number"]
H --> K["template"]
H --> L["options"]
```
### Conversion Modes
```mermaid
flowchart LR
A["Input-driven conversion"] --> B["Walk input rows in original order"]
B --> C["Map each seen input tag to one output row"]
D["Position-driven conversion"] --> E["Walk output positions from config"]
E --> F["Repeat each output tag by linked input count"]
```
## Supported Mapping Features
- Custom line, field, component, and component-array separators.
- Output generation by input order or by explicit configured positions.
- Template links like `` and component links like ``.
- Default literals with `??value`.
- Aliases for extracting stable values from the converted result.
- Trusted JavaScript post-processing via `Result.UseScript`.
### Supported Options
- `autofill`: append empty trailing fields while parsing an input row until it matches the tag `fields_number`.
## Validation Model
Validation now happens in two layers:
1. `Modification.Validate()`
- checks required separators
- rejects separator conflicts
- validates positions keys and references
- validates known options
- validates template field count and link/default syntax
2. `NewConverterParams()`
- validates the selected input/output pair together
- rejects output templates that reference unknown input tags
- rejects links that point outside known input field ranges when `fields_number` is explicit
This means invalid mappings fail earlier, before conversion starts.
## Concurrency And Safety
- `Converter` is safe for concurrent `Convert` calls on the same instance.
- `Result` is mutable and is not guaranteed safe for concurrent mutation.
- JavaScript hooks are trusted-code only. There is no built-in sandbox or timeout.
## Public API Snapshot
- Construction: `NewConverterParams`, `NewConverter`
- Converter options: `WithUsingPositions`, `WithUsingAliases`
- Execution: `Convert`, `ParseMsg`, `ParseInput`, `IndetifyMsg`
- Result helpers: `FindTag`, `ApplyAliases`, `UseScript`, `SwapRows`, `SetRow`
- Field helpers: `Components`, `Array`, `ComponentsChecked`, `ArrayChecked`
## Limitations
- Designed for ASCII-oriented messages and simple string-based separators.
- Template semantics are intentionally narrow: deterministic field mapping, not workflow orchestration.
- Component validation can only be checked at runtime because input payload shape may vary per message.
- JavaScript support is for controlled operational environments, not end-user scripting.
## CLI
A minimal CLI is published with each release as `hl7conv`. It is built from `cmd/hl7conv` and uses the same library API.
```text
hl7conv [flags]
Commands:
convert Convert a message using a config and an input/output block pair.
identify Identify a message type by matching parsed tags against configured Types.
validate-config Validate a JSON config file and optionally a single modification block.
version Print version.
```
Examples:
```bash
# convert a message read from a file, position-driven, with aliases
hl7conv convert \
-config ./examples/config.json \
-in astm_hbl -out mindray_hbl \
-input ./message.txt \
-positions -aliases
# validate config and a specific block
hl7conv validate-config -config ./examples/config.json -block astm_hbl
# identify message type
cat ./message.txt | hl7conv identify \
-config ./examples/config.json \
-in astm_hbl -out mindray_hbl
```
Build locally:
```bash
go build -o hl7conv ./cmd/hl7conv
```
## Alternatives
`hl7-converter` deliberately occupies a small slot. It is not a replacement for full integration platforms or full HL7 toolkits.
- **Mirth Connect / NextGen Connect.** Full integration server with channels, queues, persistence, retries, transformers and a UI. Use it when you need an integration platform. Use `hl7-converter` when you need an embeddable mapping engine inside your own Go service.
- **HAPI HL7v2 (Java).** Comprehensive HL7v2 parser and model with strict structures. Use it when you need full HL7v2 conformance, ACK handling, and a Java stack. Use `hl7-converter` when your reality is closer to ASTM-like or HL7-ish row formats and you want config-driven mapping in Go.
- **FHIR-oriented converters and mappers (FHIR Mapping Language, Microsoft FHIR Converter).** Aimed at producing FHIR resources, often with rich resource semantics and bundle handling. Use them when the target is FHIR. Use `hl7-converter` when both sides are row-and-tag based and you need deterministic field mapping without leaving Go.
In short: pick `hl7-converter` when the mapping is deterministic, row-based, must live in Go, and the only thing you really need is a small, testable transformation step.
## Testing And Benchmarks
Run the full test suite:
```bash
go test ./...
```
Run with the race detector:
```bash
go test -race ./...
```
Run benchmarks:
```bash
go test -bench=. ./...
```
The repository includes:
- unit tests for config validation, template parsing, conversion modes, aliases, and field helpers
- concurrency tests for shared `Converter` usage
- fuzz coverage for `Convert`
- benchmarks for realistic message payloads
## License
MIT