HL7 v2 field access
Read and write any field of a parsed message - by name, by position or by path.
A parsed message offers its fields three ways - semantic names for readable code, wire positions for fields the specification no longer names, and path expressions for reaching a value from a string. This page covers all three, reading and writing alike.
All examples below use this message:
# Zato
from zato.hl7v2 import parse_hl7
raw = (
'MSH|^~\\&|SENDER|FACILITY|RECEIVER|FAC|20260315||ADT^A01^ADT_A01|CTL001|P|2.9\r'
'EVN|A01|20260315\r'
'PID|1||12345^^^HOSP^MR~67890^^^CLINIC^PI||SMITH^JOHN^A||19800115|M\r'
'PV1|1|I|WARD^101^BED1\r'
)
message = parse_hl7(raw, validate=False)
Read fields by name
Every segment the HL7 2.9 specification defines exposes its fields as typed attributes, named after the official HL7 field names in snake_case:
message.msh.sending_application # 'SENDER'
message.msh.message_control_id # 'CTL001'
message.pv1.patient_class # 'I'
message.pid.administrative_sex # 'M'
message.pid.date_time_of_birth # '19800115'
Components of composite fields
A field with a composite data type - XPN for person names, CX for identifiers - exposes its components by name too:
Commonly used fields
The fields clinical integrations read most often:
| Attribute | Field | What it contains |
|---|---|---|
message.pid.patient_name.family_name | PID-5 | Family name |
message.pid.date_time_of_birth | PID-7 | Date of birth |
message.pid.administrative_sex | PID-8 | Administrative sex |
message.pid.primary_language | PID-15 | Primary language |
message.pid.birth_place | PID-23 | Birth place |
message.pv1.patient_class | PV1-2 | Visit class (inpatient, outpatient, etc.) |
message.pv1.assigned_patient_location | PV1-3 | Assigned location |
message.msh.sending_application | MSH-3 | Sending application |
message.msh.message_type | MSH-9 | Message type |
message.msh.message_control_id | MSH-10 | Message control ID |
message.msh.version_id | MSH-12 | HL7 version |
message.evn.recorded_date_time | EVN-2 | When the event was recorded |
Positional access
Every field is also reachable by its wire position, as segment_position - pid_13 for PID-13. This is the way to fields the v2.9 specification has withdrawn - they no longer have an official name, yet real-world messages still carry them:
# Zato
from zato.hl7v2 import PID
segment = PID()
segment.set_id_pid = '1'
# PID-13 is withdrawn in v2.9 but still used in the wild
segment.pid_13 = '0221-4523890'
segment.serialize()
# 'PID|1||||||||||||0221-4523890'
segment.pid_13
# '0221-4523890'
Repetitions
A field that repeats - values separated by ~ on the wire - reads as a list that also delegates attribute access to its first element:
identifiers = message.pid.patient_identifier_list
identifiers.id_number # '12345' - same as identifiers[0].id_number
identifiers[1].id_number # '67890'
Writing takes a single value or a list:
# Zato
from zato.hl7v2 import NTE
segment = NTE()
segment.comment = ['first note', 'second note']
segment.serialize()
# 'NTE|||first note~second note'
Write fields
A segment starts empty and takes assignments - plain strings, ER7-encoded composites or typed data type objects:
# Zato
from zato.hl7v2 import PID, XPN
# An ER7-encoded string ..
segment = PID()
segment.set_id_pid = '1'
segment.patient_name = 'SMITH^JOHN^A'
segment.serialize()
# 'PID|1||||SMITH^JOHN^A'
# .. or a typed object.
typed_segment = PID()
typed_segment.set_id_pid = '1'
typed_segment.patient_name = XPN(family_name='SMITH', given_name='JOHN')
typed_segment.serialize()
# 'PID|1||||SMITH^JOHN'
The same assignments work on segments of a parsed message, so a service can correct one field and serialize the message back.
Path expressions
The get method reaches any value with a dot-separated path, mixing positions and names freely - the form to use when the field to read arrives as configuration rather than as code:
message.get('PID.5') # 'SMITH' - the first component of the field
message.get('PID.5.1') # 'SMITH'
message.get('PID.5.2') # 'JOHN'
message.get('MSH.9.1') # 'ADT'
message.get('pid.patient_name.family_name') # 'SMITH'
message.get('pid.patient_name.2') # 'JOHN' - names and positions mix freely
Positional references stay valid for withdrawn fields, e.g. PID.2, the same way positional attributes do.
Repetitions in paths
An index in square brackets selects a repetition - without one, the first repetition is used:
message.get('PID.3[0]') # '12345' - first repetition
message.get('PID.3[1]') # '67890' - second repetition
message.get('PID.3[1].5') # 'PI' - a component of the second repetition
Subcomponents
The fourth path part selects a subcomponent - values separated by & on the wire. For a PID-3 of PT1^^^ACME&1.2.3&ISO^MR:
message.get('PID.3.4') # 'ACME' - the first subcomponent of the assigning authority
message.get('PID.3.4.2') # '1.2.3' - the universal ID subcomponent
Missing values
A path that leads nowhere returns None - whether the segment, field, component or subcomponent is missing, get never raises for absent data:
Writing through paths
The set method takes the same syntax:
message.set('PID.5.1', 'JONES')
message.get('PID.5.1') # 'JONES'
message.serialize() # the ER7 output contains JONES^JOHN^A
The path grammar
A path is a dot-separated string of up to four parts:
- segment - a segment ID (
PID) or a segment attribute name (pid) - field - a field position (
5) or a field name (patient_name), with an optional repetition index (3[1]) - component - a component position (
1) or a component name (family_name) - subcomponent - a subcomponent position (
2)
Naming conventions
Field names follow a small set of rules:
- The official HL7 name is lowercased and spaces become underscores, e.g. "Patient Name" becomes
patient_name. - Set ID fields include the segment name to stay unique, e.g.
set_id_pidfor PID-1. - Long official names are kept as they are, e.g. XPN-3 is
second_and_further_given_names_or_initials_thereof.
Field metadata
Each field is described by a descriptor recording its HL7 position, data type, usage and repeatability, straight from the v2.9 specification:
# Zato
from zato.hl7v2 import PID
descriptor = PID.patient_name
descriptor.position # 5
descriptor.datatype # 'XPN'
descriptor.usage # Usage.REQUIRED
descriptor.repeatable # True
PID.administrative_sex.table # 'HL70001' - the HL7 table constraining the values
See also
| Page | What it covers |
|---|---|
| Parsing and serialization | parse_hl7, serialize and the conversions to dicts and JSON |
| Validation | Required fields, cardinality and choice groups |
| Receiving over MLLP | The channels that deliver messages to services already parsed |
Learn more
Schedule a meaningful demo
Book a demo with an expert who will help you build meaningful systems that match your ambitions
"We evaluated 12 integration platforms and Zato was the only one to score 100%."
Philip Zuñiga, Assistant Professor, University of the Philippines