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:

message.pid.patient_name.family_name    # 'SMITH'
message.pid.patient_name.given_name     # 'JOHN'

Commonly used fields

The fields clinical integrations read most often:

AttributeFieldWhat it contains
message.pid.patient_name.family_namePID-5Family name
message.pid.date_time_of_birthPID-7Date of birth
message.pid.administrative_sexPID-8Administrative sex
message.pid.primary_languagePID-15Primary language
message.pid.birth_placePID-23Birth place
message.pv1.patient_classPV1-2Visit class (inpatient, outpatient, etc.)
message.pv1.assigned_patient_locationPV1-3Assigned location
message.msh.sending_applicationMSH-3Sending application
message.msh.message_typeMSH-9Message type
message.msh.message_control_idMSH-10Message control ID
message.msh.version_idMSH-12HL7 version
message.evn.recorded_date_timeEVN-2When 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:

message.get('PID.6')     # None - the field is empty
message.get('ZZZ.1')     # None - no such segment

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.field[.component[.subcomponent]]
  • 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_pid for 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

PageWhat it covers
Parsing and serializationparse_hl7, serialize and the conversions to dicts and JSON
ValidationRequired fields, cardinality and choice groups
Receiving over MLLPThe 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