From 4ac65da2eefde6d3d660745b51252a997e52b8e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20Lindstr=C3=B6m?= Date: Tue, 28 May 2024 19:15:48 +0200 Subject: [PATCH] 5738: Skip alternate handling if already handled by previous step (#5971) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 5738: Skip alternate handling if already handled by previous step * Credit for #5738 --------- Co-authored-by: Stefan Lindström Co-authored-by: James Agnew --- .../java/ca/uhn/fhir/parser/JsonParser.java | 10 ++++- ...5971-fix-json-parsing-alternate-names.yaml | 6 +++ .../fhir/parser/JsonParserDstu2_1Test.java | 38 ++++++++++++++++++- .../uhn/fhir/parser/JsonParserDstu3Test.java | 38 +++++++++++++++++++ .../ca/uhn/fhir/parser/JsonParserR4Test.java | 36 ++++++++++++++++++ pom.xml | 5 +++ 6 files changed, 131 insertions(+), 2 deletions(-) create mode 100644 hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_4_0/5971-fix-json-parsing-alternate-names.yaml 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 +