Stream: hapi
Topic: Getting all contained resources from an instance
Morten Ernebjerg (Jan 20 2021 at 13:48):
Hi HAPI crowd :wave: I am using HAPI 5.2 (with R4) and have some business logic that requires me to get all contained resources in a given resource. However, I discovered that naive way of doing this - using resource,getContained()
- does not always work. Specifically, if I construct an object X and refer to it in another resource Y using the setResource()
method (see this section of the HAPI docs ), then Y serializes with X as a contained resource but Y.getContained()
returns an empty array (code example below).
Is there some other generic way of retrieving all contained resources (i.e. everything pointed to by local #id
references) in a given instance, without having to loop over each field of type Reference
?
The code below illustrates the issue. It's Kotlin, so obj.prop = x
and z = obj.prop
correspond to obj.setProp(x)
and z = obj.getProp()
, respectively in Java,.
val obs: Observation = Observation() // New Observation instance
obs.status = Observation.ObservationStatus.FINAL
val cc = CodeableConcept()
cc.addCoding().display = "Random"
val pat = Patient() // New Patient object
// Set the Patient resource as the target of the reference in the subject element
obs.subject.resource = pat
// Serializes as Observation with a contained Patient
val obsAsJson = FhirContext.forR4().newJsonParser().encodeResourceToString(obs)
// ... but this array is empty
val containedArray = obs.contained
Daniel Venton (Jan 20 2021 at 14:20):
I translated your code to Java and got the patient contained resource.
Observation o = new Observation();
o.setStatus(Observation.ObservationStatus.FINAL);
CodeableConcept cc = new CodeableConcept();
cc.addCoding().setDisplay("Random");
Patient p = new Patient();
o.getSubject().setResource(p);
String json = FhirContext.forR4().newJsonParser().encodeResourceToString(o);
{"resourceType":"Observation","contained":[{"resourceType":"Patient","id":"1"}],"status":"final","subject":{"reference":"#1"}}
Morten Ernebjerg (Jan 20 2021 at 14:22):
Interesting, let me test again...
Morten Ernebjerg (Jan 20 2021 at 14:29):
Hm, still not working on my side. When I debug and inspect the object obs
, it has the Patient information inside the subject
property and the contained
property is an empty array. Consequently, obs.hasContained()
evaluates to false. So I was somehow assuming the information is only moved to contained
upon serialization. @Daniel Venton what does you object o
look like (as a Java object) after construction, is the contained
property also empty? (and thanks for looking at this, BTW!)
Lin Zhang (Jan 20 2021 at 14:50):
O.subject is of type Reference. Is it relevant to this question?
Daniel Venton (Jan 20 2021 at 15:12):
o.contained is null after every step until the .encode, at which time it becomes a [0] length array. I adjusted the code a bit. What appears to be happening is the observation you are building has "helpers" that help you get to a final state observation, but is not final until those helpers are exercised (serialization). The FHIR spec says subject is a reference, but HAPI allows you to put a resource in it (the HAPI helpers convert it to a contained resource later). However, it only appears to happen on serialization instead of on-the-fly. If you want your observation to be in a final state without the serialization step, then you'll have to generate the final state without relying on the helpers. subject.reference = "#1". p.id = "1" o.contained addresource(p). Post serialization/deserialzation the contained list is populated. New code illustrates that. Note that ser/deser is a heavy(slow) operation so probably not a production solution. @James Agnew may know of a way to fire the "helpers" without calling the serialization.
Observation build = new Observation();
build.setStatus(Observation.ObservationStatus.FINAL);
Patient p = new Patient();
build.getSubject().setResource(p);
IParser parser = FhirContext.forR4().newJsonParser();
String json = FhirContext.forR4().newJsonParser().encodeResourceToString(build);
Observation post = (Observation) parser.parseResource(json);
Morten Ernebjerg (Jan 21 2021 at 09:58):
@Daniel Venton Thanks for the analysis! - that makes sense. Indeed, for me the key thing is that I can extract contained resources from objects constructed by deserialization (i.e. in your code, post.getContained()
will be non-empty) - and that does indeed work :tada: . Nonetheless, I agree that it would be nice to have it work consistently, regardless of the object construction method - otherwise, it is hard to use in environments where one does not know the origin of objects. Perhaps it would be possible to lazily trigger the internal processing when the contained
element is accessed?
Morten Ernebjerg (Feb 12 2021 at 15:30):
@James Agnew Would this ( :point_up: - make behavior of getContained()
independent of object construction path) be worth a GitHub issue or am I misunderstanding smt.?
Last updated: Apr 12 2022 at 19:14 UTC