Aplanar Campos Repetidos de HL7 a JSON
Hl7 Tools

Aplanar Campos Repetidos de HL7 a JSON

La Parte Dificil de HL7 a JSON Son las Repeticiones, no el Parseo

Casi cualquier ingeniero que escribe su primer conversor de HL7 v2.x a JSON logra el 80 por ciento facil en una tarde. Separa por el retorno de carro que divide los segmentos, separa cada segmento por el separador de campo y emite un objeto JSON con clave por nombre de segmento. El problema empieza en cuanto llega un mensaje real con un campo repetido, un componente profundamente anidado o un delimitador escapado dentro de un valor. Ahi es donde un conversor ingenuo pierde datos en silencio mientras sigue produciendo JSON sintacticamente valido. Este articulo trata justo de la parte que se rompe en produccion: como convertir un campo repetido de HL7 en un array JSON con componentes anidados sin aplanar el significado.

Amplia nuestras guias sobre mapeo de los segmentos PID y PV1 a JSON y conversion de HL7 v2 a JSON. Para seguir el ejemplo con mensajes reales, inspecciona en el Visor HL7 y prueba el comportamiento en el Convertidor HL7 a JSON.

La Jerarquia de Cuatro Niveles de Delimitadores que Debes Respetar

HL7 v2.x define una jerarquia de codificacion estricta en el segmento MSH. Como describe el estandar HL7 Version 2.5.1, Capitulo 2 (Control), MSH-1 contiene el separador de campo y MSH-2 contiene los caracteres de codificacion en un orden fijo. Los caracteres por defecto son | para campos (MSH-1), seguidos de los cuatro caracteres de codificacion que contiene MSH-2 en su orden posicional fijo: ^ para componentes, ~ para repeticion de campo, \ para el caracter de escape y & para subcomponentes (es decir, ^~\&). Estos cinco caracteres no son decorativos. Definen un modelo de datos anidado que el conversor a JSON debe reproducir con fidelidad.

El error critico es tratar estos niveles como separaciones de string intercambiables. No lo son. El separador de repeticion ~ produce una lista de ocurrencias del mismo campo. El separador de componente ^ produce partes posicionales dentro de una sola ocurrencia. El separador de subcomponente & produce un nivel adicional de anidamiento dentro de un componente. Un conversor correcto recorre la jerarquia en este orden exacto: campo, despues repeticion, despues componente, despues subcomponente. Separar en el orden equivocado, o tratar ~ como si fuera ^, desordena la estructura de forma muy dificil de detectar despues.

Repeticion no es lo Mismo que Componentes

Considera PID-3, la lista de identificadores del paciente. Un paciente con numero de historia y un identificador enterprise podria transmitir un campo como 884422^^^CITY_GENERAL^MR~99887766^^^STATE_MPI^PI. El ~ divide esto en dos repeticiones. Cada repeticion se divide luego por ^ en sus componentes CX: ID, digito de control, esquema, authority asignadora y tipo de identificador.

La unica forma JSON correcta preserva la repeticion como array externo y los componentes como array interno, manteniendo cada componente vacio como una posicion real (aunque vacia) en vez de eliminarla:

"PID.3": [
  ["884422", null, null, "CITY_GENERAL", "MR"],
  ["99887766", null, null, "STATE_MPI", "PI"]
]

Un conversor que reduzca esto al string "884422", o peor a "884422^^^CITY_GENERAL^MR99887766...", ha destruido a la vez la frontera de repeticion y la de componente. En el primer caso el identificador enterprise desaparece; en el segundo el campo queda imposible de parsear. Como explica nuestra guia de mapeo de PID y PV1, este unico campo es donde fallan primero los conversores debiles, porque el matching de identidad depende de que sobreviva cada repeticion.

Subcomponentes — el Nivel que Todos Olvidan

Los subcomponentes son el nivel que mas conversores caseros ignoran, porque muchos mensajes nunca lo usan. Pero ciertos tipos de datos lo emplean intensamente. Los tipos XCN (identificador y nombre compuesto extendido) y HD (designador jerarquico) definidos en HL7 Version 2.5.1 Capitulo 2A (Tipos de Datos de Control) colocan namespace, ID universal y tipo de ID universal como subcomponentes de un solo componente usando el separador &.

Por ejemplo, una authority asignadora podria aparecer como CITY_GENERAL&1.2.840.114350&ISO dentro de PID-3. Eso es un componente compuesto por tres subcomponentes. En nuestro Convertidor HL7 a JSON, este anidamiento de subcomponentes se conserva como el cuarto elemento del array de componentes de PID-3:

"PID.3": ["884422", null, null, ["CITY_GENERAL", "1.2.840.114350", "ISO"], "MR"]

Aplanar esto a "CITY_GENERAL" descarta el OID que identifica de forma unica al sistema asignador, que es justo el valor que necesita un EMPI o un mapeo a FHIR Identifier.system. La regla es inequivoca: si un delimitador esta presente en el origen, el nivel estructural correspondiente debe existir en el JSON. Nunca elimines un nivel en silencio solo porque un consumidor concreto no lo use ahora mismo.

Que el Dato Decida la Forma, Pero sin Perder Nunca una Posicion

Un conversor consciente de las repeticiones tiene que decidir: ¿un campo que en este mensaje concreto solo trae una repeticion sale envuelto igualmente en un array externo, o sale como el array de componentes sin mas? Nuestro Convertidor HL7 a JSON toma la segunda opcion: el array de repeticion externo solo aparece cuando el campo origen contiene de verdad un separador de repeticion, asi que una ocurrencia no repetida de PID-3 es el propio array de componentes, no un envoltorio de un elemento alrededor de el. Eso mantiene la salida de una sola ocurrencia legible sin una capa extra de corchetes que pelar a mano.

Lo que importa mucho mas que cual de esas dos convenciones elijas es que elijas una y la apliques con consistencia, y que nunca dejes que la forma de una sola repeticion dependa de si un componente esta vacio. Sea cual sea la convencion que uses despues en tu propio pipeline, trata un array de componentes suelto y un array de repeticion de un elemento como la misma cosa al consumir este JSON, y preserva la posicion con null en cada nivel en vez de eliminar una posicion vacia — eliminarla es lo que convierte el componente 4 en el componente 3 en un mensaje si y en el siguiente no.

{
  "PID.3": ["884422", null, null, ["CITY_GENERAL", "1.2.840.114350", "ISO"], "MR"],
  "PID.5": ["Lopez", "Ana", "Maria", null, null, null, "L"]
}

Aqui PID-3 y PID-5 llevan cada uno una sola ocurrencia, asi que ninguno queda envuelto en un array de repeticion extra — pero si mas adelante llega una segunda repeticion de nombre en el mismo mensaje, PID.5 pasa a ser un array de dos arrays de componentes, y cualquier consumidor que lea este JSON tiene que esperar ambas formas para el mismo campo.

Aplanar Campos Repetidos de HL7 a JSON

Las Secuencias de Escape se Decodifican, no se Separan

Un conversor que separe de forma ingenua por los caracteres delimitadores corrompera cualquier valor que contenga legitimamente esos caracteres. HL7 lo resuelve con secuencias de escape, definidas en HL7 Version 2.5.1 Capitulo 2.7 (Uso de Secuencias de Escape en Campos de Texto). Las secuencias \F\, \S\, \T\, \R\ y \E\ representan respectivamente un caracter literal de campo, componente, subcomponente, repeticion y escape.

Esto significa que la tokenizacion debe ocurrir antes del desescape. Si un valor contiene \S\, eso es un acento circunflejo literal dentro del dato, no una frontera de componente. Un conversor que desescapa primero y separa despues tratara ese acento literal como delimitador estructural y rompera el valor. El orden correcto es: separar por los caracteres delimitadores crudos para establecer la estructura y luego decodificar las secuencias de escape dentro de cada valor hoja. Un apellido como Smith \T\ Jones debe convertirse en el string literal Smith & Jones, no en dos subcomponentes. Los escapes hexadecimales como \X0D\ para retornos de carro incrustados tambien pertenecen a este paso de decodificacion y son comunes en segmentos de texto libre NTE y OBX.

Vacio, Ausente y Nulo son Tres Cosas Distintas — Sabe Cuales Distingue de Verdad tu Conversor

Los campos repetidos agudizan la distincion entre vacio y ausente. HL7 v2.5.1 Capitulo 2 distingue tres estados que un conversor no debe confundir. Un campo que simplemente no aparece en el mensaje significa ausencia de informacion. Un campo presente pero vacio (dos delimitadores adyacentes) significa que no se aporto valor. Y el valor explicito "" — dos comillas dobles — es el nulo de HL7, que indica que el sistema emisor quiere borrar o anular ese elemento.

Nuestro Convertidor HL7 a JSON separa deliberadamente los dos primeros: un campo ausente es una clave omitida, un componente presente pero vacio es una posicion real del array que guarda null en vez de eliminarse, que es lo que mantiene intacta la identidad posicional (el componente 4 sigue siendo el componente 4 aunque este vacio). Lo que hoy no hace es dar al token de nulo explicito de HL7 "" una representacion JSON propia: ese token llega tal cual, sin desescapar, como el string de dos caracteres, en vez de como una marca distinguible de "borrar esto". Si tu sistema downstream necesita distinguir "no se aporto valor" de "el emisor quiere borrarlo", comprueba el string literal "" dentro de un componente en lugar de asumir que null significa eso — aqui null significa vacio, no nulo explicito. Como señala nuestra guia de conversion de HL7 v2 a JSON, esto importa sobre todo en flujos de actualizacion A08 y de correccion de resultados.

Un Ejemplo Trabajado — OBX con Valores Repetidos

Los resultados de observaciones muestran como se combinan todas estas reglas. Supongamos que OBX-3 es 718-7^Hemoglobin^LN y las unidades en OBX-6 son g/dL^^UCUM. Cada uno es un tipo de dato CWE con codigo, texto y sistema de codificacion como componentes. Pasado por el conversor en modo simplified, esto es lo que sale de verdad (anidado dentro del propio objeto fields del segmento, junto al nombre del segmento y su numero de ocurrencia):

{
  "segment": "OBX",
  "occurrence": 1,
  "fields": {
    "OBX.3": ["718-7", "Hemoglobin", "LN"],
    "OBX.5": "13.4",
    "OBX.6": ["g/dL", null, "UCUM"]
  }
}

Observa que OBX-5 es un valor numerico sin delimitadores internos, asi que se queda como escalar, mientras que OBX-3 y OBX-6 conservan sus arrays de componentes con el componente vacio central preservado como null en vez de eliminado. El conversor decide la estructura campo a campo segun los tipos de datos de la definicion del segmento, no adivinando a partir del dato. Por eso un conversor serio se guia por las tablas de segmentos y tipos de datos de HL7 y no por heuristicas de string.

Flujo de Validacion

El bucle practico es el mismo que recomendamos en todo el cluster. Abre un mensaje representativo en el Visor HL7 para ver el desglose de campo, componente, subcomponente y repeticion. Convierte el mismo mensaje con el Convertidor HL7 a JSON. Despues confirma que cada repeticion de PID-3, cada subcomponente de un campo de proveedor XCN y cada valor escapado de los segmentos de texto libre sobrevivio al viaje. Si un valor que contenia ~ o & en el visor aparece como un unico string aplanado en el JSON, el conversor pierde datos y debe corregirse antes de tocar una interfaz de produccion.

Conclusion

Aplanar campos repetidos de HL7 a JSON es un problema estructural, no de formato. Los cinco caracteres de codificacion declarados en MSH definen una jerarquia de cuatro niveles — campo, repeticion, componente, subcomponente — y un conversor fiel reproduce cada nivel que el dato origen utilice. Envuelve en un array de repeticion solo cuando el origen repite de verdad, decodifica las secuencias de escape solo despues de tokenizar y no elimines nunca una posicion vacia — preservala como null para que siga siendo distinguible de "ausente". Si aciertas en eso, el resto de tu pipeline de HL7 a JSON se reduce a nombrar campos, no a recuperar datos que tiraste sin querer. Empieza pasando un mensaje real con campos repetidos por el Convertidor HL7 a JSON y comprobando que los arrays se anidan como exige el estandar.

← Volver al Blog