From f4ff33dc2104a14b85d58ae4869307fc65eade7c Mon Sep 17 00:00:00 2001 From: Toby Plunkett Date: Thu, 5 Feb 2026 17:16:10 +0000 Subject: [PATCH 1/6] HDPI-4131 Submit entire address into draft instead of merging individual fields. --- .../pcs/ccd/service/DraftCaseJsonMerger.java | 62 ++++++++++++++++--- 1 file changed, 54 insertions(+), 8 deletions(-) diff --git a/src/main/java/uk/gov/hmcts/reform/pcs/ccd/service/DraftCaseJsonMerger.java b/src/main/java/uk/gov/hmcts/reform/pcs/ccd/service/DraftCaseJsonMerger.java index c494c5797a..c33f0226d9 100644 --- a/src/main/java/uk/gov/hmcts/reform/pcs/ccd/service/DraftCaseJsonMerger.java +++ b/src/main/java/uk/gov/hmcts/reform/pcs/ccd/service/DraftCaseJsonMerger.java @@ -3,28 +3,38 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Component; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + @Component public class DraftCaseJsonMerger { + private static final List ADDRESS_FIELDS = List.of( + "AddressLine1", + "AddressLine2", + "AddressLine3", + "PostTown", + "County", + "PostCode", + "Country" + ); + private final ObjectMapper objectMapper; public DraftCaseJsonMerger(@Qualifier("draftCaseDataObjectMapper") ObjectMapper objectMapper) { this.objectMapper = objectMapper; } - /** - * Peforms a merge of case data from `patchJson` onto `baseJson`. - * @param baseJson The JSON string to merge onto - * @param patchJson The updated JSON data to apply - * @return A string containing the merged JSON - * @throws JsonProcessingException If the JSON strings could not be parsed or written back - * to a string value after being combined - */ public String mergeJson(String baseJson, String patchJson) throws JsonProcessingException { JsonNode base = objectMapper.readValue(baseJson, JsonNode.class); + JsonNode patch = objectMapper.readValue(patchJson, JsonNode.class); + + clearAddressFieldsInBase(base, patch); JsonNode merged = objectMapper.readerForUpdating(base) .readValue(patchJson); @@ -32,4 +42,40 @@ public String mergeJson(String baseJson, String patchJson) throws JsonProcessing return objectMapper.writeValueAsString(merged); } + /** + * Clears address fields in the base JSON where the patch contains an address object. + * Fully replaces the old address rather than merging individual fields. + */ + private void clearAddressFieldsInBase(JsonNode base, JsonNode patch) { + clearAddressFieldsRecursively(base, patch); + } + + private void clearAddressFieldsRecursively(JsonNode base, JsonNode patch) { + if (!patch.isObject() || !base.isObject()) { + return; + } + + Iterator> fields = patch.fields(); + while (fields.hasNext()) { + Map.Entry field = fields.next(); + String fieldName = field.getKey(); + JsonNode patchChild = field.getValue(); + + if (base.has(fieldName)) { + JsonNode baseChild = base.get(fieldName); + + if ("address".equalsIgnoreCase(fieldName) && patchChild.isObject() && baseChild.isObject()) { + clearAddressFields((ObjectNode) baseChild); + } else { + clearAddressFieldsRecursively(baseChild, patchChild); + } + } + } + } + + private void clearAddressFields(ObjectNode addressNode) { + for (String addressField : ADDRESS_FIELDS) { + addressNode.remove(addressField); + } + } } From 128eb6d9191c4c273f7aafc0978859b586b3e5d1 Mon Sep 17 00:00:00 2001 From: Toby Plunkett Date: Thu, 5 Feb 2026 17:22:27 +0000 Subject: [PATCH 2/6] HDPI-4131 Add back removed comments --- .../hmcts/reform/pcs/ccd/service/DraftCaseJsonMerger.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/main/java/uk/gov/hmcts/reform/pcs/ccd/service/DraftCaseJsonMerger.java b/src/main/java/uk/gov/hmcts/reform/pcs/ccd/service/DraftCaseJsonMerger.java index c33f0226d9..7b5156090a 100644 --- a/src/main/java/uk/gov/hmcts/reform/pcs/ccd/service/DraftCaseJsonMerger.java +++ b/src/main/java/uk/gov/hmcts/reform/pcs/ccd/service/DraftCaseJsonMerger.java @@ -30,6 +30,14 @@ public DraftCaseJsonMerger(@Qualifier("draftCaseDataObjectMapper") ObjectMapper this.objectMapper = objectMapper; } + /** + * Peforms a merge of case data from `patchJson` onto `baseJson`. + * @param baseJson The JSON string to merge onto + * @param patchJson The updated JSON data to apply + * @return A string containing the merged JSON + * @throws JsonProcessingException If the JSON strings could not be parsed or written back + * to a string value after being combined + */ public String mergeJson(String baseJson, String patchJson) throws JsonProcessingException { JsonNode base = objectMapper.readValue(baseJson, JsonNode.class); JsonNode patch = objectMapper.readValue(patchJson, JsonNode.class); From fa1da077ee05d54f534d16378128c4a4e4bcfb34 Mon Sep 17 00:00:00 2001 From: amandarichards Date: Mon, 23 Mar 2026 11:56:36 +0000 Subject: [PATCH 3/6] authmode local --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index df94e646fa..60343fff68 100644 --- a/build.gradle +++ b/build.gradle @@ -512,7 +512,7 @@ tasks.named('integration') { tasks.withType(CftlibExec).configureEach { group = 'ccd tasks' - authMode = AuthMode.AAT + authMode = AuthMode.Local //Attach mockito agent for inline mocking to remove warning in logs classpath += configurations.mockitoAgentConfig From 9e4c85b77178adf1283556fc5cc166234b2d936b Mon Sep 17 00:00:00 2001 From: amandarichards Date: Mon, 23 Mar 2026 15:25:29 +0000 Subject: [PATCH 4/6] add test --- .../ccd/service/DraftCaseJsonMergerTest.java | 82 ++++++++++++++++++- 1 file changed, 81 insertions(+), 1 deletion(-) diff --git a/src/test/java/uk/gov/hmcts/reform/pcs/ccd/service/DraftCaseJsonMergerTest.java b/src/test/java/uk/gov/hmcts/reform/pcs/ccd/service/DraftCaseJsonMergerTest.java index be649c4cec..ed95567932 100644 --- a/src/test/java/uk/gov/hmcts/reform/pcs/ccd/service/DraftCaseJsonMergerTest.java +++ b/src/test/java/uk/gov/hmcts/reform/pcs/ccd/service/DraftCaseJsonMergerTest.java @@ -14,9 +14,11 @@ import uk.gov.hmcts.reform.pcs.ccd.type.DynamicStringListElement; import uk.gov.hmcts.reform.pcs.config.JacksonConfiguration; +import com.fasterxml.jackson.databind.JsonNode; +import static org.assertj.core.api.Assertions.assertThat; + import java.util.List; -import static org.assertj.core.api.Assertions.assertThat; class DraftCaseJsonMergerTest { @@ -114,4 +116,82 @@ private DynamicStringListElement createListElement(ClaimantType value) { return DynamicStringListElement.builder().code(value.name()).label(value.getLabel()).build(); } + + + @Test + void shouldClearAddressFieldsWhenPatchContainsAddress() throws Exception { + String baseJson = """ + { + "party": { + "address": { + "AddressLine1": "Old Line 1", + "AddressLine2": "Old Line 2", + "AddressLine3": "Old Line 3", + "PostTown": "Old Town", + "County": "Old County", + "PostCode": "OLD123", + "Country": "Old Country" + } + } + } + """; + + String patchJson = """ + { + "party": { + "address": { + "AddressLine1": "New Line 1" + } + } + } + """; + + // When + String mergedJson = underTest.mergeJson(baseJson, patchJson); + JsonNode merged = objectMapper.readTree(mergedJson); + JsonNode address = merged.at("/party/address"); + + // Then: new field present + assertThat(address.get("AddressLine1").asText()).isEqualTo("New Line 1"); + + // All old fields removed + assertThat(address.has("AddressLine2")).isFalse(); + assertThat(address.has("AddressLine3")).isFalse(); + assertThat(address.has("PostTown")).isFalse(); + assertThat(address.has("County")).isFalse(); + assertThat(address.has("PostCode")).isFalse(); + assertThat(address.has("Country")).isFalse(); + } + + @Test + void shouldNotClearAddressIfPatchDoesNotContainAddress() throws Exception { + String baseJson = """ + { + "party": { + "address": { + "AddressLine1": "Line 1", + "PostCode": "ABC123" + } + } + } + """; + + String patchJson = """ + { + "party": { + "name": "John" + } + } + """; + + // When + String mergedJson = underTest.mergeJson(baseJson, patchJson); + JsonNode merged = objectMapper.readTree(mergedJson); + JsonNode address = merged.at("/party/address"); + + // Then: address untouched + assertThat(address.get("AddressLine1").asText()).isEqualTo("Line 1"); + assertThat(address.get("PostCode").asText()).isEqualTo("ABC123"); + } + } From 2e7c3d097cecf2dd90e98a06aebe56d21594eab3 Mon Sep 17 00:00:00 2001 From: amandarichards Date: Tue, 24 Mar 2026 10:35:10 +0000 Subject: [PATCH 5/6] fix minors --- .../reform/pcs/ccd/service/DraftCaseJsonMerger.java | 10 +++------- .../pcs/ccd/service/DraftCaseJsonMergerTest.java | 7 ++++--- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/src/main/java/uk/gov/hmcts/reform/pcs/ccd/service/DraftCaseJsonMerger.java b/src/main/java/uk/gov/hmcts/reform/pcs/ccd/service/DraftCaseJsonMerger.java index 7b5156090a..954b50b7fa 100644 --- a/src/main/java/uk/gov/hmcts/reform/pcs/ccd/service/DraftCaseJsonMerger.java +++ b/src/main/java/uk/gov/hmcts/reform/pcs/ccd/service/DraftCaseJsonMerger.java @@ -42,7 +42,7 @@ public String mergeJson(String baseJson, String patchJson) throws JsonProcessing JsonNode base = objectMapper.readValue(baseJson, JsonNode.class); JsonNode patch = objectMapper.readValue(patchJson, JsonNode.class); - clearAddressFieldsInBase(base, patch); + clearAddressFieldsRecursively(base, patch); JsonNode merged = objectMapper.readerForUpdating(base) .readValue(patchJson); @@ -54,16 +54,12 @@ public String mergeJson(String baseJson, String patchJson) throws JsonProcessing * Clears address fields in the base JSON where the patch contains an address object. * Fully replaces the old address rather than merging individual fields. */ - private void clearAddressFieldsInBase(JsonNode base, JsonNode patch) { - clearAddressFieldsRecursively(base, patch); - } - private void clearAddressFieldsRecursively(JsonNode base, JsonNode patch) { if (!patch.isObject() || !base.isObject()) { return; } - Iterator> fields = patch.fields(); + Iterator> fields = patch.properties().iterator(); while (fields.hasNext()) { Map.Entry field = fields.next(); String fieldName = field.getKey(); @@ -72,7 +68,7 @@ private void clearAddressFieldsRecursively(JsonNode base, JsonNode patch) { if (base.has(fieldName)) { JsonNode baseChild = base.get(fieldName); - if ("address".equalsIgnoreCase(fieldName) && patchChild.isObject() && baseChild.isObject()) { + if ("address".equalsIgnoreCase(fieldName) && patchChild.isObject() && baseChild instanceof ObjectNode) { clearAddressFields((ObjectNode) baseChild); } else { clearAddressFieldsRecursively(baseChild, patchChild); diff --git a/src/test/java/uk/gov/hmcts/reform/pcs/ccd/service/DraftCaseJsonMergerTest.java b/src/test/java/uk/gov/hmcts/reform/pcs/ccd/service/DraftCaseJsonMergerTest.java index ed95567932..9305999c83 100644 --- a/src/test/java/uk/gov/hmcts/reform/pcs/ccd/service/DraftCaseJsonMergerTest.java +++ b/src/test/java/uk/gov/hmcts/reform/pcs/ccd/service/DraftCaseJsonMergerTest.java @@ -15,10 +15,11 @@ import uk.gov.hmcts.reform.pcs.config.JacksonConfiguration; import com.fasterxml.jackson.databind.JsonNode; -import static org.assertj.core.api.Assertions.assertThat; import java.util.List; +import static org.assertj.core.api.Assertions.assertThat; + class DraftCaseJsonMergerTest { @@ -116,10 +117,9 @@ private DynamicStringListElement createListElement(ClaimantType value) { return DynamicStringListElement.builder().code(value.name()).label(value.getLabel()).build(); } - - @Test void shouldClearAddressFieldsWhenPatchContainsAddress() throws Exception { + //Given String baseJson = """ { "party": { @@ -165,6 +165,7 @@ void shouldClearAddressFieldsWhenPatchContainsAddress() throws Exception { @Test void shouldNotClearAddressIfPatchDoesNotContainAddress() throws Exception { + //Given String baseJson = """ { "party": { From 4ad43bf4c6706d5a1213efe4949d745ed8360ca6 Mon Sep 17 00:00:00 2001 From: amandarichards Date: Thu, 26 Mar 2026 08:43:38 +0000 Subject: [PATCH 6/6] use createObjectNode --- .../pcs/ccd/service/DraftCaseJsonMerger.java | 36 ++++++++----------- 1 file changed, 14 insertions(+), 22 deletions(-) diff --git a/src/main/java/uk/gov/hmcts/reform/pcs/ccd/service/DraftCaseJsonMerger.java b/src/main/java/uk/gov/hmcts/reform/pcs/ccd/service/DraftCaseJsonMerger.java index 954b50b7fa..17f7fb4b7e 100644 --- a/src/main/java/uk/gov/hmcts/reform/pcs/ccd/service/DraftCaseJsonMerger.java +++ b/src/main/java/uk/gov/hmcts/reform/pcs/ccd/service/DraftCaseJsonMerger.java @@ -8,21 +8,14 @@ import org.springframework.stereotype.Component; import java.util.Iterator; -import java.util.List; import java.util.Map; +import java.util.Set; @Component public class DraftCaseJsonMerger { - private static final List ADDRESS_FIELDS = List.of( - "AddressLine1", - "AddressLine2", - "AddressLine3", - "PostTown", - "County", - "PostCode", - "Country" - ); + private static final Set REPLACE_FIELDS = Set.of("address"); + private final ObjectMapper objectMapper; @@ -42,7 +35,7 @@ public String mergeJson(String baseJson, String patchJson) throws JsonProcessing JsonNode base = objectMapper.readValue(baseJson, JsonNode.class); JsonNode patch = objectMapper.readValue(patchJson, JsonNode.class); - clearAddressFieldsRecursively(base, patch); + applyReplaceRulesRecursively(base, patch); JsonNode merged = objectMapper.readerForUpdating(base) .readValue(patchJson); @@ -51,10 +44,10 @@ public String mergeJson(String baseJson, String patchJson) throws JsonProcessing } /** - * Clears address fields in the base JSON where the patch contains an address object. - * Fully replaces the old address rather than merging individual fields. + * Clears fields in the base JSON where the patch contains an address object. + * Fully replaces the old fields rather than merging individual fields. */ - private void clearAddressFieldsRecursively(JsonNode base, JsonNode patch) { + private void applyReplaceRulesRecursively(JsonNode base, JsonNode patch) { if (!patch.isObject() || !base.isObject()) { return; } @@ -68,18 +61,17 @@ private void clearAddressFieldsRecursively(JsonNode base, JsonNode patch) { if (base.has(fieldName)) { JsonNode baseChild = base.get(fieldName); - if ("address".equalsIgnoreCase(fieldName) && patchChild.isObject() && baseChild instanceof ObjectNode) { - clearAddressFields((ObjectNode) baseChild); + if (REPLACE_FIELDS.contains(fieldName.toLowerCase()) + && patchChild.isObject() + && base instanceof ObjectNode) { + + ((ObjectNode) base).set(fieldName, objectMapper.createObjectNode()); + } else { - clearAddressFieldsRecursively(baseChild, patchChild); + applyReplaceRulesRecursively(baseChild, patchChild); } } } } - private void clearAddressFields(ObjectNode addressNode) { - for (String addressField : ADDRESS_FIELDS) { - addressNode.remove(addressField); - } - } }