Skip to content

Commit

Permalink
Fix constructor/bean properties and beans with an executable method (#…
Browse files Browse the repository at this point in the history
…650)

* Issue with Bson deserialization

* Fix constructor/bean properties and beans with an executable method

* Fix any getters

---------

Co-authored-by: radovanradic <radicr@gmail.com>
  • Loading branch information
dstepanov and radovanradic committed Nov 7, 2023
1 parent ecc6a0e commit 4571c6a
Show file tree
Hide file tree
Showing 6 changed files with 141 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,27 @@ class BsonMappingSpec extends Specification implements BsonJsonSpec, BsonBinaryS
book.pages == 12
}

def "test custom decoders deserializing a bean with a constructor"() {
given:
def bookArgument = Argument.of(BookWithConstructor)
Deserializer.DecoderContext context = serdeRegistry.newDecoderContext(null)

BsonObjectId id = new BsonObjectId()
def bookDoc = new BsonDocument()
bookDoc.put("title", new BsonString("xyz"))
bookDoc.put("objectId", id)
bookDoc.put("pages", new BsonInt32(12))

BsonReaderDecoder decoder = new BsonReaderDecoder(bookDoc.asBsonReader(), LimitingStream.DEFAULT_LIMITS)
when:
def deser = serdeRegistry.findDeserializer(BookWithConstructor).createSpecific(context, bookArgument)
def b = deser.deserialize(new BsonReaderDecoder(bookDoc.asBsonReader(), LimitingStream.DEFAULT_LIMITS), context, bookArgument)
then:
b.title == "xyz"
b.pages == 12
b.objectId == id.getValue()
}

def "test custom decoders deserializing 2"() {
given:
def bookArgument = Argument.of(Book)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package io.micronaut.serde.bson;

import io.micronaut.core.annotation.Creator;
import io.micronaut.core.annotation.NonNull;
import io.micronaut.serde.annotation.Serdeable;
import org.bson.types.ObjectId;

@Serdeable
public class BookWithConstructor {
@NonNull
private ObjectId objectId;
private String title;
private int pages;

public BookWithConstructor() {
}

@Creator
public BookWithConstructor(ObjectId objectId, String title, int pages) {
this.objectId = objectId;
this.title = title;
this.pages = pages;
}

public ObjectId getObjectId() {
return objectId;
}

public void setObjectId(ObjectId objectId) {
this.objectId = objectId;
}

public String getTitle() {
return title;
}

public void setTitle(String title) {
this.title = title;
}

public int getPages() {
return pages;
}

public void setPages(int pages) {
this.pages = pages;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,8 @@ public DeserBean(DeserializationConfiguration deserializationConfiguration,
creatorPropertiesBuilder.register(propertyName, derProperty, true);
}

this.creatorParams = creatorPropertiesBuilder.build();

if (hasBuilder) {
PropertiesBag.Builder<T> readPropertiesBuilder = new PropertiesBag.Builder<>(introspection);
BeanIntrospection.Builder<T> builder = introspection.builder();
Expand Down Expand Up @@ -294,7 +296,9 @@ public DeserBean(DeserializationConfiguration deserializationConfiguration,
annotationMetadata,
getPropertyNamingStrategy(annotationMetadata, decoderContext, entityPropertyNamingStrategy)
);

if (creatorParams != null && creatorParams.propertyIndexOf(propertyName) != -1) {
continue;
}
if (beanProperty.isReadOnly()) {
continue;
}
Expand Down Expand Up @@ -392,7 +396,7 @@ public AnnotationMetadata getAnnotationMetadata() {
this.wrapperProperty = introspection.stringValue(SerdeConfig.class, SerdeConfig.WRAPPER_PROPERTY).orElse(null);

this.anySetter = anySetterValue;
this.creatorParams = creatorPropertiesBuilder.build();

//noinspection unchecked
this.creatorUnwrapped = creatorUnwrapped != null ? creatorUnwrapped.toArray(new DerProperty[0]) : null;
//noinspection unchecked
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -155,8 +155,8 @@ public int getOrder() {
initializers.add(ctx -> initProperty(SerBean.this.jsonValue, ctx));
writeProperties = Collections.emptyList();
} else {
final Collection<BeanMethod<T, Object>> beanMethods = introspection.getBeanMethods();
final BeanMethod<T, Object> serMethod = beanMethods.stream()

final BeanMethod<T, Object> serMethod = introspection.getBeanMethods().stream()
.filter(m -> m.isAnnotationPresent(SerdeConfig.SerValue.class) || m.getAnnotationMetadata().hasAnnotation(JACKSON_VALUE))
.findFirst().orElse(null);
if (serMethod != null) {
Expand All @@ -174,11 +174,19 @@ public int getOrder() {
} else {
AnnotationMetadata annotationMetadata = new AnnotationMetadataHierarchy(introspection, type.getAnnotationMetadata());

final List<BeanMethod<T, Object>> jsonGetters = new ArrayList<>(introspection.getBeanMethods().size());
for (BeanMethod<T, Object> beanMethod : introspection.getBeanMethods()) {
if (beanMethod.isAnnotationPresent(SerdeConfig.SerGetter.class)
|| beanMethod.isAnnotationPresent(SerdeConfig.SerAnyGetter.class)) {
jsonGetters.add(beanMethod);
}
}

Optional<String> subType = annotationMetadata.stringValue(SerdeConfig.class, SerdeConfig.TYPE_NAME);
Set<String> addedProperties = CollectionUtils.newHashSet(properties.size());

if (!properties.isEmpty() || !beanMethods.isEmpty() || subType.isPresent()) {
writeProperties = new ArrayList<>(properties.size() + beanMethods.size());
if (!properties.isEmpty() || !jsonGetters.isEmpty() || subType.isPresent()) {
writeProperties = new ArrayList<>(properties.size() + jsonGetters.size());
subType.ifPresent(typeName -> {
String typeProperty = annotationMetadata.stringValue(SerdeConfig.class, SerdeConfig.TYPE_PROPERTY).orElse(null);
if (typeProperty != null) {
Expand Down Expand Up @@ -241,7 +249,7 @@ public int getOrder() {
writeProperties.add(serProperty);
}

for (BeanMethod<T, Object> jsonGetter : beanMethods) {
for (BeanMethod<T, Object> jsonGetter : jsonGetters) {
PropertyNamingStrategy propertyNamingStrategy = getPropertyNamingStrategy(jsonGetter.getAnnotationMetadata(), encoderContext, entityPropertyNamingStrategy);
final AnnotationMetadata jsonGetterAnnotationMetadata = jsonGetter.getAnnotationMetadata();
String resolvedPropertyName = resolveName(jsonGetterAnnotationMetadata,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,22 @@ abstract class AbstractBasicSerdeSpec extends Specification implements JsonSpec,
bean.bar() == 'buzz'
}

void "test a bean with an extra executable method"() {
when:
def bean = new BeanWithExtraMethod()
bean.name = "Bob"
def result = writeJson(jsonMapper, bean)

then:
jsonMatches(result, '{"name":"Bob"}')

when:
bean = jsonMapper.readValue(jsonAsBytes(result), Argument.of(BeanWithExtraMethod))

then:
bean.name == 'Bob'
}

def <T> T serializeDeserialize(T obj) {
def output = jsonMapper.writeValueAsBytes(obj)
return jsonMapper.readValue(output, Argument.of(obj.getClass())) as T
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* Copyright 2017-2021 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;

import io.micronaut.context.annotation.Executable;
import io.micronaut.serde.annotation.Serdeable;

@Serdeable
public class BeanWithExtraMethod {
private String name;

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

@Executable
public String something() {
return "xyz";
}
}

0 comments on commit 4571c6a

Please sign in to comment.