diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/JsonParser.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/JsonParser.java
index 453b6c7606b..e9e89ff286d 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/JsonParser.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/JsonParser.java
@@ -1359,9 +1359,17 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
String alternateName = keyIter.next();
if (alternateName.startsWith("_") && alternateName.length() > 1) {
BaseJsonLikeValue nextValue = theObject.get(alternateName);
+ String nextName = alternateName.substring(1);
+
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()) {
- String nextName = alternateName.substring(1);
if (theObject.get(nextName) == null) {
theState.enteringNewElement(null, nextName);
parseAlternates(nextValue, theState, alternateName, alternateName);
diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_4_0/5971-fix-json-parsing-alternate-names.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_4_0/5971-fix-json-parsing-alternate-names.yaml
new file mode 100644
index 00000000000..5251676e48f
--- /dev/null
+++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_4_0/5971-fix-json-parsing-alternate-names.yaml
@@ -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!"
diff --git a/hapi-fhir-structures-dstu2.1/src/test/java/ca/uhn/fhir/parser/JsonParserDstu2_1Test.java b/hapi-fhir-structures-dstu2.1/src/test/java/ca/uhn/fhir/parser/JsonParserDstu2_1Test.java
index 1b97e5877e0..5d34ec60272 100644
--- a/hapi-fhir-structures-dstu2.1/src/test/java/ca/uhn/fhir/parser/JsonParserDstu2_1Test.java
+++ b/hapi-fhir-structures-dstu2.1/src/test/java/ca/uhn/fhir/parser/JsonParserDstu2_1Test.java
@@ -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.Parameters;
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.Quantity;
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.UriType;
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.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
@@ -63,7 +65,6 @@ import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import java.math.BigDecimal;
-import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
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.not;
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.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
@@ -1901,6 +1903,40 @@ public class JsonParserDstu2_1Test {
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
public static void afterClassClearContext() {
TestUtil.randomizeLocaleAndTimezone();
diff --git a/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/parser/JsonParserDstu3Test.java b/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/parser/JsonParserDstu3Test.java
index cfa190aa1db..ba8ddac128b 100644
--- a/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/parser/JsonParserDstu3Test.java
+++ b/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/parser/JsonParserDstu3Test.java
@@ -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.Parameters;
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.Quantity;
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.UriType;
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.junit.jupiter.api.AfterAll;
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.not;
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.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
@@ -2548,6 +2551,41 @@ public class JsonParserDstu3Test {
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
public static void afterClassClearContext() {
TestUtil.randomizeLocaleAndTimezone();
diff --git a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/parser/JsonParserR4Test.java b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/parser/JsonParserR4Test.java
index 8964bd244f2..dccc97313e6 100644
--- a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/parser/JsonParserR4Test.java
+++ b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/parser/JsonParserR4Test.java
@@ -45,6 +45,7 @@ import org.hl7.fhir.r4.model.QuestionnaireResponse;
import org.hl7.fhir.r4.model.Reference;
import org.hl7.fhir.r4.model.StringType;
import org.hl7.fhir.r4.model.Type;
+import org.hl7.fhir.r4.model.codesystems.DataAbsentReason;
import org.hl7.fhir.utilities.xhtml.XhtmlNode;
import org.junit.jupiter.api.AfterAll;
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.stringContainsInOrder;
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.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
@@ -1190,6 +1192,40 @@ public class JsonParserR4Test extends BaseTest {
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
public void testEncodeToString_GeneralPurposeDataType() {
HumanName name = new HumanName();
diff --git a/pom.xml b/pom.xml
index 591dc15cbea..8a205938248 100644
--- a/pom.xml
+++ b/pom.xml
@@ -917,6 +917,11 @@
subigre
Renaud Subiger
+
+ stefan-lindstrom
+ Stefan Lindström
+ Softhouse AB
+