From 9ca5d18b6686d9df3b0408b82ac60820fc93dc0f Mon Sep 17 00:00:00 2001 From: Jonas Konrad Date: Wed, 27 Sep 2023 12:24:43 +0200 Subject: [PATCH] Move SimpleBuilderDeserializer into SpecificObjectDeserializer (#583) This adds support for builders on subtypes. --- .../serde/jackson/builder/BuilderSpec.groovy | 12 +++ .../jackson/builder/TestBuildSubtype.java | 36 +++++++ .../jackson/builder/TestBuildSupertype.java | 23 +++++ .../deserializers/ObjectDeserializer.java | 9 -- .../SimpleBuilderDeserializer.java | 98 ------------------- .../SpecificObjectDeserializer.java | 37 +++++++ 6 files changed, 108 insertions(+), 107 deletions(-) create mode 100644 serde-jackson/src/test/java/io/micronaut/serde/jackson/builder/TestBuildSubtype.java create mode 100644 serde-jackson/src/test/java/io/micronaut/serde/jackson/builder/TestBuildSupertype.java delete mode 100644 serde-support/src/main/java/io/micronaut/serde/support/deserializers/SimpleBuilderDeserializer.java diff --git a/serde-jackson/src/test/groovy/io/micronaut/serde/jackson/builder/BuilderSpec.groovy b/serde-jackson/src/test/groovy/io/micronaut/serde/jackson/builder/BuilderSpec.groovy index 8d39d47b6..df75b9ce0 100644 --- a/serde-jackson/src/test/groovy/io/micronaut/serde/jackson/builder/BuilderSpec.groovy +++ b/serde-jackson/src/test/groovy/io/micronaut/serde/jackson/builder/BuilderSpec.groovy @@ -44,4 +44,16 @@ class BuilderSpec extends Specification { then: result == json } + + void "test deserialize builder on subtype"() { + given: + def json = '{"sub":{"foo":"fizz","bar":"buzz"}}' + + when: + def value = objectMapper.readValue(json, TestBuildSupertype) + + then: + value.foo == 'fizz' + value.bar == 'buzz' + } } diff --git a/serde-jackson/src/test/java/io/micronaut/serde/jackson/builder/TestBuildSubtype.java b/serde-jackson/src/test/java/io/micronaut/serde/jackson/builder/TestBuildSubtype.java new file mode 100644 index 000000000..33b7b7af3 --- /dev/null +++ b/serde-jackson/src/test/java/io/micronaut/serde/jackson/builder/TestBuildSubtype.java @@ -0,0 +1,36 @@ +package io.micronaut.serde.jackson.builder; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; + +@JsonDeserialize(builder = TestBuildSubtype.Builder.class) +public class TestBuildSubtype extends TestBuildSupertype { + private final String bar; + + private TestBuildSubtype(String foo, String bar) { + super(foo); + this.bar = bar; + } + + public String getBar() { + return bar; + } + + public static class Builder { + private String foo; + private String bar; + + public Builder foo(String foo) { + this.foo = foo; + return this; + } + + public Builder bar(String bar) { + this.bar = bar; + return this; + } + + public TestBuildSubtype build() { + return new TestBuildSubtype(foo, bar); + } + } +} diff --git a/serde-jackson/src/test/java/io/micronaut/serde/jackson/builder/TestBuildSupertype.java b/serde-jackson/src/test/java/io/micronaut/serde/jackson/builder/TestBuildSupertype.java new file mode 100644 index 000000000..5c5152f96 --- /dev/null +++ b/serde-jackson/src/test/java/io/micronaut/serde/jackson/builder/TestBuildSupertype.java @@ -0,0 +1,23 @@ +package io.micronaut.serde.jackson.builder; + +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonTypeInfo; + +@JsonSubTypes({ + @JsonSubTypes.Type(value = TestBuildSubtype.class, name = "sub") +}) +@JsonTypeInfo( + use = JsonTypeInfo.Id.NAME, + include = JsonTypeInfo.As.WRAPPER_OBJECT +) +public abstract class TestBuildSupertype { + private final String foo; + + TestBuildSupertype(String foo) { + this.foo = foo; + } + + public String getFoo() { + return foo; + } +} diff --git a/serde-support/src/main/java/io/micronaut/serde/support/deserializers/ObjectDeserializer.java b/serde-support/src/main/java/io/micronaut/serde/support/deserializers/ObjectDeserializer.java index f8a1eef49..80344fe36 100644 --- a/serde-support/src/main/java/io/micronaut/serde/support/deserializers/ObjectDeserializer.java +++ b/serde-support/src/main/java/io/micronaut/serde/support/deserializers/ObjectDeserializer.java @@ -71,15 +71,6 @@ public Deserializer createSpecific(DecoderContext context, Argument type1) -> decoder.decodeArbitrary(); } DeserBean deserBean = getDeserializableBean(type, context); - if (deserBean.hasBuilder) { - return new SimpleBuilderDeserializer( - deserBean.readProperties, - deserBean.introspection, - preInstantiateCallback, - ignoreUnknown, - strictNullable - ); - } if (deserBean.simpleBean) { return new SimpleObjectDeserializer(ignoreUnknown, strictNullable, deserBean, preInstantiateCallback); } diff --git a/serde-support/src/main/java/io/micronaut/serde/support/deserializers/SimpleBuilderDeserializer.java b/serde-support/src/main/java/io/micronaut/serde/support/deserializers/SimpleBuilderDeserializer.java deleted file mode 100644 index e5c0e85e8..000000000 --- a/serde-support/src/main/java/io/micronaut/serde/support/deserializers/SimpleBuilderDeserializer.java +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright 2017-2023 original authors - * - * 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 - * - * 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. - */ -package io.micronaut.serde.support.deserializers; - -import java.io.IOException; - -import io.micronaut.core.annotation.NonNull; -import io.micronaut.core.annotation.Nullable; -import io.micronaut.core.beans.BeanIntrospection; -import io.micronaut.core.type.Argument; -import io.micronaut.serde.Decoder; -import io.micronaut.serde.Deserializer; -import io.micronaut.serde.exceptions.SerdeException; - -final class SimpleBuilderDeserializer implements Deserializer { - private final PropertiesBag builderParameters; - private final BeanIntrospection introspection; - @Nullable - private final SerdeDeserializationPreInstantiateCallback preInstantiateCallback; - private final boolean ignoreUnknown; - private final boolean strictNullable; - - SimpleBuilderDeserializer( - PropertiesBag builderParameters, - BeanIntrospection introspection, - SerdeDeserializationPreInstantiateCallback preInstantiateCallback, boolean ignoreUnknown, boolean strictNullable) { - this.builderParameters = builderParameters; - this.introspection = introspection; - this.preInstantiateCallback = preInstantiateCallback; - this.ignoreUnknown = ignoreUnknown; - this.strictNullable = strictNullable; - } - - @Override - public Object deserialize(Decoder decoder, DecoderContext context, Argument type) throws IOException { - BeanIntrospection.Builder builder = introspection.builder(); - Decoder objectDecoder = decoder.decodeObject(type); - - if (builderParameters != null) { - PropertiesBag.Consumer propertiesConsumer = builderParameters.newConsumer(); - - boolean allConsumed = false; - while (!allConsumed) { - final String prop = objectDecoder.decodeKey(); - if (prop == null) { - break; - } - final DeserBean.DerProperty consumedProperty = propertiesConsumer.consume(prop); - if (consumedProperty != null) { - consumedProperty.deserializeAndCallBuilder(objectDecoder, context, builder); - allConsumed = propertiesConsumer.isAllConsumed(); - - } else if (ignoreUnknown) { - objectDecoder.skipValue(); - } else { - throw unknownProperty(type, prop); - } - } - } - - if (ignoreUnknown) { - objectDecoder.finishStructure(true); - } else { - String unknownProp = objectDecoder.decodeKey(); - if (unknownProp != null) { - throw unknownProperty(type, unknownProp); - } - objectDecoder.finishStructure(); - } - - return builder.build(); - } - - @Override - public Object deserializeNullable(@NonNull Decoder decoder, @NonNull DecoderContext context, @NonNull Argument type) throws IOException { - if (decoder.decodeNull()) { - return null; - } - return deserialize(decoder, context, type); - } - - private SerdeException unknownProperty(Argument beanType, String prop) { - return new SerdeException("Unknown property [" + prop + "] encountered during deserialization of type: " + beanType); - } -} diff --git a/serde-support/src/main/java/io/micronaut/serde/support/deserializers/SpecificObjectDeserializer.java b/serde-support/src/main/java/io/micronaut/serde/support/deserializers/SpecificObjectDeserializer.java index 330722f6f..feae4e1e5 100644 --- a/serde-support/src/main/java/io/micronaut/serde/support/deserializers/SpecificObjectDeserializer.java +++ b/serde-support/src/main/java/io/micronaut/serde/support/deserializers/SpecificObjectDeserializer.java @@ -17,6 +17,7 @@ import io.micronaut.core.annotation.NonNull; import io.micronaut.core.annotation.Nullable; +import io.micronaut.core.beans.BeanIntrospection; import io.micronaut.core.reflect.exception.InstantiationException; import io.micronaut.core.type.Argument; import io.micronaut.core.util.ArrayUtils; @@ -328,6 +329,42 @@ public Object deserialize(Decoder decoder, DecoderContext decoderContext, Argume decoderContext ); } + } else if (db.hasBuilder) { + BeanIntrospection.Builder builder; + try { + if (preInstantiateCallback != null) { + preInstantiateCallback.preInstantiate(db.introspection); + } + builder = db.introspection.builder(); + } catch (InstantiationException e) { + throw new SerdeException(PREFIX_UNABLE_TO_DESERIALIZE_TYPE + type + "]: " + e.getMessage(), e); + } + if (hasProperties) { + while (true) { + final String prop = objectDecoder.decodeKey(); + if (prop == null) { + break; + } + final DeserBean.DerProperty property = readProperties.consume(prop); + if (property != null) { + property.deserializeAndCallBuilder(objectDecoder, decoderContext, builder); + } else { + skipOrSetAny( + decoderContext, + objectDecoder, + prop, + anyValues, + ignoreUnknown, + type + ); + } + } + } + try { + obj = builder.build(); + } catch (InstantiationException e) { + throw new SerdeException(PREFIX_UNABLE_TO_DESERIALIZE_TYPE + type + "]: " + e.getMessage(), e); + } } else { try { if (preInstantiateCallback != null) {