# 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:

```python
# 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 {#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:

```python
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 {#components-of-composite-fields}

A field with a composite data type - `XPN` for person names, `CX` for identifiers - exposes its components by name too:

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

## Commonly used fields {#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 {#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 use them:

```python
# 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 {#repetitions}

A field that repeats - values separated by `~` on the wire - reads as a list that also delegates attribute access to its first element:

```python
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:

```python
# Zato
from zato.hl7v2 import NTE

segment = NTE()
segment.comment = ['first note', 'second note']

segment.serialize()
# 'NTE|||first note~second note'
```

## Write fields {#write-fields}

A segment starts empty and takes assignments - plain strings, ER7-encoded composites or typed data type objects:

```python
# 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](https://zato.io/docs/dev/healthcare/hl7/v2/parsing/index.html) the message back.

## Path expressions {#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:

```python
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 such as `PID.2` stay valid for withdrawn fields, as do positional attributes.

### Repetitions in paths {#repetitions-in-paths}

An index in square brackets selects a repetition - without one, the first repetition is used:

```python
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 {#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`:

```python
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 {#missing-values}

A path that leads nowhere returns `None` - whether the segment, field, component or subcomponent is missing, `get` never raises for absent data:

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

### Writing through paths {#writing-through-paths}

The `set` method takes the same syntax:

```python
message.set('PID.5.1', 'JONES')

message.get('PID.5.1')    # 'JONES'
message.serialize()       # the ER7 output contains JONES^JOHN^A
```

### The path grammar {#the-path-grammar}

A path is a dot-separated string of up to four parts:

```text
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 {#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 {#field-metadata}

Each field is described by a descriptor recording its HL7 position, data type, usage and repeatability, taken from the v2.9 specification:

```python
# 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 {#see-also}

- [Parsing and serialization](https://zato.io/docs/dev/healthcare/hl7/v2/parsing/index.html) - parse\_hl7, serialize and the conversions to dicts and JSON
- [Validation](https://zato.io/docs/dev/healthcare/hl7/v2/validation/index.html) - Required fields, cardinality and choice groups
- [Receiving over MLLP](https://zato.io/docs/dev/healthcare/hl7v2/mllp/index.html) - The channels that deliver messages to services already parsed

## Learn more {#learn-more}

- [Healthcare interface engine](https://zato.io/docs/dev/healthcare/) - Clinical messages, FHIR, conversion and operations
- [HL7 v2 parsing](https://zato.io/docs/dev/healthcare/hl7/v2/parsing/) - Messages as typed objects, with field access and validation
- [MLLP channels](https://zato.io/docs/dev/healthcare/hl7v2/mllp/) - Receiving and sending HL7 v2 over MLLP sockets
- [FHIR integrations](https://zato.io/docs/dev/healthcare/hl7/fhir/) - Read and write FHIR resources, with paths, bundles and extensions
- [HL7 v2 to FHIR](https://zato.io/docs/dev/healthcare/hl7/to-fhir/) - Converting v2 messages into FHIR bundles, codes and references included
- [EDIFACT](https://zato.io/docs/dev/healthcare/edifact/) - Parsing interchanges, dialects and the transports they arrive on
- [Transformation](https://zato.io/docs/dev/healthcare/transformation/) - One message in, another standard out, in ordinary Python
- [Audit log](https://zato.io/docs/dev/healthcare/audit-log.html) - Every message with its acknowledgment, searchable by patient identifier
- [AI in clinical interfaces](https://zato.io/docs/dev/healthcare/ai/) - Services calling LLMs and AI agents calling clinical services as tools
