hapifhir / hapi-fhir

🔥 HAPI FHIR - Java API for HL7 FHIR Clients and Servers
http://hapifhir.io
Apache License 2.0
2k stars 1.31k forks source link

Adding support for more complex custom structure-aware parsing #6168

Open qligier opened 1 month ago

qligier commented 1 month ago

Feature request I would like to add a feature to allow guiding the parser in choosing the right custom structure to deserialize a resource, when dealing with existing children in a custom structure. When a resource child is a Reference, the parser must decide into which class it will deserialize the referenced resource, and there's currently not enough possibility to guide the parser into choosing the right class, when dealing with more complex Implementation Guides.

Rational In Switzerland, we have a few Implementation Guides that are gaining traction and starting to be used nationwide. We would like to provide a Java class implementation based on HAPI models for each profile, to help implementers create and read FHIR resources. This means creating/overriding getters and setters to be aligned to the IG specifications, declaring profiles, extensions, and custom data types when needed. For example, when a Coding is bound to a value set (with required), we can create an enum from the value set, and use it in the field getter and setter methods.

The main issue we have is to parse resources to the right Java class. HAPI provides multiple mechanisms to customize that, but none are capable of handling all use-cases:

In all of our cases currently, the profile can be determined from the parent resource: the reference has been constrained to specific profiles.

Technical solution proposal I am considering creating a new HAPI annotation to be used by the parser, something like @CustomTypeForChild.

Since we are extending an existing HAPI model, we don't have access to the original field, so the annotation would be applied to the whole class. It should take one parameter to specify which child it applies to.

Then, I would like to add two exclusive parameters to specify the custom type to use:

  1. one parameter would simply take an array of class references: { MyPatient.class, MyPractitioner.class }. The parser would use this list the same way it uses the prefer types list;
  2. the second parameter would allow specifying a method that would help determine the right class to use. The parser would call it with the current context (to be determined what exactly), and use the resulting class to continue deserialisation. This is helpful when the reference is in a slice: it may be constrained differently in the slices, and the logic to determine the right class is a bit more complex.
@ResourceDef(name = "Composition", profile = "http://example.com/StructureDefinition/my-composition")
@CustomTypeForChild(child = "author", type = { MyPatient.class, MyPractitioner.class })
@CustomTypeForChild(child = "section.entry", advisor = "getEntryTypeAdvice")
class MyComposition {

  public /*static? */ Class<?> getEntryTypeAdvice(final Object parent) {
    if (parent instanceof final SectionComponent section) {
      if ("1234".equals(section.getCode().getCodingFirstRep().getCode())) {
        return MyConditionType1.class;
      } else if ("789".equals(section.getCode().getCodingFirstRep().getCode())) {
        return MyConditionType2.class;
      }
      return null; // Use the 'prefer types' or default type
    }
    throw new IllegalArgumentException();
  }
}

Identified entry point: enteringNewElement methods in ParserState

I can work on this and provide a PR, if the new feature and technical proposal are approved.

grahamegrieve commented 1 month ago

I think that this is a bad idea. Profiles are not specialisations. Profiles are sets of restrictions on the content, and you can't tell at parsing time which profile to use, for several important reasons. Most importantly, there can be more than one possible profile. And also, profiles have super complicated slicing rules, beyond the scope of the parser to implement (in fact, can only choose once everything has been parsed).

I won't be helping with this bad idea, or approving any PRs related to it (which would be if they are in core)

But have you looked at ProfileElement in R5? My intention is to post this back to R4 when I have time, and I think it does what you actually care about, and does it properly