diff --git a/CHANGELOG.md b/CHANGELOG.md index 8894b9f05..fffa4ed7a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ # Prowide ISO 20022 - CHANGELOG +#### 10.1.8 - SNAPSHOT + * (GH-118) Added `toJsonV9` and `fromJsonV9` in the `AbstractMX` to handle retro-compatibility with Java 8 DateTime elements in JSON serialization + #### 10.1.7 - August 2024 * (PW-1958) Fixed the `DefaultMxMetadataStrategy` NPE issue when the amount values are null diff --git a/build.gradle b/build.gradle index 1411eb8c9..2b72d196c 100644 --- a/build.gradle +++ b/build.gradle @@ -345,9 +345,8 @@ dependencies { //implementation 'com.google.code.gson:gson:2.9.0' // jaxb is required because it is no longer included in the JDK - implementation 'com.sun.xml.bind:jaxb-impl:4.0.5' // bind-api is included transitively by jaxb-impl - // implementation 'jakarta.xml.bind:jakarta.xml.bind-api:4.0.2' + implementation 'com.sun.xml.bind:jaxb-impl:4.0.5' } project.ext { diff --git a/iso20022-core/src/main/java/com/prowidesoftware/swift/model/mx/AbstractMX.java b/iso20022-core/src/main/java/com/prowidesoftware/swift/model/mx/AbstractMX.java index 601a94c78..96c9f7f38 100644 --- a/iso20022-core/src/main/java/com/prowidesoftware/swift/model/mx/AbstractMX.java +++ b/iso20022-core/src/main/java/com/prowidesoftware/swift/model/mx/AbstractMX.java @@ -26,6 +26,7 @@ import com.prowidesoftware.swift.model.MxId; import com.prowidesoftware.swift.model.mt.AbstractMT; import com.prowidesoftware.swift.model.mx.adapters.*; +import com.prowidesoftware.swift.model.mx.adapters.v9.V9DateTimeJsonAdapter; import com.prowidesoftware.swift.utils.Lib; import jakarta.xml.bind.JAXBContext; import jakarta.xml.bind.annotation.XmlTransient; @@ -37,6 +38,7 @@ import java.util.Objects; import java.util.logging.Level; import java.util.logging.Logger; +import javax.xml.datatype.XMLGregorianCalendar; import javax.xml.transform.Source; import javax.xml.transform.dom.DOMResult; import javax.xml.transform.stream.StreamSource; @@ -260,7 +262,7 @@ public static AbstractMX parse(final String xml, MxId id, final MxReadConfigurat * @since 7.10.3 */ protected static T fromJson(String json, Class classOfT) { - final Gson gson = getGsonBuilderWithCustomAdapters(); + final Gson gson = getGsonBuilderWithV10Adapters(); return gson.fromJson(json, classOfT); } @@ -272,7 +274,41 @@ protected static T fromJson(String json, Class classOfT) { * @since 7.10.3 */ public static AbstractMX fromJson(String json) { - final Gson gson = getGsonBuilderWithCustomAdapters(); + final Gson gson = getGsonBuilderWithV10Adapters(); + return gson.fromJson(json, AbstractMX.class); + } + + /** + * Deserializes the given JSON string into an object of the specified class, using version 9 (Java 8) adapters. + * + *

This method ensures compatibility by checking for date-time fields stored in JSON as + * {@link XMLGregorianCalendar} based json elements and converting them into {@link OffsetDateTime}. The Gson + * adapters (version 9) manage both Java 8 and Java 11 formats. + * + * @param the type of the object to be deserialized + * @param json a JSON representation of the object + * @param classOfT the class of the object to be deserialized + * @return a deserialized instance of the specified class, or null if the JSON string cannot be parsed. + * @since 10.1.8 + */ + protected static T fromJsonV9(String json, Class classOfT) { + final Gson gson = getGsonBuilderWithV9Adapters(); + return gson.fromJson(json, classOfT); + } + + /** + * Deserializes the given JSON string into a specific MX message object, using version 9 (Java 8) adapters. + * + *

This method ensures compatibility with older versions by converting + * {@link XMLGregorianCalendar} based json elements in JSON to {@link OffsetDateTime} if needed. The + * deserialization uses custom adapters (version 9) that handle both formats for date-time fields. + * + * @param json a JSON representation of an MX message + * @return a deserialized instance of {@link AbstractMX}, or null if the JSON string cannot be parsed. + * @since 10.1.8 + */ + public static AbstractMX fromJsonV9(String json) { + final Gson gson = getGsonBuilderWithV9Adapters(); return gson.fromJson(json, AbstractMX.class); } @@ -628,19 +664,45 @@ public Element element(JAXBContext inputContext) { @Override public String toJson() { // we use AbstractMX and not this.getClass() in order to force usage of the adapter - final Gson gson = getGsonBuilderWithCustomAdapters(); + final Gson gson = getGsonBuilderWithV10Adapters(); return gson.toJson(this, AbstractMX.class); } - private static Gson getGsonBuilderWithCustomAdapters() { - final Gson gson = new GsonBuilder() + /** + * Serializes this MX message into its JSON representation, using version 9 (Java 8) adapters. + * + *

This method ensures that when serializing to JSON, {@link OffsetDateTime} fields can be converted + * back into {@link XMLGregorianCalendar} based json element format for compatibility with older systems that still use the Java 8 format. + * + * @return a JSON representation of the MX message, compatible with Java 8 + * @since 10.1.8 + */ + public String toJsonV9() { + final Gson gson = getGsonBuilderWithV9Adapters(); + return gson.toJson(this, AbstractMX.class); + } + + private static GsonBuilder getGsonBuilderWithCustomAdapters() { + return new GsonBuilder() .registerTypeAdapter(AbstractMX.class, new AbstractMXAdapter()) - .registerTypeAdapter(OffsetDateTime.class, new OffsetDateTimeJsonAdapter()) .registerTypeAdapter(OffsetTime.class, new OffsetTimeJsonAdapter()) .registerTypeAdapter(LocalDate.class, new LocalDateJsonAdapter()) .registerTypeAdapter(Year.class, new YearJsonAdapter()) .registerTypeAdapter(YearMonth.class, new YearMonthJsonAdapter()) - .registerTypeAdapter(AppHdr.class, new AppHdrAdapter()) + .registerTypeAdapter(AppHdr.class, new AppHdrAdapter()); + } + + private static Gson getGsonBuilderWithV9Adapters() { + final Gson gson = getGsonBuilderWithCustomAdapters() + .registerTypeAdapter(OffsetDateTime.class, new V9DateTimeJsonAdapter()) + .setPrettyPrinting() + .create(); + return gson; + } + + private static Gson getGsonBuilderWithV10Adapters() { + final Gson gson = getGsonBuilderWithCustomAdapters() + .registerTypeAdapter(OffsetDateTime.class, new OffsetDateTimeJsonAdapter()) .setPrettyPrinting() .create(); return gson; diff --git a/iso20022-core/src/main/java/com/prowidesoftware/swift/model/mx/adapters/v9/V9DateTimeJsonAdapter.java b/iso20022-core/src/main/java/com/prowidesoftware/swift/model/mx/adapters/v9/V9DateTimeJsonAdapter.java new file mode 100644 index 000000000..b12a8fc08 --- /dev/null +++ b/iso20022-core/src/main/java/com/prowidesoftware/swift/model/mx/adapters/v9/V9DateTimeJsonAdapter.java @@ -0,0 +1,114 @@ +package com.prowidesoftware.swift.model.mx.adapters.v9; + +import com.google.gson.*; +import java.lang.reflect.Type; +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.time.OffsetDateTime; +import java.time.ZoneOffset; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Adapter for serializing and deserializing {@link OffsetDateTime} to and from JSON in a format compatible + * with the legacy {@code XMLGregorianCalendar} used in older versions of the library. + *

+ * This adapter is used to maintain backward compatibility with systems that expect the older + * date-time format while allowing migration to Java 11's {@code OffsetDateTime}. + *

+ * + *

Serialization

+ * The {@code serialize} method converts an {@code OffsetDateTime} object into a JSON structure + * similar to the structure used by {@code XMLGregorianCalendar}, preserving the fields for year, + * month, day, time, timezone, and fractional seconds if present. + * + *

Deserialization

+ * The {@code deserialize} method converts a JSON structure formatted for {@code XMLGregorianCalendar} + * into an {@code OffsetDateTime} object, parsing the legacy fields and converting them to their modern + * equivalents, including handling timezone and fractional seconds. + * + * @since 10.1.8 + */ +public class V9DateTimeJsonAdapter implements JsonSerializer, JsonDeserializer { + private static final Logger log = Logger.getLogger(V9DateTimeJsonAdapter.class.getName()); + + private final Gson gson = new Gson(); + + /** + * Serializes an {@link OffsetDateTime} into JSON format compatible with the older + * {@code XMLGregorianCalendar} representation. + * + * @param offsetDateTime the {@code OffsetDateTime} to be serialized + * @param type the specific genericized type of the source object + * @param jsonSerializationContext the context for serialization + * @return a {@link JsonElement} representing the serialized date-time information + */ + @Override + public JsonElement serialize( + OffsetDateTime offsetDateTime, Type type, JsonSerializationContext jsonSerializationContext) { + + XMLGregorianCalendarDTO xMLGregorianCalendarDTO = new XMLGregorianCalendarDTO(); + + xMLGregorianCalendarDTO.year = offsetDateTime.getYear(); + xMLGregorianCalendarDTO.month = offsetDateTime.getMonthValue(); + xMLGregorianCalendarDTO.day = offsetDateTime.getDayOfMonth(); + xMLGregorianCalendarDTO.hour = offsetDateTime.getHour(); + xMLGregorianCalendarDTO.minute = offsetDateTime.getMinute(); + xMLGregorianCalendarDTO.second = offsetDateTime.getSecond(); + if (offsetDateTime.getNano() != 0) { + xMLGregorianCalendarDTO.fractionalSecond = BigDecimal.valueOf(offsetDateTime.getNano()) + .divide(BigDecimal.valueOf(1_000_000_000), 3, RoundingMode.DOWN); + } + if (offsetDateTime.getOffset() != null) { + xMLGregorianCalendarDTO.timezone = offsetDateTime.getOffset().getTotalSeconds() / 60; + } + + return gson.toJsonTree(xMLGregorianCalendarDTO, XMLGregorianCalendarDTO.class); + } + + /** + * Deserializes a JSON element into an {@link OffsetDateTime}, converting the legacy + * {@code XMLGregorianCalendar} fields into their modern equivalents. + * + * @param xMLGregorianCalendarJsonElement the JSON data to be deserialized + * @param type the specific genericized type of the source object + * @param jsonDeserializationContext the context for deserialization + * @return the deserialized {@code OffsetDateTime}, or {@code null} if parsing fails + */ + @Override + public OffsetDateTime deserialize( + JsonElement xMLGregorianCalendarJsonElement, + Type type, + JsonDeserializationContext jsonDeserializationContext) { + try { + XMLGregorianCalendarDTO xMLGregorianCalendarDTO = + gson.fromJson(xMLGregorianCalendarJsonElement, XMLGregorianCalendarDTO.class); + + // Prepare OffsetDateTime + ZoneOffset zoneoffset = ZoneOffset.ofTotalSeconds(xMLGregorianCalendarDTO.timezone * 60); + return OffsetDateTime.of( + xMLGregorianCalendarDTO.year, + xMLGregorianCalendarDTO.month, + xMLGregorianCalendarDTO.day, + xMLGregorianCalendarDTO.hour, + xMLGregorianCalendarDTO.minute, + xMLGregorianCalendarDTO.second, + (int) (xMLGregorianCalendarDTO.fractionalSecond.doubleValue() * 1000_000_000), + zoneoffset); + } catch (Exception e) { + log.log(Level.FINEST, "Cannot parse JSON into OffsetDateTime: " + e.getMessage(), e); + return null; + } + } + + static class XMLGregorianCalendarDTO { + Integer year = 0; + Integer month = 0; + Integer day = 0; + Integer timezone = 0; + Integer hour = 0; + Integer minute = 0; + Integer second = 0; + BigDecimal fractionalSecond = BigDecimal.ZERO; + } +} diff --git a/iso20022-core/src/test/java/com/prowidesoftware/swift/model/mx/AbstractMxJsonV9Test.java b/iso20022-core/src/test/java/com/prowidesoftware/swift/model/mx/AbstractMxJsonV9Test.java new file mode 100644 index 000000000..701a8cdc1 --- /dev/null +++ b/iso20022-core/src/test/java/com/prowidesoftware/swift/model/mx/AbstractMxJsonV9Test.java @@ -0,0 +1,462 @@ +/* + * Copyright 2006-2024 Prowide + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.prowidesoftware.swift.model.mx; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import com.prowidesoftware.swift.model.MxId; +import org.junit.jupiter.api.Test; + +/** + * Test for JSON conversion in the MX model (AbstractMX and subclasses). + * + * @since 7.10.2 + */ +public class AbstractMxJsonV9Test { + + String xml = "" + + "" + + " " + + " " + + " " + + " " + + " FOORUS33" + + " " + + " " + + " " + + " " + + " " + + " " + + " FOORGB2L" + + " " + + " " + + " " + + " pacs009bizmsgidr-001" + + " pacs.009.001.08CORE" + + " swift.cbprplus.cov.02" + + " 2020-12-14T09:00:00-04:00" + + " " + + " " + + " " + + " " + + " pacs009bizmsgidr-001" + + " 2020-12-14T09:12:54.412-04:00" + + " 1" + + " " + + " INDA" + + " " + + " " + + " " + + " " + + " pcsdrId00a" + + " pacs009EndToEndId-001" + + " 2f876d32-df01-44ef-b925-34418eb3a998" + + " " + + " 100000000" + + " 2020-12-14" + + " " + + " " + + " FOORUS33" + + " " + + " " + + " " + + " " + + " FOORGB2L" + + " " + + " " + + " " + + " " + + " FOORUS33" + + " " + + " " + + " " + + " " + + " FOOYGB2L" + + " " + + " " + + " " + + " " + + " GOLDMANS" + + " " + + " 5TH AVENUE" + + " New York" + + " US" + + " " + + " " + + " " + + " Virgay" + + " " + + " Virginia Lane" + + " 36" + + " NJ 07311" + + " Jersey City" + + " US" + + " " + + " " + + " " + + " GOLDMANS" + + " " + + " 5TH AVENUE" + + " New York" + + " US" + + " " + + " " + + " " + + " " + + " FOORUS33" + + " " + + " " + + " " + + " " + + " FOOYGB2L" + + " " + + " " + + " " + + " SMITHSON" + + " " + + " LONDON WALL" + + " LONDON" + + " GB" + + " " + + " " + + " " + + " " + + " " + + " " + + ""; + + String jsonV10 = "{" + " 'fiCdtTrf': {" + + " 'grpHdr': {" + + " 'msgId': 'pacs009bizmsgidr-001'," + + " 'creDtTm': {" + + " 'dateTime': {" + + " 'date': {" + + " 'year': 2020," + + " 'month': 12," + + " 'day': 14" + + " }," + + " 'time': {" + + " 'hour': 9," + + " 'minute': 12," + + " 'second': 54," + + " 'nano': 412000000" + + " }" + + " }," + + " 'offset': {" + + " 'totalSeconds': -14400" + + " }" + + " }," + + " 'nbOfTxs': '1'," + + " 'sttlmInf': {" + + " 'sttlmMtd': 'INDA'" + + " }" + + " }," + + " 'cdtTrfTxInf': [" + + " {" + + " 'pmtId': {" + + " 'instrId': 'pcsdrId00a'," + + " 'endToEndId': 'pacs009EndToEndId-001'," + + " 'uetr': '2f876d32-df01-44ef-b925-34418eb3a998'" + + " }," + + " 'intrBkSttlmAmt': {" + + " 'value': 100000000," + + " 'ccy': 'GBP'" + + " }," + + " 'intrBkSttlmDt': {" + + " 'year': 2020," + + " 'month': 12," + + " 'day': 14" + + " }," + + " 'instgAgt': {" + + " 'finInstnId': {" + + " 'bicfi': 'FOORUS33'" + + " }" + + " }," + + " 'instdAgt': {" + + " 'finInstnId': {" + + " 'bicfi': 'FOORGB2L'" + + " }" + + " }," + + " 'dbtr': {" + + " 'finInstnId': {" + + " 'bicfi': 'FOORUS33'" + + " }" + + " }," + + " 'cdtr': {" + + " 'finInstnId': {" + + " 'bicfi': 'FOOYGB2L'" + + " }" + + " }," + + " 'undrlygCstmrCdtTrf': {" + + " 'ultmtDbtr': {" + + " 'nm': 'GOLDMANS'," + + " 'pstlAdr': {" + + " 'strtNm': '5TH AVENUE'," + + " 'twnNm': 'New York'," + + " 'ctry': 'US'" + + " }" + + " }," + + " 'initgPty': {" + + " 'nm': 'Virgay'," + + " 'pstlAdr': {" + + " 'strtNm': 'Virginia Lane'," + + " 'bldgNb': '36'," + + " 'pstCd': 'NJ 07311'," + + " 'twnNm': 'Jersey City'," + + " 'ctry': 'US'" + + " }" + + " }," + + " 'dbtr': {" + + " 'nm': 'GOLDMANS'," + + " 'pstlAdr': {" + + " 'strtNm': '5TH AVENUE'," + + " 'twnNm': 'New York'," + + " 'ctry': 'US'" + + " }" + + " }," + + " 'dbtrAgt': {" + + " 'finInstnId': {" + + " 'bicfi': 'FOORUS33'" + + " }" + + " }," + + " 'cdtrAgt': {" + + " 'finInstnId': {" + + " 'bicfi': 'FOOYGB2L'" + + " }" + + " }," + + " 'cdtr': {" + + " 'nm': 'SMITHSON'," + + " 'pstlAdr': {" + + " 'strtNm': 'LONDON WALL'," + + " 'twnNm': 'LONDON'," + + " 'ctry': 'GB'" + + " }" + + " }" + + " }" + + " }" + + " ]" + + " }," + + " 'appHdr': {" + + " 'fr': {" + + " 'fiId': {" + + " 'finInstnId': {" + + " 'bicfi': 'FOORUS33'" + + " }" + + " }" + + " }," + + " 'to': {" + + " 'fiId': {" + + " 'finInstnId': {" + + " 'bicfi': 'FOORGB2L'" + + " }" + + " }" + + " }," + + " 'bizMsgIdr': 'pacs009bizmsgidr-001'," + + " 'msgDefIdr': 'pacs.009.001.08CORE'," + + " 'bizSvc': 'swift.cbprplus.cov.02'," + + " 'creDt': {" + + " 'dateTime': {" + + " 'date': {" + + " 'year': 2020," + + " 'month': 12," + + " 'day': 14" + + " }," + + " 'time': {" + + " 'hour': 9," + + " 'minute': 0," + + " 'second': 0," + + " 'nano': 0" + + " }" + + " }," + + " 'offset': {" + + " 'totalSeconds': -14400" + + " }" + + " }," + + " 'namespace': 'urn:iso:std:iso:20022:tech:xsd:head.001.001.02'" + + " }," + + " 'type': 'MX'," + + " '@xmlns': 'urn:iso:std:iso:20022:tech:xsd:pacs.009.001.08'," + + " 'identifier': 'pacs.009.001.08'" + + "}"; + + String jsonV9 = "{" + " 'fiCdtTrf': {" + + " 'grpHdr': {" + + " 'msgId': 'pacs009bizmsgidr-001'," + + " 'creDtTm': {" + + " 'year': 2020," + + " 'month': 12," + + " 'day': 14," + + " 'timezone': -240," + + " 'hour': 9," + + " 'minute': 12," + + " 'second': 54," + + " 'fractionalSecond': 0.412" + + " }," + + " 'nbOfTxs': '1'," + + " 'sttlmInf': {" + + " 'sttlmMtd': 'INDA'" + + " }" + + " }," + + " 'cdtTrfTxInf': [" + + " {" + + " 'pmtId': {" + + " 'instrId': 'pcsdrId00a'," + + " 'endToEndId': 'pacs009EndToEndId-001'," + + " 'uetr': '2f876d32-df01-44ef-b925-34418eb3a998'" + + " }," + + " 'intrBkSttlmAmt': {" + + " 'value': 100000000," + + " 'ccy': 'GBP'" + + " }," + + " 'intrBkSttlmDt': {" + + " 'year': 2020," + + " 'month': 12," + + " 'day': 14" + + " }," + + " 'instgAgt': {" + + " 'finInstnId': {" + + " 'bicfi': 'FOORUS33'" + + " }" + + " }," + + " 'instdAgt': {" + + " 'finInstnId': {" + + " 'bicfi': 'FOORGB2L'" + + " }" + + " }," + + " 'dbtr': {" + + " 'finInstnId': {" + + " 'bicfi': 'FOORUS33'" + + " }" + + " }," + + " 'cdtr': {" + + " 'finInstnId': {" + + " 'bicfi': 'FOOYGB2L'" + + " }" + + " }," + + " 'undrlygCstmrCdtTrf': {" + + " 'ultmtDbtr': {" + + " 'nm': 'GOLDMANS'," + + " 'pstlAdr': {" + + " 'strtNm': '5TH AVENUE'," + + " 'twnNm': 'New York'," + + " 'ctry': 'US'" + + " }" + + " }," + + " 'initgPty': {" + + " 'nm': 'Virgay'," + + " 'pstlAdr': {" + + " 'strtNm': 'Virginia Lane'," + + " 'bldgNb': '36'," + + " 'pstCd': 'NJ 07311'," + + " 'twnNm': 'Jersey City'," + + " 'ctry': 'US'" + + " }" + + " }," + + " 'dbtr': {" + + " 'nm': 'GOLDMANS'," + + " 'pstlAdr': {" + + " 'strtNm': '5TH AVENUE'," + + " 'twnNm': 'New York'," + + " 'ctry': 'US'" + + " }" + + " }," + + " 'dbtrAgt': {" + + " 'finInstnId': {" + + " 'bicfi': 'FOORUS33'" + + " }" + + " }," + + " 'cdtrAgt': {" + + " 'finInstnId': {" + + " 'bicfi': 'FOOYGB2L'" + + " }" + + " }," + + " 'cdtr': {" + + " 'nm': 'SMITHSON'," + + " 'pstlAdr': {" + + " 'strtNm': 'LONDON WALL'," + + " 'twnNm': 'LONDON'," + + " 'ctry': 'GB'" + + " }" + + " }" + + " }" + + " }" + + " ]" + + " }," + + " 'appHdr': {" + + " 'fr': {" + + " 'fiId': {" + + " 'finInstnId': {" + + " 'bicfi': 'FOORUS33'" + + " }" + + " }" + + " }," + + " 'to': {" + + " 'fiId': {" + + " 'finInstnId': {" + + " 'bicfi': 'FOORGB2L'" + + " }" + + " }" + + " }," + + " 'bizMsgIdr': 'pacs009bizmsgidr-001'," + + " 'msgDefIdr': 'pacs.009.001.08CORE'," + + " 'bizSvc': 'swift.cbprplus.cov.02'," + + " 'creDt': {" + + " 'year': 2020," + + " 'month': 12," + + " 'day': 14," + + " 'timezone': -240," + + " 'hour': 9," + + " 'minute': 0," + + " 'second': 0," + + " 'fractionalSecond': 0" + + " }," + + " 'namespace': 'urn:iso:std:iso:20022:tech:xsd:head.001.001.02'" + + " }," + + " 'type': 'MX'," + + " '@xmlns': 'urn:iso:std:iso:20022:tech:xsd:pacs.009.001.08'," + + " 'identifier': 'pacs.009.001.08'" + + "}"; + + @Test + public void testMxToJson() { + AbstractMX mx = MxPacs00900108.parse(xml, new MxId("pacs.009.001.08")); + + String v10Json = mx.toJson(); + assertTrue(v10Json.contains("\"totalSeconds\": -14400")); + assertTrue(v10Json.contains("\"nano\": 412000000")); + + String v9Json = mx.toJsonV9(); + assertTrue(v9Json.contains("\"timezone\": -240")); + assertTrue(v9Json.contains("\"fractionalSecond\": 0.412")); + } + + @Test + public void testMxFromJsonV10() { + MxPacs00900108 mxFromJson = MxPacs00900108.fromJson(jsonV10); + MxPacs00900108 mx = (MxPacs00900108) MxPacs00900108.parse(xml, new MxId("pacs.009.001.08")); + assertEquals(mx.message(), mxFromJson.message()); + } + + @Test + public void testMxFromJsonV9() { + AbstractMX mxFromJson = MxPacs00900108.fromJsonV9(jsonV9); + AbstractMX mx = MxPacs00900108.parse(xml, new MxId("pacs.009.001.08")); + assertEquals(mx.message(), mxFromJson.message()); + } +} diff --git a/iso20022-core/src/test/java/com/prowidesoftware/swift/model/mx/adapters/v9/V9DateTimeJsonAdapterTest.java b/iso20022-core/src/test/java/com/prowidesoftware/swift/model/mx/adapters/v9/V9DateTimeJsonAdapterTest.java new file mode 100644 index 000000000..5005de5d8 --- /dev/null +++ b/iso20022-core/src/test/java/com/prowidesoftware/swift/model/mx/adapters/v9/V9DateTimeJsonAdapterTest.java @@ -0,0 +1,59 @@ +package com.prowidesoftware.swift.model.mx.adapters.v9; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import com.google.gson.JsonElement; +import com.google.gson.JsonParser; +import java.time.OffsetDateTime; +import org.junit.jupiter.api.Test; + +class V9DateTimeJsonAdapterTest { + + private final V9DateTimeJsonAdapter adapter = new V9DateTimeJsonAdapter(); + + @Test + void testDeserialization() { + String sourceJsonV9; + OffsetDateTime expectedOffsetDateTime; + + sourceJsonV9 = + "{\"year\":2022,\"month\":11,\"day\":30,\"timezone\":-180,\"hour\":10,\"minute\":43,\"second\":57,\"fractionalSecond\":0.412}"; + expectedOffsetDateTime = OffsetDateTime.parse("2022-11-30T10:43:57.412-03:00"); + testDeserialization(sourceJsonV9, expectedOffsetDateTime); + + // without offset, nano 0 + sourceJsonV9 = + "{\"year\":2019,\"month\":4,\"day\":28,\"timezone\":0,\"hour\":12,\"minute\":13,\"second\":14,\"fractionalSecond\":0}"; + expectedOffsetDateTime = OffsetDateTime.parse("2019-04-28T12:13:14.0-00:00"); + testDeserialization(sourceJsonV9, expectedOffsetDateTime); + } + + @Test + void testSerialization() { + OffsetDateTime sourceOffsetDateTime; + String expectedJsonV9; + + sourceOffsetDateTime = OffsetDateTime.parse("2022-11-30T10:43:57.412-03:00"); + expectedJsonV9 = + "{\"year\":2022,\"month\":11,\"day\":30,\"timezone\":-180,\"hour\":10,\"minute\":43,\"second\":57,\"fractionalSecond\":0.412}"; + testSerialization(sourceOffsetDateTime, expectedJsonV9); + + // without offset, nano 0 + sourceOffsetDateTime = OffsetDateTime.parse("2019-04-28T12:13:14.0-00:00"); + expectedJsonV9 = + "{\"year\":2019,\"month\":4,\"day\":28,\"timezone\":0,\"hour\":12,\"minute\":13,\"second\":14,\"fractionalSecond\":0}"; + testSerialization(sourceOffsetDateTime, expectedJsonV9); + } + + private void testDeserialization(String sourceJsonV9, OffsetDateTime expected) { + JsonElement sourceJsonElement = new JsonParser().parse(sourceJsonV9); + OffsetDateTime deserializeResult = adapter.deserialize(sourceJsonElement, null, null); + assertEquals(expected, deserializeResult); + } + + private void testSerialization(OffsetDateTime sourceOffsetDateTimeV10, String expectedJsonV9) { + JsonElement expectedJsonElement = new JsonParser().parse(expectedJsonV9); + JsonElement serializeResult = adapter.serialize(sourceOffsetDateTimeV10, null, null); + assertEquals(expectedJsonElement, serializeResult); + } +}