5738: Skip alternate handling if already handled by previous step (#5971)

* 5738: Skip alternate handling if already handled by previous step

* Credit for #5738

---------

Co-authored-by: Stefan Lindström <stefan.lindstrom@ehalsomyndigheten.se>
Co-authored-by: James Agnew <jamesagnew@gmail.com>
This commit is contained in:
Stefan Lindström 2024-05-28 19:15:48 +02:00 committed by GitHub
parent a9815c8857
commit 4ac65da2ee
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 131 additions and 2 deletions

View File

@ -1359,9 +1359,17 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
String alternateName = keyIter.next(); String alternateName = keyIter.next();
if (alternateName.startsWith("_") && alternateName.length() > 1) { if (alternateName.startsWith("_") && alternateName.length() > 1) {
BaseJsonLikeValue nextValue = theObject.get(alternateName); BaseJsonLikeValue nextValue = theObject.get(alternateName);
String nextName = alternateName.substring(1);
if (nextValue != null) { if (nextValue != null) {
BaseJsonLikeValue nonAlternativeValue = theObject.get(nextName);
// Only alternate values with no corresponding "normal" value is unhandled from previous step.
if (nonAlternativeValue != null) {
continue;
}
if (nextValue.isObject()) { if (nextValue.isObject()) {
String nextName = alternateName.substring(1);
if (theObject.get(nextName) == null) { if (theObject.get(nextName) == null) {
theState.enteringNewElement(null, nextName); theState.enteringNewElement(null, nextName);
parseAlternates(nextValue, theState, alternateName, alternateName); parseAlternates(nextValue, theState, alternateName, alternateName);

View File

@ -0,0 +1,6 @@
---
type: fix
issue: 5971
title: "The JSON Parser failed to parse alternate names (e.g. `_family`) containing extensions if the
corresponding regular named element (e.g. `family`) was not present. Thanks to Stefan Lindström for
the pull request!"

View File

@ -46,6 +46,7 @@ import org.hl7.fhir.dstu2016may.model.Observation;
import org.hl7.fhir.dstu2016may.model.Observation.ObservationStatus; import org.hl7.fhir.dstu2016may.model.Observation.ObservationStatus;
import org.hl7.fhir.dstu2016may.model.Parameters; import org.hl7.fhir.dstu2016may.model.Parameters;
import org.hl7.fhir.dstu2016may.model.Patient; import org.hl7.fhir.dstu2016may.model.Patient;
import org.hl7.fhir.dstu2016may.model.Practitioner;
import org.hl7.fhir.dstu2016may.model.PrimitiveType; import org.hl7.fhir.dstu2016may.model.PrimitiveType;
import org.hl7.fhir.dstu2016may.model.Quantity; import org.hl7.fhir.dstu2016may.model.Quantity;
import org.hl7.fhir.dstu2016may.model.QuestionnaireResponse; import org.hl7.fhir.dstu2016may.model.QuestionnaireResponse;
@ -55,6 +56,7 @@ import org.hl7.fhir.dstu2016may.model.SimpleQuantity;
import org.hl7.fhir.dstu2016may.model.StringType; import org.hl7.fhir.dstu2016may.model.StringType;
import org.hl7.fhir.dstu2016may.model.UriType; import org.hl7.fhir.dstu2016may.model.UriType;
import org.hl7.fhir.dstu2016may.model.ValueSet; import org.hl7.fhir.dstu2016may.model.ValueSet;
import org.hl7.fhir.dstu2016may.model.codesystems.DataAbsentReason;
import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.instance.model.api.IIdType;
import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.AfterEach;
@ -63,7 +65,6 @@ import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor; import org.mockito.ArgumentCaptor;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
@ -78,6 +79,7 @@ import static org.hamcrest.Matchers.empty;
import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not; import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.stringContainsInOrder; import static org.hamcrest.Matchers.stringContainsInOrder;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNotNull;
@ -1901,6 +1903,40 @@ public class JsonParserDstu2_1Test {
assertEquals(expectedJson, patientString); assertEquals(expectedJson, patientString);
} }
@Test
public void testObjectWithBothPrimitiverAndArrayAlternatives() {
String resource = "{\n" +
" \"resourceType\": \"Practitioner\",\n" +
" \"id\": \"1\",\n" +
" \"name\": [{\n" +
" \"_family\": {\n" +
" \"extension\": [{\n" +
" \"url\": \"http://hl7.org/fhir/StructureDefinition/data-absent-reason\",\n" +
" \"valueString\": \"masked\"\n" +
" }\n" +
" ]\n" +
" },\n" +
" \"given\": [\n" +
" null\n" +
" ],\n" +
" \"_given\": [{\n" +
" \"extension\": [{\n" +
" \"url\": \"http://hl7.org/fhir/StructureDefinition/data-absent-reason\",\n" +
" \"valueString\": \"masked\"\n" +
" }\n" +
" ]\n" +
" }\n" +
" ]\n" +
" }\n" +
" ]\n" +
"}\n";
Practitioner practitioner = assertDoesNotThrow(() -> ourCtx.newJsonParser().parseResource(Practitioner.class, resource));
HumanName humanName = practitioner.getName().get(0);
StringType given = humanName.getGiven().get(0);
assertTrue(given.getExtension().stream().allMatch(ext -> DataAbsentReason.MASKED.toCode().equals(ext.getValue().primitiveValue())));
assertTrue(humanName.getFamily().get(0).getExtension().stream().allMatch(ext -> DataAbsentReason.MASKED.toCode().equals(ext.getValue().primitiveValue())));
}
@AfterAll @AfterAll
public static void afterClassClearContext() { public static void afterClassClearContext() {
TestUtil.randomizeLocaleAndTimezone(); TestUtil.randomizeLocaleAndTimezone();

View File

@ -63,6 +63,7 @@ import org.hl7.fhir.dstu3.model.Observation.ObservationStatus;
import org.hl7.fhir.dstu3.model.Organization; import org.hl7.fhir.dstu3.model.Organization;
import org.hl7.fhir.dstu3.model.Parameters; import org.hl7.fhir.dstu3.model.Parameters;
import org.hl7.fhir.dstu3.model.Patient; import org.hl7.fhir.dstu3.model.Patient;
import org.hl7.fhir.dstu3.model.Practitioner;
import org.hl7.fhir.dstu3.model.PrimitiveType; import org.hl7.fhir.dstu3.model.PrimitiveType;
import org.hl7.fhir.dstu3.model.Quantity; import org.hl7.fhir.dstu3.model.Quantity;
import org.hl7.fhir.dstu3.model.QuestionnaireResponse; import org.hl7.fhir.dstu3.model.QuestionnaireResponse;
@ -74,6 +75,7 @@ import org.hl7.fhir.dstu3.model.SimpleQuantity;
import org.hl7.fhir.dstu3.model.StringType; import org.hl7.fhir.dstu3.model.StringType;
import org.hl7.fhir.dstu3.model.UriType; import org.hl7.fhir.dstu3.model.UriType;
import org.hl7.fhir.dstu3.model.ValueSet; import org.hl7.fhir.dstu3.model.ValueSet;
import org.hl7.fhir.dstu3.model.codesystems.DataAbsentReason;
import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.instance.model.api.IIdType;
import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.AfterEach;
@ -101,6 +103,7 @@ import static org.hamcrest.Matchers.empty;
import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not; import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.stringContainsInOrder; import static org.hamcrest.Matchers.stringContainsInOrder;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNotNull;
@ -2548,6 +2551,41 @@ public class JsonParserDstu3Test {
assertThat(patientString, is(not(containsString("fhir_comment")))); assertThat(patientString, is(not(containsString("fhir_comment"))));
} }
@Test
public void testObjectWithBothPrimitiverAndArrayAlternatives() {
String resource = "{\n" +
" \"resourceType\": \"Practitioner\",\n" +
" \"id\": \"1\",\n" +
" \"name\": [{\n" +
" \"_family\": {\n" +
" \"extension\": [{\n" +
" \"url\": \"http://hl7.org/fhir/StructureDefinition/data-absent-reason\",\n" +
" \"valueString\": \"masked\"\n" +
" }\n" +
" ]\n" +
" },\n" +
" \"given\": [\n" +
" null\n" +
" ],\n" +
" \"_given\": [{\n" +
" \"extension\": [{\n" +
" \"url\": \"http://hl7.org/fhir/StructureDefinition/data-absent-reason\",\n" +
" \"valueString\": \"masked\"\n" +
" }\n" +
" ]\n" +
" }\n" +
" ]\n" +
" }\n" +
" ]\n" +
"}\n";
Practitioner practitioner = assertDoesNotThrow(() -> ourCtx.newJsonParser().parseResource(Practitioner.class, resource));
HumanName humanName = practitioner.getName().get(0);
StringType given = humanName.getGiven().get(0);
assertTrue(given.getExtension().stream().allMatch(ext -> DataAbsentReason.MASKED.toCode().equals(ext.getValue().primitiveValue())));
assertTrue(humanName.getFamilyElement().getExtension().stream().allMatch(ext -> DataAbsentReason.MASKED.toCode().equals(ext.getValue().primitiveValue())));
}
@AfterAll @AfterAll
public static void afterClassClearContext() { public static void afterClassClearContext() {
TestUtil.randomizeLocaleAndTimezone(); TestUtil.randomizeLocaleAndTimezone();

View File

@ -45,6 +45,7 @@ import org.hl7.fhir.r4.model.QuestionnaireResponse;
import org.hl7.fhir.r4.model.Reference; import org.hl7.fhir.r4.model.Reference;
import org.hl7.fhir.r4.model.StringType; import org.hl7.fhir.r4.model.StringType;
import org.hl7.fhir.r4.model.Type; import org.hl7.fhir.r4.model.Type;
import org.hl7.fhir.r4.model.codesystems.DataAbsentReason;
import org.hl7.fhir.utilities.xhtml.XhtmlNode; import org.hl7.fhir.utilities.xhtml.XhtmlNode;
import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.AfterEach;
@ -73,6 +74,7 @@ import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue; import static org.hamcrest.Matchers.notNullValue;
import static org.hamcrest.Matchers.stringContainsInOrder; import static org.hamcrest.Matchers.stringContainsInOrder;
import static org.hamcrest.core.IsNot.not; import static org.hamcrest.core.IsNot.not;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertNotEquals;
@ -1190,6 +1192,40 @@ public class JsonParserR4Test extends BaseTest {
assertEquals(expected, actual); assertEquals(expected, actual);
} }
@Test
public void testObjectWithBothPrimitiverAndArrayAlternatives() {
String resource = "{\n" +
" \"resourceType\": \"Practitioner\",\n" +
" \"id\": \"1\",\n" +
" \"name\": [{\n" +
" \"_family\": {\n" +
" \"extension\": [{\n" +
" \"url\": \"http://hl7.org/fhir/StructureDefinition/data-absent-reason\",\n" +
" \"valueString\": \"masked\"\n" +
" }\n" +
" ]\n" +
" },\n" +
" \"given\": [\n" +
" null\n" +
" ],\n" +
" \"_given\": [{\n" +
" \"extension\": [{\n" +
" \"url\": \"http://hl7.org/fhir/StructureDefinition/data-absent-reason\",\n" +
" \"valueString\": \"masked\"\n" +
" }\n" +
" ]\n" +
" }\n" +
" ]\n" +
" }\n" +
" ]\n" +
"}\n";
Practitioner practitioner = assertDoesNotThrow(() -> ourCtx.newJsonParser().parseResource(Practitioner.class, resource));
HumanName humanName = practitioner.getNameFirstRep();
StringType given = humanName.getGiven().get(0);
assertTrue(given.getExtension().stream().allMatch(ext -> DataAbsentReason.MASKED.toCode().equals(ext.getValue().primitiveValue())));
assertTrue(humanName.getFamilyElement().getExtension().stream().allMatch(ext -> DataAbsentReason.MASKED.toCode().equals(ext.getValue().primitiveValue())));
}
@Test @Test
public void testEncodeToString_GeneralPurposeDataType() { public void testEncodeToString_GeneralPurposeDataType() {
HumanName name = new HumanName(); HumanName name = new HumanName();

View File

@ -917,6 +917,11 @@
<id>subigre</id> <id>subigre</id>
<name>Renaud Subiger</name> <name>Renaud Subiger</name>
</developer> </developer>
<developer>
<id>stefan-lindstrom</id>
<name>Stefan Lindström</name>
<organization>Softhouse AB</organization>
</developer>
</developers> </developers>
<licenses> <licenses>