FirelyTeam / firely-net-sdk

The official Firely .NET SDK for HL7 FHIR
Other
821 stars 342 forks source link

Deserialization of Bundle.EntryComponent with a resource causes exception System.InvalidOperationException: 'Stack empty.' #2627

Closed cames-be closed 6 months ago

cames-be commented 10 months ago

Describe the bug A bug was introduced in https://github.com/FirelyTeam/firely-net-sdk/issues/2414 where only FHIR resources at the root element can be deserialized, but resources found as sub-elements of a non-resource component will fail. This was tested on both Bundle.EntryComponent and Parameters.ParameterComponent, but would apply to any element class that derives from BackBoneElement and contains a Resource.

When attempting to deserialize a Bundle.EntryComponent or a Parameters.ParameterComponent that has a valid Resource property set, the PathStack class will throw an exception. During deserialization the class will push on the _paths member when EnterElement is called and not push when EnterResource is called due to the _paths member already having an entry. Then, when ExitResource is called it will _paths.Pop() and then again when ExitElement is called which causes the exception.

To Reproduce Deserialization of Bundle.EntryComponent

    var options = new JsonSerializerOptions().ForFhir(ModelInfo.ModelInspector);

    var bundle_EntryComponent = new Bundle.EntryComponent()
    {
        Resource = new Basic()
        {
            Code = new CodeableConcept("http://terminology.hl7.org/CodeSystem/basic-resource-type", "referral")
        }
    };

    var jsonString = JsonSerializer.Serialize(bundle_EntryComponent, options);

    var seq = new ReadOnlySequence<byte>(Encoding.UTF8.GetBytes(jsonString));

    var newJsonReader = new Utf8JsonReader(seq, true, default);

    // System.InvalidOperationException: 'Stack empty.' thrown when attempting to deserialize
    var result = JsonSerializer.Deserialize<Bundle.EntryComponent>(ref newJsonReader, options);

Deserialization of Parameters.ParameterComponent

    var options = new JsonSerializerOptions().ForFhir(ModelInfo.ModelInspector);

    var bundle_EntryComponent = new Parameters.ParameterComponent()
    {
        Name = "name",
        Resource = new Basic()
        {
            Code = new CodeableConcept("http://terminology.hl7.org/CodeSystem/basic-resource-type", "referral")
        }
    };

    var jsonString = JsonSerializer.Serialize(bundle_EntryComponent, options);

    var seq = new ReadOnlySequence<byte>(Encoding.UTF8.GetBytes(jsonString));

    var newJsonReader = new Utf8JsonReader(seq, true, default);

    // System.InvalidOperationException: 'Stack empty.' thrown when attempting to deserialize
    var result = JsonSerializer.Deserialize<Parameters.ParameterComponent>(ref newJsonReader, options);

Expected behavior A valid deserialized object is returned and no exception is thrown.

Version used:

Additional context Prior to popping from the _paths Stack object, it should verify that there is an entry to pop. https://github.com/FirelyTeam/firely-net-sdk/blob/main/src/Hl7.Fhir.Base/Serialization/PathStack.cs#L103

if (_paths.Any())
    _paths.Pop();
brianpos commented 10 months ago

@cames-be I assume this was from your own code calling the serializers and not via a regular API or the FhirClient right? And those 2 snippits should be enough to have in a unit test to verify it?

cames-be commented 10 months ago

@brianpos

I assume this was from your own code calling the serializers and not via a regular API or the FhirClient right?

Yes, it is our code calling the serializers directly. We have run into memory pressure issues when deserializing large Bundle resources in their entirety, so instead we deserialize the individual Bundle.EntryComponent instances while processing.

And those 2 snippits should be enough to have in a unit test to verify it?

Yes, the two code examples were provided with unit tests in mind.