使用Firely NuGet包解析FHIR Patient资源并导出CSV的方法
问题描述
我正在使用Firely NuGet包从FHIR Bundle中解析Patient资源,发现KeyValuePair.Value的类型为object,且复杂FHIR数据类型包含嵌套对象。请问如何解析并提取所有FHIR元素及其值并导出为CSV格式?另外,是否存在无需先对资源进行类型转换再逐个提取内部元素的更简便方法来解析和枚举元素?
代码示例
string fhirBundle = GetFHIRBundleFromFile(); FhirJsonParser fhirJsonParser = new FhirJsonParser(); Bundle bundle = fhirJsonParser.Parse<Bundle>(fhirBundle); foreach (var entry in bundle.Entry) { switch (entry.Resource) { case Patient: Patient patient = (Patient)entry.Resource; foreach (KeyValuePair<string, object> keyValuePair in patient) Console.WriteLine(keyValuePair.Key + ": " + keyValuePair.Value.ToString()); break; default: Console.WriteLine("Resource is not of type Patient"); break; } }
解决方案
一、解析嵌套元素并导出CSV
要处理FHIR的嵌套数据类型,需要递归遍历所有元素,将数据扁平化后再导出为CSV,具体步骤如下:
- 递归遍历元素:编写递归方法,判断值的类型——如果是集合或FHIR复杂类型,就继续遍历内部元素,用点号/索引拼接嵌套路径(比如
name[0].family);如果是基础类型,直接记录键值对。 - 收集扁平化数据:将每个Patient的所有元素路径和对应值存入字典,确保字段路径唯一。
- 生成CSV:提取所有唯一字段作为表头,再按表头顺序将每个Patient的扁平化数据写入CSV行。
示例代码:
// 递归遍历FHIR元素,生成扁平化键值对 private static void TraverseFhirElement(object element, string parentPath, Dictionary<string, string> flatData) { if (element == null) return; // 处理集合类型(如List<HumanName>) if (element is IEnumerable enumerable && !(element is string)) { int index = 0; foreach (var item in enumerable) { TraverseFhirElement(item, $"{parentPath}[{index}]", flatData); index++; } return; } // 处理FHIR复杂类型(实现了IEnumerable<KeyValuePair<string, object>>的类型) if (element is IEnumerable<KeyValuePair<string, object>> fhirElement) { foreach (var kvp in fhirElement) { string currentPath = string.IsNullOrEmpty(parentPath) ? kvp.Key : $"{parentPath}.{kvp.Key}"; TraverseFhirElement(kvp.Value, currentPath, flatData); } return; } // 基础类型直接存入 flatData[parentPath] = element.ToString(); } // 将扁平化的Patient数据导出为CSV private static void ExportPatientsToCsv(List<Dictionary<string, string>> allPatientData, string filePath) { // 获取所有唯一字段作为表头 var allFields = allPatientData.SelectMany(d => d.Keys).Distinct().ToList(); using (var writer = new StreamWriter(filePath)) { // 写入表头(处理引号转义) writer.WriteLine(string.Join(",", allFields.Select(f => $"\"{f}\""))); // 写入每行数据 foreach (var patientData in allPatientData) { var row = allFields.Select(field => patientData.TryGetValue(field, out var value) ? $"\"{value?.Replace("\"", "\"\"")}\"" : "\"\""); writer.WriteLine(string.Join(",", row)); } } } // 使用示例 var allPatientData = new List<Dictionary<string, string>>(); foreach (var entry in bundle.Entry) { if (entry.Resource is Patient patient) { var flatData = new Dictionary<string, string>(); TraverseFhirElement(patient, string.Empty, flatData); allPatientData.Add(flatData); } } ExportPatientsToCsv(allPatientData, "patients.csv");
二、无需类型转换的简便枚举方法
Firely的Resource基类本身实现了IEnumerable<KeyValuePair<string, object>>,因此可以直接遍历entry.Resource,无需先强制转换为Patient类型。如果需要过滤资源类型,用类型判断即可:
方法1:直接遍历Resource基类
foreach (var entry in bundle.Entry) { if (entry.Resource is not Patient) { Console.WriteLine("Resource is not of type Patient"); continue; } // 直接遍历Resource的键值对,结合递归方法扁平化数据 var flatData = new Dictionary<string, string>(); TraverseFhirElement(entry.Resource, string.Empty, flatData); foreach (var kvp in flatData) { Console.WriteLine($"{kvp.Key}: {kvp.Value}"); } }
方法2:使用ElementNavigator类
Firely SDK提供的ElementNavigator类支持类似XPath的路径查询,能更便捷地遍历所有元素:
using Hl7.Fhir.Navigation; foreach (var entry in bundle.Entry) { if (entry.Resource is Patient patient) { var navigator = new ElementNavigator(patient); while (navigator.MoveToNext()) { string elementPath = navigator.Path; string elementValue = navigator.Value?.ToString(); Console.WriteLine($"{elementPath}: {elementValue}"); } } }
内容的提问来源于stack exchange,提问作者nobody
相关产品推荐
相关产品推荐

