001package org.hl7.fhir.validation.instance.type; 002 003import java.io.ByteArrayOutputStream; 004import java.io.IOException; 005import java.util.ArrayList; 006import java.util.Comparator; 007import java.util.HashSet; 008import java.util.List; 009import java.util.Set; 010 011import org.hl7.fhir.convertors.conv10_50.VersionConvertor_10_50; 012import org.hl7.fhir.convertors.conv14_50.VersionConvertor_14_50; 013import org.hl7.fhir.convertors.conv30_50.VersionConvertor_30_50; 014import org.hl7.fhir.convertors.factory.*; 015import org.hl7.fhir.exceptions.FHIRException; 016import org.hl7.fhir.r5.conformance.ProfileUtilities; 017import org.hl7.fhir.r5.context.IWorkerContext; 018import org.hl7.fhir.r5.context.IWorkerContext.ValidationResult; 019import org.hl7.fhir.r5.elementmodel.Element; 020import org.hl7.fhir.r5.elementmodel.Manager; 021import org.hl7.fhir.r5.elementmodel.Manager.FhirFormat; 022import org.hl7.fhir.r5.formats.IParser.OutputStyle; 023import org.hl7.fhir.r5.model.Coding; 024import org.hl7.fhir.r5.model.ElementDefinition; 025import org.hl7.fhir.r5.model.ExpressionNode; 026import org.hl7.fhir.r5.model.Resource; 027import org.hl7.fhir.r5.model.StructureDefinition; 028import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionKind; 029import org.hl7.fhir.r5.model.StructureDefinition.TypeDerivationRule; 030import org.hl7.fhir.r5.model.ValueSet; 031import org.hl7.fhir.r5.utils.FHIRPathEngine; 032import org.hl7.fhir.r5.utils.ToolingExtensions; 033import org.hl7.fhir.r5.utils.XVerExtensionManager; 034import org.hl7.fhir.utilities.Utilities; 035import org.hl7.fhir.utilities.VersionUtilities; 036import org.hl7.fhir.utilities.i18n.I18nConstants; 037import org.hl7.fhir.utilities.validation.ValidationMessage; 038import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType; 039import org.hl7.fhir.utilities.validation.ValidationMessage.Source; 040import org.hl7.fhir.utilities.validation.ValidationOptions; 041import org.hl7.fhir.validation.BaseValidator; 042import org.hl7.fhir.validation.TimeTracker; 043import org.hl7.fhir.validation.instance.utils.NodeStack; 044 045public class StructureDefinitionValidator extends BaseValidator { 046 047 public class FhirPathSorter implements Comparator<ExpressionNode> { 048 049 @Override 050 public int compare(ExpressionNode arg0, ExpressionNode arg1) { 051 return arg0.toString().compareTo(arg1.toString()); 052 } 053 054 } 055 056 private FHIRPathEngine fpe; 057 private boolean wantCheckSnapshotUnchanged; 058 059 public StructureDefinitionValidator(IWorkerContext context, TimeTracker timeTracker, FHIRPathEngine fpe, boolean wantCheckSnapshotUnchanged, XVerExtensionManager xverManager) { 060 super(context, xverManager); 061 source = Source.InstanceValidator; 062 this.fpe = fpe; 063 this.timeTracker = timeTracker; 064 this.wantCheckSnapshotUnchanged = wantCheckSnapshotUnchanged; 065 } 066 067 public void validateStructureDefinition(List<ValidationMessage> errors, Element src, NodeStack stack) { 068 StructureDefinition sd = null; 069 try { 070 sd = loadAsSD(src); 071 List<ElementDefinition> snapshot = sd.getSnapshot().getElement(); 072 sd.setSnapshot(null); 073 StructureDefinition base = context.fetchResource(StructureDefinition.class, sd.getBaseDefinition()); 074 if (warning(errors, IssueType.NOTFOUND, stack.getLiteralPath(), base != null, I18nConstants.UNABLE_TO_FIND_BASE__FOR_, sd.getBaseDefinition(), "StructureDefinition, so can't check the differential")) { 075 if (rule(errors, IssueType.NOTFOUND, stack.getLiteralPath(), sd.hasDerivation(), I18nConstants.SD_MUST_HAVE_DERIVATION, sd.getUrl())) { 076 if (sd.getDerivation() == TypeDerivationRule.CONSTRAINT) { 077 List<ValidationMessage> msgs = new ArrayList<>(); 078 ProfileUtilities pu = new ProfileUtilities(context, msgs, null); 079 pu.setXver(xverManager); 080 pu.generateSnapshot(base, sd, sd.getUrl(), "http://hl7.org/fhir/R4/", sd.getName()); 081 if (msgs.size() > 0) { 082 for (ValidationMessage msg : msgs) { 083 // we need to set the location for the context 084 String loc = msg.getLocation(); 085 if (loc.contains("#")) { 086 msg.setLocation(stack.getLiteralPath()+".differential.element.where(path = '"+loc.substring(loc.indexOf("#")+1)+"')"); 087 } else { 088 msg.setLocation(stack.getLiteralPath()); 089 } 090 errors.add(msg); 091 } 092 } 093 if (!snapshot.isEmpty() && wantCheckSnapshotUnchanged) { 094 int was = snapshot.size(); 095 int is = sd.getSnapshot().getElement().size(); 096 rule(errors, IssueType.NOTFOUND, stack.getLiteralPath(), was == is, I18nConstants.SNAPSHOT_EXISTING_PROBLEM, was, is); 097 } 098 } 099 } 100 if ("constraint".equals(src.getChildValue("derivation"))) { 101 rule(errors, IssueType.NOTFOUND, stack.getLiteralPath(), base.getKindElement().primitiveValue().equals(src.getChildValue("kind")), 102 I18nConstants.SD_DERIVATION_KIND_MISMATCH, base.getKindElement().primitiveValue(), src.getChildValue("kind")); 103 } 104 } 105 } catch (FHIRException | IOException e) { 106 rule(errors, IssueType.EXCEPTION, stack.getLiteralPath(), false, I18nConstants.ERROR_GENERATING_SNAPSHOT, e.getMessage()); 107 } 108 List<Element> differentials = src.getChildrenByName("differential"); 109 List<Element> snapshots = src.getChildrenByName("snapshot"); 110 for (Element differential : differentials) { 111 validateElementList(errors, differential, stack.push(differential, -1, null, null), false, snapshots.size() > 0, sd); 112 } 113 for (Element snapshot : snapshots) { 114 validateElementList(errors, snapshot, stack.push(snapshot, -1, null, null), true, true, sd); 115 } 116 } 117 118 private void validateElementList(List<ValidationMessage> errors, Element elementList, NodeStack stack, boolean snapshot, boolean hasSnapshot, StructureDefinition sd) { 119 List<Element> elements = elementList.getChildrenByName("element"); 120 int cc = 0; 121 for (Element element : elements) { 122 validateElementDefinition(errors, element, stack.push(element, cc, null, null), snapshot, hasSnapshot, sd); 123 cc++; 124 } 125 } 126 127 private void validateElementDefinition(List<ValidationMessage> errors, Element element, NodeStack stack, boolean snapshot, boolean hasSnapshot, StructureDefinition sd) { 128 boolean typeMustSupport = false; 129 List<Element> types = element.getChildrenByName("type"); 130 Set<String> typeCodes = new HashSet<>(); 131 for (Element type : types) { 132 if (hasMustSupportExtension(type)) { 133 typeMustSupport = true; 134 } 135 String tc = type.getChildValue("code"); 136 if (type.hasExtension("http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type")) { 137 tc = type.getExtensionValue("http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type").primitiveValue(); 138 } 139 if (Utilities.noString(tc) && type.hasChild("code")) { 140 if (type.getNamedChild("code").hasExtension("http://hl7.org/fhir/StructureDefinition/structuredefinition-json-type")) { 141 tc = "*"; 142 } 143 } 144 typeCodes.add(tc); 145 // check the stated profile - must be a constraint on the type 146 if (snapshot || sd != null) { 147 validateElementType(errors, type, stack.push(type, -1, null, null), sd, element.getChildValue("path")); 148 } 149 } 150 if (typeMustSupport) { 151 if (snapshot) { 152 rule(errors, IssueType.EXCEPTION, stack.getLiteralPath(), "true".equals(element.getChildValue("mustSupport")), I18nConstants.SD_NESTED_MUST_SUPPORT_SNAPSHOT, element.getNamedChildValue("path")); 153 } else { 154 hint(errors, IssueType.EXCEPTION, stack.getLiteralPath(), hasSnapshot || "true".equals(element.getChildValue("mustSupport")), I18nConstants.SD_NESTED_MUST_SUPPORT_DIFF, element.getNamedChildValue("path")); 155 } 156 } 157 if (element.hasChild("binding")) { 158 Element binding = element.getNamedChild("binding"); 159 validateBinding(errors, binding, stack.push(binding, -1, null, null), typeCodes, snapshot, element.getNamedChildValue("path")); 160 } else { 161 // this is a good idea but there's plenty of cases where the rule isn't met; maybe one day it's worth investing the time to exclude these cases and bring this rule back 162// String bt = boundType(typeCodes); 163// hint(errors, IssueType.BUSINESSRULE, stack.getLiteralPath(), !snapshot || bt == null, I18nConstants.SD_ED_SHOULD_BIND, element.getNamedChildValue("path"), bt); 164 } 165 // in a snapshot, we validate that fixedValue, pattern, and defaultValue, if present, are all of the right type 166 if (snapshot && (element.getIdBase() != null) && (element.getIdBase().contains("."))) { 167 if (rule(errors, IssueType.EXCEPTION, stack.getLiteralPath(), !typeCodes.isEmpty() || element.hasChild("contentReference"), I18nConstants.SD_NO_TYPES_OR_CONTENTREF, element.getIdBase())) { 168 Element v = element.getNamedChild("defaultValue"); 169 if (v != null) { 170 rule(errors, IssueType.EXCEPTION, stack.push(v, -1, null, null).getLiteralPath(), typeCodes.contains(v.fhirType()), I18nConstants.SD_VALUE_TYPE_IILEGAL, element.getIdBase(), "defaultValue", v.fhirType(), typeCodes); 171 } 172 v = element.getNamedChild("fixed"); 173 if (v != null) { 174 rule(errors, IssueType.EXCEPTION, stack.push(v, -1, null, null).getLiteralPath(), typeCodes.contains(v.fhirType()), I18nConstants.SD_VALUE_TYPE_IILEGAL, element.getIdBase(), "fixed", v.fhirType(), typeCodes); 175 } 176 v = element.getNamedChild("pattern"); 177 if (v != null) { 178 rule(errors, IssueType.EXCEPTION, stack.push(v, -1, null, null).getLiteralPath(), typeCodes.contains(v.fhirType()), I18nConstants.SD_VALUE_TYPE_IILEGAL, element.getIdBase(), "pattern", v.fhirType(), typeCodes); 179 } 180 } 181 } 182 } 183 184 private String boundType(Set<String> typeCodes) { 185 for (String tc : typeCodes) { 186 if (Utilities.existsInList(tc, "code", "Coding", "CodeableConcept", "Quantity", "CodeableReference")) { 187 return tc; 188 } 189 } 190 return null; 191 } 192 193 private String bindableType(Set<String> typeCodes) { 194 String ret = boundType(typeCodes); 195 if (ret != null) { 196 return ret; 197 } 198 for (String tc : typeCodes) { 199 if (Utilities.existsInList(tc, "string", "uri", "CodeableConcept", "Quantity", "CodeableReference")) { 200 return tc; 201 } 202 StructureDefinition sd = context.fetchTypeDefinition(tc); 203 if (sd != null) { 204 if (sd.hasExtension(ToolingExtensions.EXT_BINDING_METHOD)) { 205 return tc; 206 } 207 } 208 } 209 return null; 210 } 211 212 private void validateBinding(List<ValidationMessage> errors, Element binding, NodeStack stack, Set<String> typeCodes, boolean snapshot, String path) { 213 rule(errors, IssueType.BUSINESSRULE, stack.getLiteralPath(), !snapshot || bindableType(typeCodes) != null, I18nConstants.SD_ED_BIND_NO_BINDABLE, path, typeCodes.toString()); 214 if (!snapshot) { 215 Set<String> bindables = getListofBindableTypes(typeCodes); 216 hint(errors, IssueType.BUSINESSRULE, stack.getLiteralPath(), bindables.size() <= 1, I18nConstants.SD_ED_BIND_MULTIPLE_TYPES, path, typeCodes.toString()); 217 } 218 219 if (binding.hasChild("valueSet")) { 220 Element valueSet = binding.getNamedChild("valueSet"); 221 String ref = valueSet.hasPrimitiveValue() ? valueSet.primitiveValue() : valueSet.getNamedChildValue("reference"); 222 if (warning(errors, IssueType.BUSINESSRULE, stack.getLiteralPath(), !snapshot || ref != null, I18nConstants.SD_ED_SHOULD_BIND_WITH_VS, path)) { 223 Resource vs = context.fetchResource(Resource.class, ref); 224 225 // just because we can't resolve it directly doesn't mean that terminology server can't. Check with it 226 227 if (warning(errors, IssueType.BUSINESSRULE, stack.getLiteralPath(), vs != null || serverSupportsValueSet(ref), I18nConstants.SD_ED_BIND_UNKNOWN_VS, path, ref)) { 228 if (vs != null) { 229 rule(errors, IssueType.BUSINESSRULE, stack.getLiteralPath(), vs instanceof ValueSet, I18nConstants.SD_ED_BIND_NOT_VS, path, ref, vs.fhirType()); 230 } 231 } 232 } 233 } 234 } 235 236 private Set<String> getListofBindableTypes(Set<String> types) { 237 Set<String> res = new HashSet<>(); 238 for (String s : types) { 239 if (Utilities.existsInList(s, "code", "string", "url", "uri", "Coding", "CodeableConcept", "Quantity", "CodeableReference")) { 240 res.add(s); 241 } 242 } 243 return res; 244 } 245 246 private boolean serverSupportsValueSet(String ref) { 247 ValidationResult vr = context.validateCode(new ValidationOptions().checkValueSetOnly().setVsAsUrl().noClient(), new Coding("http://loinc.org", "5792-7", null), new ValueSet().setUrl(ref)); 248 return vr.getErrorClass() == null; 249 } 250 251 private void validateElementType(List<ValidationMessage> errors, Element type, NodeStack stack, StructureDefinition sd, String path) { 252 String code = type.getNamedChildValue("code"); 253 if (code == null && path != null) { 254 code = getTypeCodeFromSD(sd, path); 255 } 256 if (code != null) { 257 List<Element> profiles = type.getChildrenByName("profile"); 258 if (VersionUtilities.isR2Ver(context.getVersion()) || VersionUtilities.isR2BVer(context.getVersion()) ) { 259 for (Element profile : profiles) { 260 validateProfileTypeOrTarget(errors, profile, code, stack.push(profile, -1, null, null), path); 261 } 262 263 } else { 264 for (Element profile : profiles) { 265 validateTypeProfile(errors, profile, code, stack.push(profile, -1, null, null), path); 266 } 267 profiles = type.getChildrenByName("targetProfile"); 268 for (Element profile : profiles) { 269 validateTargetProfile(errors, profile, code, stack.push(profile, -1, null, null), path); 270 } 271 } 272 } 273 } 274 275 private void validateProfileTypeOrTarget(List<ValidationMessage> errors, Element profile, String code, NodeStack stack, String path) { 276 String p = profile.primitiveValue(); 277 StructureDefinition sd = context.fetchResource(StructureDefinition.class, p); 278 if (code.equals("Reference")) { 279 if (warning(errors, IssueType.EXCEPTION, stack.getLiteralPath(), sd != null, I18nConstants.SD_ED_TYPE_PROFILE_UNKNOWN, p)) { 280 StructureDefinition t = determineBaseType(sd); 281 if (t == null) { 282 rule(errors, IssueType.EXCEPTION, stack.getLiteralPath(), false, I18nConstants.SD_ED_TYPE_PROFILE_NOTYPE, p); 283 } else { 284 rule(errors, IssueType.EXCEPTION, stack.getLiteralPath(), sd.getKind() == StructureDefinitionKind.RESOURCE, I18nConstants.SD_ED_TYPE_PROFILE_WRONG, p, t, code, path); 285 } 286 } 287 } else { 288 if (sd == null ) { 289 sd = getXverExt(errors, stack.getLiteralPath(), profile, p); 290 } 291 if (warning(errors, IssueType.EXCEPTION, stack.getLiteralPath(), sd != null, I18nConstants.SD_ED_TYPE_PROFILE_UNKNOWN, p)) { 292 StructureDefinition t = determineBaseType(sd); 293 if (t == null) { 294 rule(errors, IssueType.EXCEPTION, stack.getLiteralPath(), false, I18nConstants.SD_ED_TYPE_PROFILE_NOTYPE, p); 295 } else { 296 rule(errors, IssueType.EXCEPTION, stack.getLiteralPath(), isInstanceOf(t, code), I18nConstants.SD_ED_TYPE_PROFILE_WRONG, p, t, code, path); 297 } 298 } 299 } 300 } 301 302 private String getTypeCodeFromSD(StructureDefinition sd, String path) { 303 ElementDefinition ed = null; 304 for (ElementDefinition t : sd.getSnapshot().getElement()) { 305 if (t.hasPath() && t.getPath().equals(path)) { 306 if (ed == null) { 307 ed = t; 308 } else { 309 return null; // more than one match, we don't know which is which 310 } 311 } 312 } 313 return ed != null && ed.getType().size() == 1 ? ed.getTypeFirstRep().getCode() : null; 314 } 315 316 private void validateTypeProfile(List<ValidationMessage> errors, Element profile, String code, NodeStack stack, String path) { 317 String p = profile.primitiveValue(); 318 StructureDefinition sd = context.fetchResource(StructureDefinition.class, p); 319 if (sd == null ) { 320 sd = getXverExt(errors, stack.getLiteralPath(), profile, p); 321 } 322 if (warning(errors, IssueType.EXCEPTION, stack.getLiteralPath(), sd != null, I18nConstants.SD_ED_TYPE_PROFILE_UNKNOWN, p)) { 323 StructureDefinition t = determineBaseType(sd); 324 if (t == null) { 325 rule(errors, IssueType.EXCEPTION, stack.getLiteralPath(), false, I18nConstants.SD_ED_TYPE_PROFILE_NOTYPE, p); 326 } else if (!isInstanceOf(t, code)) { 327 rule(errors, IssueType.EXCEPTION, stack.getLiteralPath(), false, I18nConstants.SD_ED_TYPE_PROFILE_WRONG, p, t, code, path); 328 } 329 } 330 } 331 332 private void validateTargetProfile(List<ValidationMessage> errors, Element profile, String code, NodeStack stack, String path) { 333 String p = profile.primitiveValue(); 334 StructureDefinition sd = context.fetchResource(StructureDefinition.class, p); 335 if (code.equals("Reference")) { 336 if (warning(errors, IssueType.EXCEPTION, stack.getLiteralPath(), sd != null, I18nConstants.SD_ED_TYPE_PROFILE_UNKNOWN, p)) { 337 StructureDefinition t = determineBaseType(sd); 338 if (t == null) { 339 rule(errors, IssueType.EXCEPTION, stack.getLiteralPath(), false, I18nConstants.SD_ED_TYPE_PROFILE_NOTYPE, p); 340 } else { 341 rule(errors, IssueType.EXCEPTION, stack.getLiteralPath(), sd.getKind() == StructureDefinitionKind.RESOURCE, I18nConstants.SD_ED_TYPE_PROFILE_WRONG_TARGET, p, t, code, path, "Resource"); 342 } 343 } 344 } else if (code.equals("canonical")) { 345 if (warning(errors, IssueType.EXCEPTION, stack.getLiteralPath(), sd != null, I18nConstants.SD_ED_TYPE_PROFILE_UNKNOWN, p)) { 346 StructureDefinition t = determineBaseType(sd); 347 if (t == null) { 348 rule(errors, IssueType.EXCEPTION, stack.getLiteralPath(), false, I18nConstants.SD_ED_TYPE_PROFILE_NOTYPE, p); 349 } else if (!VersionUtilities.isR5Ver(context.getVersion())) { 350 rule(errors, IssueType.EXCEPTION, stack.getLiteralPath(), VersionUtilities.getCanonicalResourceNames(context.getVersion()).contains(t.getType()) || "Resource".equals(t.getType()), I18nConstants.SD_ED_TYPE_PROFILE_WRONG_TARGET, p, t, code, path, "Canonical Resource"); 351 } else { 352 rule(errors, IssueType.EXCEPTION, stack.getLiteralPath(), VersionUtilities.getCanonicalResourceNames(context.getVersion()).contains(t.getType()), I18nConstants.SD_ED_TYPE_PROFILE_WRONG_TARGET, p, t, code, path, "Canonical Resource"); 353 } 354 } 355 } else { 356 rule(errors, IssueType.EXCEPTION, stack.getLiteralPath(), false, I18nConstants.SD_ED_TYPE_NO_TARGET_PROFILE, code); 357 } 358 } 359 360 private boolean isInstanceOf(StructureDefinition sd, String code) { 361 while (sd != null) { 362 if (sd.getType().equals(code)) { 363 return true; 364 } 365 if (sd.getUrl().equals(code)) { 366 return true; 367 } 368 sd = sd.hasBaseDefinition() ? context.fetchResource(StructureDefinition.class, sd.getBaseDefinition()) : null; 369 if (!(VersionUtilities.isR2Ver(context.getVersion()) || VersionUtilities.isR2BVer(context.getVersion())) && sd != null && !sd.getAbstract() && sd.getKind() != StructureDefinitionKind.LOGICAL) { 370 sd = null; 371 } 372 } 373 374 return false; 375 } 376 377 private StructureDefinition determineBaseType(StructureDefinition sd) { 378 while (sd != null && sd.getDerivation() == TypeDerivationRule.CONSTRAINT) { 379 sd = context.fetchResource(StructureDefinition.class, sd.getBaseDefinition()); 380 } 381 return sd; 382 } 383 384 private boolean hasMustSupportExtension(Element type) { 385 if ("true".equals(getExtensionValue(type, ToolingExtensions.EXT_MUST_SUPPORT))) { 386 return true; 387 } 388 List<Element> profiles = type.getChildrenByName("profile"); 389 for (Element profile : profiles) { 390 if ("true".equals(getExtensionValue(profile, ToolingExtensions.EXT_MUST_SUPPORT))) { 391 return true; 392 } 393 } 394 profiles = type.getChildrenByName("targetProfile"); 395 for (Element profile : profiles) { 396 if ("true".equals(getExtensionValue(profile, ToolingExtensions.EXT_MUST_SUPPORT))) { 397 return true; 398 } 399 } 400 return false; 401 } 402 403 private String getExtensionValue(Element element, String url) { 404 List<Element> extensions = element.getChildrenByName("extension"); 405 for (Element extension : extensions) { 406 if (url.equals(extension.getNamedChildValue("url"))) { 407 return extension.getNamedChildValue("value"); 408 } 409 } 410 return null; 411 } 412 413 private StructureDefinition loadAsSD(Element src) throws FHIRException, IOException { 414 ByteArrayOutputStream bs = new ByteArrayOutputStream(); 415 Manager.compose(context, src, bs, FhirFormat.JSON, OutputStyle.NORMAL, null); 416 if (VersionUtilities.isR2Ver(context.getVersion())) { 417 org.hl7.fhir.dstu2.model.Resource r2 = new org.hl7.fhir.dstu2.formats.JsonParser().parse(bs.toByteArray()); 418 return (StructureDefinition) VersionConvertorFactory_10_50.convertResource(r2); 419 } 420 if (VersionUtilities.isR2BVer(context.getVersion())) { 421 org.hl7.fhir.dstu2016may.model.Resource r2b = new org.hl7.fhir.dstu2016may.formats.JsonParser().parse(bs.toByteArray()); 422 return (StructureDefinition) VersionConvertorFactory_14_50.convertResource(r2b); 423 } 424 if (VersionUtilities.isR3Ver(context.getVersion())) { 425 org.hl7.fhir.dstu3.model.Resource r3 = new org.hl7.fhir.dstu3.formats.JsonParser().parse(bs.toByteArray()); 426 return (StructureDefinition) VersionConvertorFactory_30_50.convertResource(r3); 427 } 428 if (VersionUtilities.isR4Ver(context.getVersion())) { 429 org.hl7.fhir.r4.model.Resource r4 = new org.hl7.fhir.r4.formats.JsonParser().parse(bs.toByteArray()); 430 return (StructureDefinition) VersionConvertorFactory_40_50.convertResource(r4); 431 } 432 return (StructureDefinition) new org.hl7.fhir.r5.formats.JsonParser().parse(bs.toByteArray()); 433 } 434 435}