From cfb8153d0aec476a1409acffa2112a2233f5615f Mon Sep 17 00:00:00 2001 From: Martin Kaeser Date: Wed, 22 Feb 2023 09:54:32 +0100 Subject: [PATCH 1/5] feat(common): Generic JsonDeserializer - Add deserializer class --- .../DataClassUnwrappedJsonDeserializer.kt | 97 +++++++++++++++++++ 1 file changed, 97 insertions(+) create mode 100644 bpdm-common/src/main/kotlin/org/eclipse/tractusx/bpdm/common/service/DataClassUnwrappedJsonDeserializer.kt diff --git a/bpdm-common/src/main/kotlin/org/eclipse/tractusx/bpdm/common/service/DataClassUnwrappedJsonDeserializer.kt b/bpdm-common/src/main/kotlin/org/eclipse/tractusx/bpdm/common/service/DataClassUnwrappedJsonDeserializer.kt new file mode 100644 index 000000000..312f986c7 --- /dev/null +++ b/bpdm-common/src/main/kotlin/org/eclipse/tractusx/bpdm/common/service/DataClassUnwrappedJsonDeserializer.kt @@ -0,0 +1,97 @@ +/******************************************************************************* + * Copyright (c) 2021,2023 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://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. + * + * SPDX-License-Identifier: Apache-2.0 + ******************************************************************************/ + +package org.eclipse.tractusx.bpdm.common.service + +import com.fasterxml.jackson.annotation.JsonUnwrapped +import com.fasterxml.jackson.core.JsonParser +import com.fasterxml.jackson.databind.* +import com.fasterxml.jackson.databind.deser.ContextualDeserializer +import kotlin.reflect.KClass +import kotlin.reflect.KFunction +import kotlin.reflect.KType +import kotlin.reflect.full.memberProperties +import kotlin.reflect.full.primaryConstructor +import kotlin.reflect.jvm.javaField +import kotlin.reflect.jvm.javaType + +/** + * This is a generic JsonDeserializer that works with Kotlin data classes containing the annotation JsonUnwrapped. + * The new object is initialized via the primary constructor. + */ +class DataClassUnwrappedJsonDeserializer : JsonDeserializer(), ContextualDeserializer { + // We need a ContextualDeserializer to find out the destination type. + override fun createContextual(ctxt: DeserializationContext, property: BeanProperty?): JsonDeserializer<*> { + val javaType: JavaType = ctxt.contextualType + ?: throw IllegalStateException("ContextualType is missing from DeserializationContext") + return DataClassUnwrappedJsonDeserializerForType(javaType) + } + + override fun deserialize(p: JsonParser?, ctxt: DeserializationContext?): Any? { + throw IllegalStateException("DataClassUnwrappedJsonDeserializer.deserialize() can not be used directly") + } +} + +private class DataClassUnwrappedJsonDeserializerForType(destinationJavaType: JavaType) : JsonDeserializer() { + private val destinationClass: KClass + private val primaryConstructor: KFunction + private val constructorParameters: List + + init { + this.destinationClass = destinationJavaType.rawClass.kotlin + this.primaryConstructor = destinationClass.primaryConstructor + ?: throw IllegalStateException("Primary constructor required for '$destinationClass'") + + // Annotation @field:JsonUnwrapped is stored on the Java field, not the constructor parameter. + val propertiesByName = destinationClass.memberProperties.associateBy { it.name } + + this.constructorParameters = primaryConstructor.parameters.map { param -> + val name = param.name + ?: throw IllegalStateException("Some primary constructor parameter of '$destinationClass' doesn't have a name") + val type = param.type + val jsonUnwrapped = propertiesByName[name]?.javaField?.getAnnotation(JsonUnwrapped::class.java) != null + ConstructorParameter(name, type, jsonUnwrapped) + } + } + + override fun deserialize(parser: JsonParser, ctxt: DeserializationContext): Any { + val rootNode = parser.codec.readTree(parser) + + val constructorValues = constructorParameters.map { param -> + val jacksonType = ctxt.typeFactory.constructType(param.type.javaType) + val node = if (param.jsonUnwrapped) rootNode else rootNode.get(param.name) + val value = readTreeAsValue(ctxt, node, jacksonType) + if (value == null && !param.type.isMarkedNullable) + throw IllegalArgumentException("Field '${param.name}' of '$destinationClass' is required") + value + } + + return primaryConstructor.call(args = constructorValues.toTypedArray()) + } + + private fun readTreeAsValue(ctxt: DeserializationContext, node: JsonNode?, javaType: JavaType) : Any? = + if (node == null || node.isNull) null + else ctxt.readTreeAsValue(node, javaType) +} + +private data class ConstructorParameter( + val name: String, + val type: KType, + val jsonUnwrapped: Boolean, +) From 838a4ba8cd2843a4a09b22834d2e76bb94d98ba2 Mon Sep 17 00:00:00 2001 From: Martin Kaeser Date: Wed, 22 Feb 2023 09:56:02 +0100 Subject: [PATCH 2/5] feat(common): Generic JsonDeserializer - Replace all manual deserializers with generic one --- .../common/dto/response/AddressBpnResponse.kt | 17 ++---------- .../dto/response/AddressPartnerResponse.kt | 22 +++------------ .../response/LegalEntityPartnerResponse.kt | 22 +++------------ .../bpdm/gate/dto/AddressGateInput.kt | 20 ++------------ .../bpdm/gate/dto/AddressGateOutput.kt | 20 ++------------ .../bpdm/gate/dto/LegalEntityGateInput.kt | 18 ++----------- .../bpdm/gate/dto/LegalEntityGateOutput.kt | 19 ++----------- .../tractusx/bpdm/gate/dto/SiteGateInput.kt | 19 ++----------- .../tractusx/bpdm/gate/dto/SiteGateOutput.kt | 20 ++------------ .../request/AddressPartnerCreateRequest.kt | 22 +++------------ .../request/AddressPartnerUpdateRequest.kt | 21 +++------------ .../LegalEntityPartnerCreateRequest.kt | 20 +++----------- .../LegalEntityPartnerUpdateRequest.kt | 21 +++------------ .../dto/request/SitePartnerCreateRequest.kt | 21 +++------------ .../dto/request/SitePartnerUpdateRequest.kt | 21 +++------------ .../response/AddressPartnerCreateResponse.kt | 22 +++------------ .../dto/response/BusinessPartnerResponse.kt | 27 +++---------------- .../LegalEntityPartnerCreateResponse.kt | 24 +++-------------- 18 files changed, 47 insertions(+), 329 deletions(-) diff --git a/bpdm-common/src/main/kotlin/org/eclipse/tractusx/bpdm/common/dto/response/AddressBpnResponse.kt b/bpdm-common/src/main/kotlin/org/eclipse/tractusx/bpdm/common/dto/response/AddressBpnResponse.kt index 61fcfddf2..33e839ffe 100644 --- a/bpdm-common/src/main/kotlin/org/eclipse/tractusx/bpdm/common/dto/response/AddressBpnResponse.kt +++ b/bpdm-common/src/main/kotlin/org/eclipse/tractusx/bpdm/common/dto/response/AddressBpnResponse.kt @@ -20,14 +20,11 @@ package org.eclipse.tractusx.bpdm.common.dto.response import com.fasterxml.jackson.annotation.JsonUnwrapped -import com.fasterxml.jackson.core.JsonParser -import com.fasterxml.jackson.databind.DeserializationContext -import com.fasterxml.jackson.databind.JsonNode import com.fasterxml.jackson.databind.annotation.JsonDeserialize -import com.fasterxml.jackson.databind.deser.std.StdDeserializer import io.swagger.v3.oas.annotations.media.Schema +import org.eclipse.tractusx.bpdm.common.service.DataClassUnwrappedJsonDeserializer -@JsonDeserialize(using = AddressBpnResponseDeserializer::class) +@JsonDeserialize(using = DataClassUnwrappedJsonDeserializer::class) @Schema(name = "AddressBpnResponse", description = "Localized address record of a business partner") data class AddressBpnResponse( @Schema(description = "Business Partner Number, main identifier value for addresses") @@ -35,13 +32,3 @@ data class AddressBpnResponse( @field:JsonUnwrapped val address: AddressResponse ) - -class AddressBpnResponseDeserializer(vc: Class?) : StdDeserializer(vc) { - override fun deserialize(parser: JsonParser, ctxt: DeserializationContext): AddressBpnResponse { - val node = parser.codec.readTree(parser) - return AddressBpnResponse( - node.get(AddressBpnResponse::bpn.name).textValue(), - ctxt.readTreeAsValue(node, AddressResponse::class.java) - ) - } -} \ No newline at end of file diff --git a/bpdm-common/src/main/kotlin/org/eclipse/tractusx/bpdm/common/dto/response/AddressPartnerResponse.kt b/bpdm-common/src/main/kotlin/org/eclipse/tractusx/bpdm/common/dto/response/AddressPartnerResponse.kt index 560ce612a..10ed19c89 100644 --- a/bpdm-common/src/main/kotlin/org/eclipse/tractusx/bpdm/common/dto/response/AddressPartnerResponse.kt +++ b/bpdm-common/src/main/kotlin/org/eclipse/tractusx/bpdm/common/dto/response/AddressPartnerResponse.kt @@ -20,31 +20,15 @@ package org.eclipse.tractusx.bpdm.common.dto.response import com.fasterxml.jackson.annotation.JsonUnwrapped -import com.fasterxml.jackson.core.JsonParser -import com.fasterxml.jackson.databind.DeserializationContext -import com.fasterxml.jackson.databind.JsonNode import com.fasterxml.jackson.databind.annotation.JsonDeserialize -import com.fasterxml.jackson.databind.deser.std.StdDeserializer import io.swagger.v3.oas.annotations.media.Schema +import org.eclipse.tractusx.bpdm.common.service.DataClassUnwrappedJsonDeserializer -@JsonDeserialize(using = AddressPartnerResponse.CustomDeserializer::class) +@JsonDeserialize(using = DataClassUnwrappedJsonDeserializer::class) @Schema(name = "AddressPartnerResponse", description = "Business partner of type address") data class AddressPartnerResponse( @Schema(description = "Business Partner Number of this address") val bpn: String, @field:JsonUnwrapped val properties: AddressResponse -) { - class CustomDeserializer(vc: Class?) : StdDeserializer(vc) { - constructor() : this(null) // for some reason jackson needs this explicit default constructor - - override fun deserialize(parser: JsonParser, ctxt: DeserializationContext): AddressPartnerResponse { - val node = parser.codec.readTree(parser) - return AddressPartnerResponse( - node.get(AddressPartnerResponse::bpn.name).textValue(), - ctxt.readTreeAsValue(node, AddressResponse::class.java) - ) - } - } -} - +) diff --git a/bpdm-common/src/main/kotlin/org/eclipse/tractusx/bpdm/common/dto/response/LegalEntityPartnerResponse.kt b/bpdm-common/src/main/kotlin/org/eclipse/tractusx/bpdm/common/dto/response/LegalEntityPartnerResponse.kt index 63f3b6ac1..a3154800c 100644 --- a/bpdm-common/src/main/kotlin/org/eclipse/tractusx/bpdm/common/dto/response/LegalEntityPartnerResponse.kt +++ b/bpdm-common/src/main/kotlin/org/eclipse/tractusx/bpdm/common/dto/response/LegalEntityPartnerResponse.kt @@ -20,15 +20,12 @@ package org.eclipse.tractusx.bpdm.common.dto.response import com.fasterxml.jackson.annotation.JsonUnwrapped -import com.fasterxml.jackson.core.JsonParser -import com.fasterxml.jackson.databind.DeserializationContext -import com.fasterxml.jackson.databind.JsonNode import com.fasterxml.jackson.databind.annotation.JsonDeserialize -import com.fasterxml.jackson.databind.deser.std.StdDeserializer import io.swagger.v3.oas.annotations.media.Schema +import org.eclipse.tractusx.bpdm.common.service.DataClassUnwrappedJsonDeserializer import java.time.Instant -@JsonDeserialize(using = LegalEntityPartnerResponse.CustomDeserializer::class) +@JsonDeserialize(using = DataClassUnwrappedJsonDeserializer::class) @Schema(name = "LegalEntityPartnerResponse", description = "Business partner of type legal entity with currentness") data class LegalEntityPartnerResponse( @Schema(description = "Business Partner Number of this legal entity") @@ -37,17 +34,4 @@ data class LegalEntityPartnerResponse( val properties: LegalEntityResponse, @Schema(description = "The timestamp the business partner data was last indicated to be still current") val currentness: Instant -) { - class CustomDeserializer(vc: Class?) : StdDeserializer(vc) { - constructor() : this(null) // for some reason jackson needs this explicit default constructor - - override fun deserialize(parser: JsonParser, ctxt: DeserializationContext): LegalEntityPartnerResponse { - val node = parser.codec.readTree(parser) - return LegalEntityPartnerResponse( - node.get(LegalEntityPartnerResponse::bpn.name).textValue(), - ctxt.readTreeAsValue(node, LegalEntityResponse::class.java), - ctxt.readTreeAsValue(node.get(LegalEntityPartnerResponse::currentness.name), Instant::class.java) - ) - } - } -} +) diff --git a/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/dto/AddressGateInput.kt b/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/dto/AddressGateInput.kt index 4f52a2895..6d5f0c843 100644 --- a/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/dto/AddressGateInput.kt +++ b/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/dto/AddressGateInput.kt @@ -20,15 +20,12 @@ package org.eclipse.tractusx.bpdm.gate.dto import com.fasterxml.jackson.annotation.JsonUnwrapped -import com.fasterxml.jackson.core.JsonParser -import com.fasterxml.jackson.databind.DeserializationContext -import com.fasterxml.jackson.databind.JsonNode import com.fasterxml.jackson.databind.annotation.JsonDeserialize -import com.fasterxml.jackson.databind.deser.std.StdDeserializer import io.swagger.v3.oas.annotations.media.Schema import org.eclipse.tractusx.bpdm.common.dto.AddressDto +import org.eclipse.tractusx.bpdm.common.service.DataClassUnwrappedJsonDeserializer -@JsonDeserialize(using = AddressGateInputDeserializer::class) +@JsonDeserialize(using = DataClassUnwrappedJsonDeserializer::class) @Schema( name = "AddressGateInput", description = "Address with legal entity or site references. " + "Only one of either legal entity or site external id can be set for an address." @@ -45,16 +42,3 @@ data class AddressGateInput( @Schema(description = "External id of the related site") val siteExternalId: String? = null ) - -class AddressGateInputDeserializer(vc: Class?) : StdDeserializer(vc) { - override fun deserialize(parser: JsonParser, ctxt: DeserializationContext): AddressGateInput { - val node = parser.codec.readTree(parser) - return AddressGateInput( - node.get(AddressGateInput::bpn.name)?.textValue(), - ctxt.readTreeAsValue(node, AddressDto::class.java), - node.get(AddressGateInput::externalId.name).textValue(), - node.get(AddressGateInput::legalEntityExternalId.name)?.textValue(), - node.get(AddressGateInput::siteExternalId.name)?.textValue() - ) - } -} \ No newline at end of file diff --git a/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/dto/AddressGateOutput.kt b/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/dto/AddressGateOutput.kt index 201582b69..aa6f92d4f 100644 --- a/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/dto/AddressGateOutput.kt +++ b/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/dto/AddressGateOutput.kt @@ -20,15 +20,12 @@ package org.eclipse.tractusx.bpdm.gate.dto import com.fasterxml.jackson.annotation.JsonUnwrapped -import com.fasterxml.jackson.core.JsonParser -import com.fasterxml.jackson.databind.DeserializationContext -import com.fasterxml.jackson.databind.JsonNode import com.fasterxml.jackson.databind.annotation.JsonDeserialize -import com.fasterxml.jackson.databind.deser.std.StdDeserializer import io.swagger.v3.oas.annotations.media.Schema import org.eclipse.tractusx.bpdm.common.dto.response.AddressResponse +import org.eclipse.tractusx.bpdm.common.service.DataClassUnwrappedJsonDeserializer -@JsonDeserialize(using = AddressGateOutputDeserializer::class) +@JsonDeserialize(using = DataClassUnwrappedJsonDeserializer::class) @Schema( name = "AddressGateOutput", description = "Address with legal entity or site references. " + "Only one of either legal entity or site external id can be set for an address." @@ -45,16 +42,3 @@ data class AddressGateOutput( @Schema(description = "External id of the related site") val siteBpn: String? = null ) - -class AddressGateOutputDeserializer(vc: Class?) : StdDeserializer(vc) { - override fun deserialize(parser: JsonParser, ctxt: DeserializationContext): AddressGateOutput { - val node = parser.codec.readTree(parser) - return AddressGateOutput( - node.get(AddressGateOutput::bpn.name)?.textValue(), - ctxt.readTreeAsValue(node, AddressResponse::class.java), - node.get(AddressGateOutput::externalId.name).textValue(), - node.get(AddressGateOutput::legalEntityBpn.name)?.textValue(), - node.get(AddressGateOutput::siteBpn.name)?.textValue() - ) - } -} \ No newline at end of file diff --git a/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/dto/LegalEntityGateInput.kt b/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/dto/LegalEntityGateInput.kt index 7c68c5243..fe1f73fd5 100644 --- a/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/dto/LegalEntityGateInput.kt +++ b/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/dto/LegalEntityGateInput.kt @@ -20,15 +20,12 @@ package org.eclipse.tractusx.bpdm.gate.dto import com.fasterxml.jackson.annotation.JsonUnwrapped -import com.fasterxml.jackson.core.JsonParser -import com.fasterxml.jackson.databind.DeserializationContext -import com.fasterxml.jackson.databind.JsonNode import com.fasterxml.jackson.databind.annotation.JsonDeserialize -import com.fasterxml.jackson.databind.deser.std.StdDeserializer import io.swagger.v3.oas.annotations.media.Schema import org.eclipse.tractusx.bpdm.common.dto.LegalEntityDto +import org.eclipse.tractusx.bpdm.common.service.DataClassUnwrappedJsonDeserializer -@JsonDeserialize(using = LegalEntityGateInputDeserializer::class) +@JsonDeserialize(using = DataClassUnwrappedJsonDeserializer::class) @Schema(name = "LegalEntityGateInput", description = "Legal entity with external id") data class LegalEntityGateInput( @Schema(description = "ID the record has in the external system where the record originates from", required = true) @@ -38,14 +35,3 @@ data class LegalEntityGateInput( @field:JsonUnwrapped val legalEntity: LegalEntityDto ) - -class LegalEntityGateInputDeserializer(vc: Class?) : StdDeserializer(vc) { - override fun deserialize(parser: JsonParser, ctxt: DeserializationContext): LegalEntityGateInput { - val node = parser.codec.readTree(parser) - return LegalEntityGateInput( - node.get(LegalEntityGateInput::externalId.name).textValue(), - node.get(LegalEntityGateInput::bpn.name)?.textValue(), - ctxt.readTreeAsValue(node, LegalEntityDto::class.java) - ) - } -} diff --git a/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/dto/LegalEntityGateOutput.kt b/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/dto/LegalEntityGateOutput.kt index 7fbf35c43..76b5049d9 100644 --- a/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/dto/LegalEntityGateOutput.kt +++ b/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/dto/LegalEntityGateOutput.kt @@ -21,16 +21,13 @@ package org.eclipse.tractusx.bpdm.gate.dto import com.fasterxml.jackson.annotation.JsonUnwrapped -import com.fasterxml.jackson.core.JsonParser -import com.fasterxml.jackson.databind.DeserializationContext -import com.fasterxml.jackson.databind.JsonNode import com.fasterxml.jackson.databind.annotation.JsonDeserialize -import com.fasterxml.jackson.databind.deser.std.StdDeserializer import io.swagger.v3.oas.annotations.media.Schema import org.eclipse.tractusx.bpdm.common.dto.response.AddressResponse import org.eclipse.tractusx.bpdm.common.dto.response.LegalEntityResponse +import org.eclipse.tractusx.bpdm.common.service.DataClassUnwrappedJsonDeserializer -@JsonDeserialize(using = LegalEntityGateOutputDeserializer::class) +@JsonDeserialize(using = DataClassUnwrappedJsonDeserializer::class) @Schema(name = "LegalEntityGateOutput", description = "Legal entity with references") data class LegalEntityGateOutput( @field:JsonUnwrapped @@ -42,15 +39,3 @@ data class LegalEntityGateOutput( @Schema(description = "ID the record has in the external system where the record originates from") val externalId: String ) - -class LegalEntityGateOutputDeserializer(vc: Class?) : StdDeserializer(vc) { - override fun deserialize(parser: JsonParser, ctxt: DeserializationContext): LegalEntityGateOutput { - val node = parser.codec.readTree(parser) - return LegalEntityGateOutput( - ctxt.readTreeAsValue(node, LegalEntityResponse::class.java), - ctxt.readTreeAsValue(node.get(LegalEntityGateOutput::legalAddress.name), AddressResponse::class.java), - node.get(LegalEntityGateOutput::bpn.name)?.textValue(), - node.get(LegalEntityGateOutput::externalId.name).textValue() - ) - } -} \ No newline at end of file diff --git a/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/dto/SiteGateInput.kt b/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/dto/SiteGateInput.kt index 1760bc430..f75d4e73e 100644 --- a/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/dto/SiteGateInput.kt +++ b/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/dto/SiteGateInput.kt @@ -20,15 +20,12 @@ package org.eclipse.tractusx.bpdm.gate.dto import com.fasterxml.jackson.annotation.JsonUnwrapped -import com.fasterxml.jackson.core.JsonParser -import com.fasterxml.jackson.databind.DeserializationContext -import com.fasterxml.jackson.databind.JsonNode import com.fasterxml.jackson.databind.annotation.JsonDeserialize -import com.fasterxml.jackson.databind.deser.std.StdDeserializer import io.swagger.v3.oas.annotations.media.Schema import org.eclipse.tractusx.bpdm.common.dto.SiteDto +import org.eclipse.tractusx.bpdm.common.service.DataClassUnwrappedJsonDeserializer -@JsonDeserialize(using = SiteGateInputDeserializer::class) +@JsonDeserialize(using = DataClassUnwrappedJsonDeserializer::class) @Schema( name = "SiteGateInput", description = " Site with legal entity reference ." ) @@ -42,15 +39,3 @@ data class SiteGateInput( @Schema(description = "External id of the related legal entity") val legalEntityExternalId: String, ) - -class SiteGateInputDeserializer(vc: Class?) : StdDeserializer(vc) { - override fun deserialize(parser: JsonParser, ctxt: DeserializationContext): SiteGateInput { - val node = parser.codec.readTree(parser) - return SiteGateInput( - node.get(SiteGateInput::bpn.name)?.textValue(), - ctxt.readTreeAsValue(node, SiteDto::class.java), - node.get(SiteGateInput::externalId.name).textValue(), - node.get(SiteGateInput::legalEntityExternalId.name)?.textValue()!! - ) - } -} \ No newline at end of file diff --git a/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/dto/SiteGateOutput.kt b/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/dto/SiteGateOutput.kt index 82cb2a1c1..4027166d9 100644 --- a/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/dto/SiteGateOutput.kt +++ b/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/dto/SiteGateOutput.kt @@ -20,16 +20,13 @@ package org.eclipse.tractusx.bpdm.gate.dto import com.fasterxml.jackson.annotation.JsonUnwrapped -import com.fasterxml.jackson.core.JsonParser -import com.fasterxml.jackson.databind.DeserializationContext -import com.fasterxml.jackson.databind.JsonNode import com.fasterxml.jackson.databind.annotation.JsonDeserialize -import com.fasterxml.jackson.databind.deser.std.StdDeserializer import io.swagger.v3.oas.annotations.media.Schema import org.eclipse.tractusx.bpdm.common.dto.response.AddressResponse import org.eclipse.tractusx.bpdm.common.dto.response.SiteResponse +import org.eclipse.tractusx.bpdm.common.service.DataClassUnwrappedJsonDeserializer -@JsonDeserialize(using = SiteGateOutputDeserializer::class) +@JsonDeserialize(using = DataClassUnwrappedJsonDeserializer::class) @Schema(name = "SiteGateOutput", description = "Site with legal entity reference.") data class SiteGateOutput( @field:JsonUnwrapped @@ -43,16 +40,3 @@ data class SiteGateOutput( @Schema(description = "Bpn of the related legal entity") val legalEntityBpn: String, ) - -class SiteGateOutputDeserializer(vc: Class?) : StdDeserializer(vc) { - override fun deserialize(parser: JsonParser, ctxt: DeserializationContext): SiteGateOutput { - val node = parser.codec.readTree(parser) - return SiteGateOutput( - ctxt.readTreeAsValue(node, SiteResponse::class.java), - ctxt.readTreeAsValue(node.get(SiteGateOutput::mainAddress.name), AddressResponse::class.java), - node.get(SiteGateOutput::externalId.name).textValue(), - node.get(SiteGateOutput::bpn.name)?.textValue(), - node.get(SiteGateOutput::legalEntityBpn.name).textValue() - ) - } -} \ No newline at end of file diff --git a/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/dto/request/AddressPartnerCreateRequest.kt b/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/dto/request/AddressPartnerCreateRequest.kt index 898376db9..561edd9d7 100644 --- a/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/dto/request/AddressPartnerCreateRequest.kt +++ b/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/dto/request/AddressPartnerCreateRequest.kt @@ -20,15 +20,12 @@ package org.eclipse.tractusx.bpdm.pool.dto.request import com.fasterxml.jackson.annotation.JsonUnwrapped -import com.fasterxml.jackson.core.JsonParser -import com.fasterxml.jackson.databind.DeserializationContext -import com.fasterxml.jackson.databind.JsonNode import com.fasterxml.jackson.databind.annotation.JsonDeserialize -import com.fasterxml.jackson.databind.deser.std.StdDeserializer import io.swagger.v3.oas.annotations.media.Schema import org.eclipse.tractusx.bpdm.common.dto.AddressDto +import org.eclipse.tractusx.bpdm.common.service.DataClassUnwrappedJsonDeserializer -@JsonDeserialize(using = AddressPartnerCreateRequest.CustomDeserializer::class) +@JsonDeserialize(using = DataClassUnwrappedJsonDeserializer::class) @Schema(name = "AddressPartnerCreateRequest", description = "Request for creating new business partner record of type address") data class AddressPartnerCreateRequest( @field:JsonUnwrapped @@ -37,17 +34,4 @@ data class AddressPartnerCreateRequest( val parent: String, @Schema(description = "User defined index to conveniently match this entry to the corresponding entry in the response") val index: String? -) { - class CustomDeserializer(vc: Class?) : StdDeserializer(vc) { - override fun deserialize(parser: JsonParser, ctxt: DeserializationContext): AddressPartnerCreateRequest { - val node = parser.codec.readTree(parser) - return AddressPartnerCreateRequest( - ctxt.readTreeAsValue(node, AddressDto::class.java), - node.get(AddressPartnerCreateRequest::parent.name).textValue(), - node.get(AddressPartnerCreateRequest::index.name)?.textValue() - ) - } - } -} - - +) diff --git a/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/dto/request/AddressPartnerUpdateRequest.kt b/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/dto/request/AddressPartnerUpdateRequest.kt index 3fafa909a..7f8b71ce2 100644 --- a/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/dto/request/AddressPartnerUpdateRequest.kt +++ b/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/dto/request/AddressPartnerUpdateRequest.kt @@ -20,31 +20,16 @@ package org.eclipse.tractusx.bpdm.pool.dto.request import com.fasterxml.jackson.annotation.JsonUnwrapped -import com.fasterxml.jackson.core.JsonParser -import com.fasterxml.jackson.databind.DeserializationContext -import com.fasterxml.jackson.databind.JsonNode import com.fasterxml.jackson.databind.annotation.JsonDeserialize -import com.fasterxml.jackson.databind.deser.std.StdDeserializer import io.swagger.v3.oas.annotations.media.Schema import org.eclipse.tractusx.bpdm.common.dto.AddressDto +import org.eclipse.tractusx.bpdm.common.service.DataClassUnwrappedJsonDeserializer -@JsonDeserialize(using = AddressPartnerUpdateRequest.CustomDeserializer::class) +@JsonDeserialize(using = DataClassUnwrappedJsonDeserializer::class) @Schema(name = "AddressPartnerUpdateRequest", description = "Request for updating a business partner record of type address") data class AddressPartnerUpdateRequest( @Schema(description = "Business Partner Number of this address") val bpn: String, @field:JsonUnwrapped val properties: AddressDto -) { - class CustomDeserializer(vc: Class?) : StdDeserializer(vc) { - override fun deserialize(parser: JsonParser, ctxt: DeserializationContext): AddressPartnerUpdateRequest { - val node = parser.codec.readTree(parser) - return AddressPartnerUpdateRequest( - node.get(AddressPartnerUpdateRequest::bpn.name).textValue(), - ctxt.readTreeAsValue(node, AddressDto::class.java), - ) - } - } -} - - +) diff --git a/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/dto/request/LegalEntityPartnerCreateRequest.kt b/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/dto/request/LegalEntityPartnerCreateRequest.kt index cb996cbaf..26034829a 100644 --- a/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/dto/request/LegalEntityPartnerCreateRequest.kt +++ b/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/dto/request/LegalEntityPartnerCreateRequest.kt @@ -20,30 +20,16 @@ package org.eclipse.tractusx.bpdm.pool.dto.request import com.fasterxml.jackson.annotation.JsonUnwrapped -import com.fasterxml.jackson.core.JsonParser -import com.fasterxml.jackson.databind.DeserializationContext -import com.fasterxml.jackson.databind.JsonNode import com.fasterxml.jackson.databind.annotation.JsonDeserialize -import com.fasterxml.jackson.databind.deser.std.StdDeserializer import io.swagger.v3.oas.annotations.media.Schema import org.eclipse.tractusx.bpdm.common.dto.LegalEntityDto +import org.eclipse.tractusx.bpdm.common.service.DataClassUnwrappedJsonDeserializer -@JsonDeserialize(using = LegalEntityPartnerCreateRequest.CustomDeserializer::class) +@JsonDeserialize(using = DataClassUnwrappedJsonDeserializer::class) @Schema(name = "LegalEntityPartnerCreateRequest", description = "Request for creating new business partner record of type legal entity") data class LegalEntityPartnerCreateRequest( @field:JsonUnwrapped val properties: LegalEntityDto, @Schema(description = "User defined index to conveniently match this entry to the corresponding entry in the response") val index: String? -) { - class CustomDeserializer(vc: Class?) : StdDeserializer(vc) { - override fun deserialize(parser: JsonParser, ctxt: DeserializationContext): LegalEntityPartnerCreateRequest { - val node = parser.codec.readTree(parser) - return LegalEntityPartnerCreateRequest( - ctxt.readTreeAsValue(node, LegalEntityDto::class.java), - node.get(LegalEntityPartnerCreateRequest::index.name)?.textValue(), - ) - } - } -} - +) diff --git a/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/dto/request/LegalEntityPartnerUpdateRequest.kt b/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/dto/request/LegalEntityPartnerUpdateRequest.kt index f425d4b74..4041e0837 100644 --- a/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/dto/request/LegalEntityPartnerUpdateRequest.kt +++ b/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/dto/request/LegalEntityPartnerUpdateRequest.kt @@ -20,31 +20,16 @@ package org.eclipse.tractusx.bpdm.pool.dto.request import com.fasterxml.jackson.annotation.JsonUnwrapped -import com.fasterxml.jackson.core.JsonParser -import com.fasterxml.jackson.databind.DeserializationContext -import com.fasterxml.jackson.databind.JsonNode import com.fasterxml.jackson.databind.annotation.JsonDeserialize -import com.fasterxml.jackson.databind.deser.std.StdDeserializer import io.swagger.v3.oas.annotations.media.Schema import org.eclipse.tractusx.bpdm.common.dto.LegalEntityDto +import org.eclipse.tractusx.bpdm.common.service.DataClassUnwrappedJsonDeserializer -@JsonDeserialize(using = LegalEntityPartnerUpdateRequest.CustomDeserializer::class) +@JsonDeserialize(using = DataClassUnwrappedJsonDeserializer::class) @Schema(name = "LegalEntityUpdateRequest", description = "Request for updating a business partner record of type legal entity") data class LegalEntityPartnerUpdateRequest( @Schema(description = "Business Partner Number") val bpn: String, @field:JsonUnwrapped val properties: LegalEntityDto -) { - class CustomDeserializer(vc: Class?) : StdDeserializer(vc) { - override fun deserialize(parser: JsonParser, ctxt: DeserializationContext): LegalEntityPartnerUpdateRequest { - val node = parser.codec.readTree(parser) - return LegalEntityPartnerUpdateRequest( - node.get(LegalEntityPartnerUpdateRequest::bpn.name).textValue(), - ctxt.readTreeAsValue(node, LegalEntityDto::class.java) - ) - } - } -} - - +) diff --git a/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/dto/request/SitePartnerCreateRequest.kt b/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/dto/request/SitePartnerCreateRequest.kt index 26e90b0f7..3b8fdcaf0 100644 --- a/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/dto/request/SitePartnerCreateRequest.kt +++ b/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/dto/request/SitePartnerCreateRequest.kt @@ -20,15 +20,12 @@ package org.eclipse.tractusx.bpdm.pool.dto.request import com.fasterxml.jackson.annotation.JsonUnwrapped -import com.fasterxml.jackson.core.JsonParser -import com.fasterxml.jackson.databind.DeserializationContext -import com.fasterxml.jackson.databind.JsonNode import com.fasterxml.jackson.databind.annotation.JsonDeserialize -import com.fasterxml.jackson.databind.deser.std.StdDeserializer import io.swagger.v3.oas.annotations.media.Schema import org.eclipse.tractusx.bpdm.common.dto.SiteDto +import org.eclipse.tractusx.bpdm.common.service.DataClassUnwrappedJsonDeserializer -@JsonDeserialize(using = SitePartnerCreateRequest.CustomDeserializer::class) +@JsonDeserialize(using = DataClassUnwrappedJsonDeserializer::class) @Schema(name = "SitePartnerCreateRequest", description = "Request for creating new business partner record of type site") data class SitePartnerCreateRequest( @field:JsonUnwrapped @@ -37,16 +34,4 @@ data class SitePartnerCreateRequest( val legalEntity: String, @Schema(description = "User defined index to conveniently match this entry to the corresponding entry in the response") val index: String? -) { - class CustomDeserializer(vc: Class?) : StdDeserializer(vc) { - override fun deserialize(parser: JsonParser, ctxt: DeserializationContext): SitePartnerCreateRequest { - val node = parser.codec.readTree(parser) - return SitePartnerCreateRequest( - ctxt.readTreeAsValue(node, SiteDto::class.java), - node.get(SitePartnerCreateRequest::legalEntity.name).textValue(), - node.get(SitePartnerCreateRequest::index.name)?.textValue(), - ) - } - } -} - +) diff --git a/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/dto/request/SitePartnerUpdateRequest.kt b/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/dto/request/SitePartnerUpdateRequest.kt index f2c8f08a4..e6700e3d6 100644 --- a/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/dto/request/SitePartnerUpdateRequest.kt +++ b/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/dto/request/SitePartnerUpdateRequest.kt @@ -20,31 +20,16 @@ package org.eclipse.tractusx.bpdm.pool.dto.request import com.fasterxml.jackson.annotation.JsonUnwrapped -import com.fasterxml.jackson.core.JsonParser -import com.fasterxml.jackson.databind.DeserializationContext -import com.fasterxml.jackson.databind.JsonNode import com.fasterxml.jackson.databind.annotation.JsonDeserialize -import com.fasterxml.jackson.databind.deser.std.StdDeserializer import io.swagger.v3.oas.annotations.media.Schema import org.eclipse.tractusx.bpdm.common.dto.SiteDto +import org.eclipse.tractusx.bpdm.common.service.DataClassUnwrappedJsonDeserializer -@JsonDeserialize(using = SitePartnerUpdateRequest.CustomDeserializer::class) +@JsonDeserialize(using = DataClassUnwrappedJsonDeserializer::class) @Schema(name = "SitePartnerUpdateRequest", description = "Request for updating a business partner record of type site") data class SitePartnerUpdateRequest( @Schema(description = "Business Partner Number of this site") val bpn: String, @field:JsonUnwrapped val site: SiteDto -) { - class CustomDeserializer(vc: Class?) : StdDeserializer(vc) { - override fun deserialize(parser: JsonParser, ctxt: DeserializationContext): SitePartnerUpdateRequest { - val node = parser.codec.readTree(parser) - return SitePartnerUpdateRequest( - node.get(LegalEntityPartnerUpdateRequest::bpn.name).textValue(), - ctxt.readTreeAsValue(node, SiteDto::class.java) - ) - } - } -} - - +) diff --git a/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/dto/response/AddressPartnerCreateResponse.kt b/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/dto/response/AddressPartnerCreateResponse.kt index f2f08abaa..f42833218 100644 --- a/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/dto/response/AddressPartnerCreateResponse.kt +++ b/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/dto/response/AddressPartnerCreateResponse.kt @@ -20,15 +20,12 @@ package org.eclipse.tractusx.bpdm.pool.dto.response import com.fasterxml.jackson.annotation.JsonUnwrapped -import com.fasterxml.jackson.core.JsonParser -import com.fasterxml.jackson.databind.DeserializationContext -import com.fasterxml.jackson.databind.JsonNode import com.fasterxml.jackson.databind.annotation.JsonDeserialize -import com.fasterxml.jackson.databind.deser.std.StdDeserializer import io.swagger.v3.oas.annotations.media.Schema import org.eclipse.tractusx.bpdm.common.dto.response.AddressResponse +import org.eclipse.tractusx.bpdm.common.service.DataClassUnwrappedJsonDeserializer -@JsonDeserialize(using = AddressPartnerCreateResponse.CustomDeserializer::class) +@JsonDeserialize(using = DataClassUnwrappedJsonDeserializer::class) @Schema(name = "AddressPartnerCreateResponse", description = "Created business partners of type address") data class AddressPartnerCreateResponse( @Schema(description = "Business Partner Number of this address") @@ -37,17 +34,4 @@ data class AddressPartnerCreateResponse( val properties: AddressResponse, @Schema(description = "User defined index to conveniently match this entry to the corresponding entry from the request") val index: String? -) { - class CustomDeserializer(vc: Class?) : StdDeserializer(vc) { - override fun deserialize(parser: JsonParser, ctxt: DeserializationContext): AddressPartnerCreateResponse { - val node = parser.codec.readTree(parser) - return AddressPartnerCreateResponse( - node.get(AddressPartnerCreateResponse::bpn.name).textValue(), - ctxt.readTreeAsValue(node, AddressResponse::class.java), - node.get(AddressPartnerCreateResponse::index.name)?.textValue(), - ) - } - } -} - - +) diff --git a/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/dto/response/BusinessPartnerResponse.kt b/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/dto/response/BusinessPartnerResponse.kt index bd19cc563..a59c221a6 100644 --- a/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/dto/response/BusinessPartnerResponse.kt +++ b/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/dto/response/BusinessPartnerResponse.kt @@ -20,18 +20,15 @@ package org.eclipse.tractusx.bpdm.pool.dto.response import com.fasterxml.jackson.annotation.JsonUnwrapped -import com.fasterxml.jackson.core.JsonParser -import com.fasterxml.jackson.databind.DeserializationContext -import com.fasterxml.jackson.databind.JsonNode import com.fasterxml.jackson.databind.annotation.JsonDeserialize -import com.fasterxml.jackson.databind.deser.std.StdDeserializer import io.swagger.v3.oas.annotations.media.Schema import org.eclipse.tractusx.bpdm.common.dto.response.AddressPartnerResponse import org.eclipse.tractusx.bpdm.common.dto.response.LegalEntityResponse import org.eclipse.tractusx.bpdm.common.dto.response.SitePartnerResponse +import org.eclipse.tractusx.bpdm.common.service.DataClassUnwrappedJsonDeserializer import java.time.Instant -@JsonDeserialize(using = BusinessPartnerResponse.CustomDeserializer::class) +@JsonDeserialize(using = DataClassUnwrappedJsonDeserializer::class) @Schema(name = "BusinessPartnerResponse", description = "Business Partner of type legal entity in deprecated response format", deprecated = true) data class BusinessPartnerResponse( val uuid: String, @@ -43,22 +40,4 @@ data class BusinessPartnerResponse( val sites: Collection, @Schema(description = "The timestamp the business partner data was last indicated to be still current") val currentness: Instant -) { - class CustomDeserializer(vc: Class?) : StdDeserializer(vc) { - override fun deserialize(parser: JsonParser, ctxt: DeserializationContext): BusinessPartnerResponse { - val node = parser.codec.readTree(parser) - - val addresses = node.get(BusinessPartnerResponse::addresses.name).map { ctxt.readTreeAsValue(it, AddressPartnerResponse::class.java) } - val sites = node.get(BusinessPartnerResponse::sites.name).map { ctxt.readTreeAsValue(it, SitePartnerResponse::class.java) } - - return BusinessPartnerResponse( - node.get(BusinessPartnerResponse::uuid.name).textValue(), - node.get(BusinessPartnerResponse::bpn.name).textValue(), - ctxt.readTreeAsValue(node, LegalEntityResponse::class.java), - addresses, - sites, - ctxt.readTreeAsValue(node.get(BusinessPartnerResponse::currentness.name), Instant::class.java), - ) - } - } -} +) diff --git a/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/dto/response/LegalEntityPartnerCreateResponse.kt b/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/dto/response/LegalEntityPartnerCreateResponse.kt index 5d3706e25..e3b123e65 100644 --- a/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/dto/response/LegalEntityPartnerCreateResponse.kt +++ b/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/dto/response/LegalEntityPartnerCreateResponse.kt @@ -20,17 +20,14 @@ package org.eclipse.tractusx.bpdm.pool.dto.response import com.fasterxml.jackson.annotation.JsonUnwrapped -import com.fasterxml.jackson.core.JsonParser -import com.fasterxml.jackson.databind.DeserializationContext -import com.fasterxml.jackson.databind.JsonNode import com.fasterxml.jackson.databind.annotation.JsonDeserialize -import com.fasterxml.jackson.databind.deser.std.StdDeserializer import io.swagger.v3.oas.annotations.media.Schema import org.eclipse.tractusx.bpdm.common.dto.response.AddressResponse import org.eclipse.tractusx.bpdm.common.dto.response.LegalEntityResponse +import org.eclipse.tractusx.bpdm.common.service.DataClassUnwrappedJsonDeserializer import java.time.Instant -@JsonDeserialize(using = LegalEntityPartnerCreateResponse.CustomDeserializer::class) +@JsonDeserialize(using = DataClassUnwrappedJsonDeserializer::class) @Schema(name = "LegalEntityPartnerCreateResponse", description = "Created business partner of type legal entity") data class LegalEntityPartnerCreateResponse( @Schema(description = "Business Partner Number of this legal entity") @@ -43,19 +40,4 @@ data class LegalEntityPartnerCreateResponse( val legalAddress: AddressResponse, @Schema(description = "User defined index to conveniently match this entry to the corresponding entry from the request") val index: String? -) { - class CustomDeserializer(vc: Class?) : StdDeserializer(vc) { - override fun deserialize(parser: JsonParser, ctxt: DeserializationContext): LegalEntityPartnerCreateResponse { - val node = parser.codec.readTree(parser) - return LegalEntityPartnerCreateResponse( - node.get(LegalEntityPartnerCreateResponse::bpn.name).textValue(), - ctxt.readTreeAsValue(node, LegalEntityResponse::class.java), - ctxt.readTreeAsValue(node.get(LegalEntityPartnerCreateResponse::currentness.name), Instant::class.java), - ctxt.readTreeAsValue(node.get(LegalEntityPartnerCreateResponse::legalAddress.name), AddressResponse::class.java), - node.get(LegalEntityPartnerCreateResponse::index.name)?.textValue(), - ) - } - } -} - - +) From c1cc0035722a67ffb8293777f6dff01f7d92d2c0 Mon Sep 17 00:00:00 2001 From: Martin Kaeser Date: Wed, 22 Feb 2023 15:47:45 +0100 Subject: [PATCH 3/5] feat(common): Generic JsonDeserializer - Unit test for DataClassUnwrappedJsonDeserializer --- bpdm-common/pom.xml | 22 ++ .../DataClassUnwrappedJsonDeserializerTest.kt | 213 ++++++++++++++++++ 2 files changed, 235 insertions(+) create mode 100644 bpdm-common/src/test/kotlin/org/eclipse/tractusx/bpdm/common/service/DataClassUnwrappedJsonDeserializerTest.kt diff --git a/bpdm-common/pom.xml b/bpdm-common/pom.xml index 0e61f583e..3eec09eed 100644 --- a/bpdm-common/pom.xml +++ b/bpdm-common/pom.xml @@ -70,6 +70,28 @@ io.github.microutils kotlin-logging-jvm + + com.fasterxml.jackson.module + jackson-module-kotlin + + + + org.springframework.boot + spring-boot-starter-test + test + + + + org.mockito + mockito-core + + + + + org.assertj + assertj-core + test + diff --git a/bpdm-common/src/test/kotlin/org/eclipse/tractusx/bpdm/common/service/DataClassUnwrappedJsonDeserializerTest.kt b/bpdm-common/src/test/kotlin/org/eclipse/tractusx/bpdm/common/service/DataClassUnwrappedJsonDeserializerTest.kt new file mode 100644 index 000000000..c3367ea55 --- /dev/null +++ b/bpdm-common/src/test/kotlin/org/eclipse/tractusx/bpdm/common/service/DataClassUnwrappedJsonDeserializerTest.kt @@ -0,0 +1,213 @@ +/******************************************************************************* + * Copyright (c) 2021,2023 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://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. + * + * SPDX-License-Identifier: Apache-2.0 + ******************************************************************************/ + +package org.eclipse.tractusx.bpdm.common.service + +import com.fasterxml.jackson.annotation.JsonUnwrapped +import com.fasterxml.jackson.databind.DeserializationFeature +import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.databind.annotation.JsonDeserialize +import com.fasterxml.jackson.module.kotlin.KotlinModule +import com.fasterxml.jackson.module.kotlin.MissingKotlinParameterException +import org.assertj.core.api.Assertions.* +import org.junit.jupiter.api.Test + +class DataClassUnwrappedJsonDeserializerTest { + + val objectMapper = buildObjectMapper() + + @Test + fun `test standard cases`() { + testSerializeAndDeserialize(MainDto("a", 11, DetailDto("A", 23))) + testSerializeAndDeserialize(MainDto("a", null, DetailDto("A", 23))) + testSerializeAndDeserialize(MainDto("a", null, DetailDto(null, 23))) + + testSerializeAndDeserialize(WrapperDto(MainDto("a", null, DetailDto(null, 23)), "extra")) + testSerializeAndDeserialize(WrapperDto(MainDto("a", null, DetailDto(null, 23)), null)) + } + + @Test + fun `test complex object`() { + val d1 = DetailDto("A", 1) + val d2 = DetailDto(null, 2) + val complexDto = ComplexDto( + d1, Pair("first", 2), + listOf("a", "", "c"), + mapOf(Pair("d1", listOf(d1)), Pair("d2", listOf(d2)), Pair("all", listOf(d1, d2))) + ) + testSerializeAndDeserialize(complexDto) + } + + @Test + fun `test error handling`() { + // serialized MainDto + val strOkay = """ + {"key": "mykey", "detail2": 42} + """.trimIndent() + val objOkay = objectMapper.readValue(strOkay, MainDto::class.java) + assertThat(objOkay).isEqualTo(MainDto("mykey", null, DetailDto(null, 42))) + + // serialized MainDto + val strNullField = """ + {"key": null, "detail2": 42} + """.trimIndent() + // IllegalArgumentException because "key" must not be null! + assertThatIllegalArgumentException().isThrownBy { + objectMapper.readValue(strNullField, MainDto::class.java) + } + + // serialized MainDto + val strMissingField = """ + {"detail2": 42} + """.trimIndent() + // IllegalArgumentException because "key" must not be null! + assertThatIllegalArgumentException().isThrownBy { + objectMapper.readValue(strMissingField, MainDto::class.java) + } + + // serialized MainNonNullDto + val strMissingNestedField = """ + {"detail1": null, "detail2": 42, "key": "mykey"} + """.trimIndent() + // MissingKotlinParameterException because "detail1" in DetailNonNullDto must not be null! + assertThatExceptionOfType(MissingKotlinParameterException::class.java).isThrownBy { + objectMapper.readValue(strMissingNestedField, MainNonNullDto::class.java) + } + } + + @Test + fun `test limitations with null JsonUnwrapped property`() { + // @JsonUnwrapped property is null + val objOrg = MainDto("b", 42, null) + val objRestored = serializeAndDeserialize(objOrg) + // Deserialization of @JsonUnwrapped "details" property doesn't return null, but an "empty" DetailDto object! + assertThat(objRestored).isEqualTo( + MainDto( + key = objOrg.key, + optional = objOrg.optional, + details = DetailDto( // !!! it should be null, but we get an "empty" DetailDto + detail1 = null, + detail2 = 0 + ) + ) + ) + } + + @Test + fun `test limitations with nested null int attribute`() { + // serialized MainDto + val str = """ + {"key": "mykey", "detail1": "ABC", "detail2": null} + """.trimIndent() + val obj = objectMapper.readValue(str, MainDto::class.java) + // Standard deserializer (not DataClassUnwrappedJsonDeserializer) writes 0 into "detail2" (non-nullable) instead of throwing an exception + assertThat(obj).isEqualTo( + MainDto( + key = "mykey", + optional = null, + details = DetailDto( + detail1 = "ABC", + detail2 = 0 // !!! it should be null, but field is not nullable + ) + ) + ) + } + + @Test + fun `test limitations with top level null int attribute`() { + // serialized DetailDto + val str = """ + {"detail1": "ABC", "detail2": null} + """.trimIndent() + val obj = objectMapper.readValue(str, DetailDto::class.java) + // Standard deserializer writes 0 into "detail2" (non-nullable) instead of throwing an exception + assertThat(obj).isEqualTo( + DetailDto( + detail1 = "ABC", + detail2 = 0 // !!! it should be null, but field is not nullable + ) + ) + } + + @Test + fun `test standard error handling with top level null string attribute`() { + // serialized DetailNonNullDto + val str = """ + {"detail1": null, "detail2": 42} + """.trimIndent() + // Standard deserializer throws an exception because "detail1" is non-nullable and there is no default value for String + assertThatExceptionOfType(MissingKotlinParameterException::class.java).isThrownBy { + objectMapper.readValue(str, DetailNonNullDto::class.java) + } + } + + private fun buildObjectMapper() = ObjectMapper() + .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) + .registerModule(KotlinModule.Builder().build()) + + private inline fun testSerializeAndDeserialize(obj: T) { + assertThat(serializeAndDeserialize(obj)).isEqualTo(obj) + } + + private inline fun serializeAndDeserialize(obj: T): T { + val objString = objectMapper.writeValueAsString(obj) + return objectMapper.readValue(objString, T::class.java) + } +} + +@JsonDeserialize(using = DataClassUnwrappedJsonDeserializer::class) +data class WrapperDto( + @field:JsonUnwrapped + val dto: MainDto, + val extra: String?, +) + +@JsonDeserialize(using = DataClassUnwrappedJsonDeserializer::class) +data class MainDto( + val key: String, + val optional: Int?, + @field:JsonUnwrapped + val details: DetailDto? +) + +@JsonDeserialize(using = DataClassUnwrappedJsonDeserializer::class) +data class ComplexDto( + @field:JsonUnwrapped + val details: DetailDto, + val pair: Pair, + val list: Collection, + val map: Map>, +) + +@JsonDeserialize(using = DataClassUnwrappedJsonDeserializer::class) +data class MainNonNullDto( + val key: String, + @field:JsonUnwrapped + val detail: DetailNonNullDto, +) + +data class DetailDto( + val detail1: String?, + val detail2: Int, +) + +data class DetailNonNullDto( + val detail1: String, + val detail2: Int +) From 873a16bff60f4c42e27db56c8f3e35c2898bca7e Mon Sep 17 00:00:00 2001 From: Martin Kaeser Date: Thu, 23 Feb 2023 13:44:40 +0100 Subject: [PATCH 4/5] feat(common): Generic JsonDeserializer - Improve doc --- .../bpdm/common/service/DataClassUnwrappedJsonDeserializer.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bpdm-common/src/main/kotlin/org/eclipse/tractusx/bpdm/common/service/DataClassUnwrappedJsonDeserializer.kt b/bpdm-common/src/main/kotlin/org/eclipse/tractusx/bpdm/common/service/DataClassUnwrappedJsonDeserializer.kt index 312f986c7..7827c8daf 100644 --- a/bpdm-common/src/main/kotlin/org/eclipse/tractusx/bpdm/common/service/DataClassUnwrappedJsonDeserializer.kt +++ b/bpdm-common/src/main/kotlin/org/eclipse/tractusx/bpdm/common/service/DataClassUnwrappedJsonDeserializer.kt @@ -32,8 +32,8 @@ import kotlin.reflect.jvm.javaField import kotlin.reflect.jvm.javaType /** - * This is a generic JsonDeserializer that works with Kotlin data classes containing the annotation JsonUnwrapped. - * The new object is initialized via the primary constructor. + * Data classes using the annotation JsonUnwrapped are not supported out-of-the-box by jackson-module-kotlin and need a custom deserializer. + * This generic JsonDeserializer supports this use case. A new object is initialized via the primary constructor. */ class DataClassUnwrappedJsonDeserializer : JsonDeserializer(), ContextualDeserializer { // We need a ContextualDeserializer to find out the destination type. From fc579160b08591a2ec5f1e866256771727fe57b0 Mon Sep 17 00:00:00 2001 From: Martin Kaeser Date: Thu, 23 Feb 2023 13:47:09 +0100 Subject: [PATCH 5/5] feat(common): Generic JsonDeserializer - Changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 70ea07b74..e0578537a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,10 @@ The format is based on Keep a Changelog (https://keepachangelog.com/en/1.0.0/), - BPDM Pool: Update dependencies to mitigate vulnerabilities in old versions +### Changed + +- Replaced manual JSON deserializer implementations for various DTOs by generic DataClassUnwrappedJsonDeserializer. + ## [3.0.2] - 2022-02-15 ### Changed