Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Small improvements, refactoring + more tests #749

Merged
merged 1 commit into from
Feb 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,31 @@ import spock.lang.Unroll

class JsonPropertySpec extends JsonCompileSpec {

void "missing nullable properties are not overwritten"() {
given:
def context = buildContext('example.Test', '''
package example;

import io.micronaut.core.annotation.Introspected;
import io.micronaut.core.annotation.Nullable;
import java.util.Optional;

@io.micronaut.serde.annotation.Serdeable
@Introspected(accessKind = Introspected.AccessKind.FIELD)
class Test {
@Nullable
public String foo = "bar";
}
''')

expect:
jsonMapper.readValue('{}', typeUnderTest).foo == 'bar'
jsonMapper.readValue('{"foo":null}', typeUnderTest).foo == null

cleanup:
context.close()
}

void "optional nullable mix"() {
given:
def context = buildContext('example.Test', '''
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -614,4 +614,63 @@ class Container {
cleanup:
context.close()
}

void "test custom deserializer"() {
given:
def context = buildContext('custom.CustomValue','''
package custom;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.micronaut.core.annotation.NonNull;
import io.micronaut.core.type.Argument;
import io.micronaut.serde.annotation.Serdeable;
import io.micronaut.serde.annotation.SerdeImport;
import io.micronaut.serde.Decoder;
import io.micronaut.serde.Deserializer;
import jakarta.inject.Singleton;
import java.io.IOException;

@Serdeable
record CustomValue(
@Serdeable.Deserializable(using = CustomSerde.class)
@JsonProperty("value")
@NonNull
Integer value
) {
@JsonCreator
public CustomValue {
}
}

@Singleton
class CustomSerde implements Deserializer<Integer> {
@Override
public Integer getDefaultValue(DecoderContext ignoredContext, Argument<? super Integer> ignoredType) {
return -2;
}

@Override
public Integer deserialize(Decoder decoder, DecoderContext context, Argument<? super Integer> type) throws IOException {
return decoder.decodeInt();
}

@Override
public Integer deserializeNullable(Decoder decoder, DecoderContext context, Argument<? super Integer> type) throws IOException {
if (decoder.decodeNull()) {
return -1;
}
return decoder.decodeInt();
}
}
''')

expect:
jsonMapper.readValue('{"value":1}', typeUnderTest).value() == 1
jsonMapper.readValue('{"value":null}', typeUnderTest).value() == -1
jsonMapper.readValue('{}', typeUnderTest).value() == -2

cleanup:
context.close()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,56 @@ import spock.lang.PendingFeature

class SerdeJsonPropertySpec extends JsonPropertySpec {

void "test optional by default primitive field in constructor XXX"() {

given:
def ctx = buildContext('test.Test', """
package test;

import com.fasterxml.jackson.annotation.JsonProperty;
import io.micronaut.serde.annotation.Serdeable;
import io.micronaut.core.annotation.Nullable;

@Serdeable
class Test {
private final $type value;

@com.fasterxml.jackson.annotation.JsonCreator
Test(@JsonProperty("value") $type value) {
this.value = value;
}

public $type getValue() {
return value;
}
}
""")

when:
def bean = jsonMapper.readValue('{}', argumentOf(ctx, 'test.Test'))
then:
bean.value == value

cleanup:
ctx.close()

where:
type | value
"byte" | (byte) 0
"short" | (short) 0
"int" | 0
"long" | 0L
"float" | 0F
"double" | 0D

"@Nullable Byte" | null
"@Nullable Short" | null
"@Nullable Integer" | null
"@Nullable Long" | null
"@Nullable Float" | null
"@Nullable Double" | null
}

void "implicit creator with parameter names"() {
given:
def context = buildContext('example.Test', '''
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import io.micronaut.core.annotation.AnnotationMetadata;
import io.micronaut.core.annotation.Creator;
import io.micronaut.core.annotation.Internal;
import io.micronaut.core.annotation.NextMajorVersion;
import io.micronaut.core.annotation.NonNull;
import io.micronaut.core.annotation.Nullable;
import io.micronaut.core.beans.BeanIntrospection;
Expand Down Expand Up @@ -177,22 +178,6 @@ public DeserBean(DeserializationConfiguration defaultDeserializationConfiguratio
}
if (annotationMetadata.isAnnotationPresent(SerdeConfig.SerAnySetter.class)) {
anySetterValue = new AnySetter<>(constructorArgument, i);
final String n = constructorArgument.getName();
creatorPropertiesBuilder.register(
n,
new DerProperty<>(
conversionService,
introspection,
i,
n,
constructorArgument,
null,
null,
null,
null
),
false
);
continue;
}

Expand Down Expand Up @@ -743,7 +728,7 @@ public static final class DerProperty<B, P> {
// Null when DeserBean not initialized
public Deserializer<P> deserializer;

public DerProperty(ConversionService conversionService,
DerProperty(ConversionService conversionService,
BeanIntrospection<B> introspection,
int index,
String property,
Expand All @@ -765,7 +750,7 @@ public DerProperty(ConversionService conversionService,
);
}

public DerProperty(ConversionService conversionService,
DerProperty(ConversionService conversionService,
BeanIntrospection<B> instrospection,
int index,
String property,
Expand Down Expand Up @@ -822,131 +807,91 @@ public DerProperty(ConversionService conversionService,
}

public void setDefaultPropertyValue(Deserializer.DecoderContext decoderContext, @NonNull B bean) throws SerdeException {
if (!explicitlyRequired) {
P def = defaultValue;
if (def == null) {
if (!mustSetField) {
return;
}
def = deserializer.getDefaultValue(decoderContext, argument);
}
if (def != null) {
if (beanProperty != null) {
beanProperty.setUnsafe(bean, def);
return;
}
}
if (explicitlyRequired) {
throw new SerdeException("Unable to deserialize type [" + instrospection.getBeanType().getName() + "]. Required property [" + argument +
"] is not present in supplied data");
}
P value = provideDefaultValue(decoderContext);
if (value != null) {
beanProperty.setUnsafe(bean, value);
}
throw new SerdeException("Unable to deserialize type [" + instrospection.getBeanType().getName() + "]. Required property [" + argument +
"] is not present in supplied data");
}

public void setDefaultConstructorValue(Deserializer.DecoderContext decoderContext, @NonNull Object[] params) throws SerdeException {
if (!explicitlyRequired) {
if (defaultValue != null) {
params[index] = defaultValue;
return;
}
if (!mustSetField && !argument.isPrimitive()) {
return;
}
P newDefaultValue = deserializer.getDefaultValue(decoderContext, argument);
if (newDefaultValue != null) {
params[index] = newDefaultValue;
return;
}
if (explicitlyRequired) {
throw new SerdeException("Unable to deserialize type [" + instrospection.getBeanType().getName() + "]. Required constructor parameter [" + argument + "] at index [" + index + "] is not present or is null in the supplied data");
}
throw new SerdeException("Unable to deserialize type [" + instrospection.getBeanType().getName() + "]. Required constructor parameter [" + argument + "] at index [" + index + "] is not present or is null in the supplied data");
params[index] = provideDefaultValue(decoderContext, mustSetField || argument.isPrimitive());
}

public void set(@NonNull B obj, @Nullable P v) throws SerdeException {
if (v == null && nonNull) {
throw new SerdeException("Unable to deserialize type [" + instrospection.getBeanType().getName() + "]. Required property [" + argument +
"] is not present in supplied data");

}
if (beanProperty != null) {
beanProperty.setUnsafe(obj, v);
public void set(@NonNull Deserializer.DecoderContext decoderContext, @NonNull B obj, @Nullable P value) throws SerdeException {
if (value == null) {
setDefaultPropertyValue(decoderContext, obj);
} else {
beanProperty.setUnsafe(obj, value);
}
}

public void deserializeAndSetConstructorValue(Decoder objectDecoder, Deserializer.DecoderContext decoderContext, Object[] values) throws IOException {
try {
P value = deserializer.deserializeNullable(objectDecoder, decoderContext, argument);
if (value == null && !nullable) {
if (!explicitlyRequired) {
value = defaultValue;
if (value == null) {
if (!mustSetField) {
return;
}
value = deserializer.getDefaultValue(decoderContext, argument);
}
} else {
throw new SerdeException("Unable to deserialize type [" + instrospection.getBeanType().getName() + "]. Required property [" + argument +
"] is not present in supplied data");
}
}
values[index] = value;
values[index] = deserializeValue(objectDecoder, decoderContext);
} catch (InvalidFormatException e) {
throw new InvalidPropertyFormatException(e, argument);
} catch (Exception e) {
throw new SerdeException("Error decoding property [" + argument + "] of type [" + instrospection.getBeanType() + "]: " + e.getMessage(), e);
}
}

@Nullable
@NextMajorVersion("Receiving a null value for a primitive or a non-null should produce an expection")
public void deserializeAndSetPropertyValue(Decoder objectDecoder, Deserializer.DecoderContext decoderContext, B beanInstance) throws IOException {
try {
P value = deserializer.deserializeNullable(objectDecoder, decoderContext, argument);
if (value == null && !nullable) {
if (!explicitlyRequired) {
value = defaultValue;
if (value == null) {
if (!mustSetField) {
return;
}
value = deserializer.getDefaultValue(decoderContext, argument);
}
} else {
throw new SerdeException("Unable to deserialize type [" + instrospection.getBeanType().getName() + "]. Required property [" + argument +
"] is not present in supplied data");
}
P value = deserializeValue(objectDecoder, decoderContext);
if (value != null || nullable) {
beanProperty.setUnsafe(beanInstance, value);
}
beanProperty.setUnsafe(beanInstance, value);
} catch (InvalidFormatException e) {
throw new InvalidPropertyFormatException(e, argument);
} catch (Exception e) {
throw new SerdeException("Error decoding property [" + argument + "] of type [" + instrospection.getBeanType() + "]: " + e.getMessage(), e);
}
}

@Nullable
public void deserializeAndCallBuilder(Decoder objectDecoder, Deserializer.DecoderContext decoderContext, BeanIntrospection.Builder<B> builder) throws IOException {
try {
P value = deserializer.deserializeNullable(objectDecoder, decoderContext, argument);
if (value == null && !nullable) {
if (!explicitlyRequired) {
value = defaultValue;
if (value == null) {
if (!mustSetField) {
return;
}
value = deserializer.getDefaultValue(decoderContext, argument);
}
} else {
throw new SerdeException("Unable to deserialize type [" + instrospection.getBeanType().getName() + "]. Required property [" + argument +
"] is not present in supplied data");
}
P value = deserializeValue(objectDecoder, decoderContext);
if (value != null || nullable) {
builder.with(index, argument, value);
}
builder.with(index, argument, value);
} catch (InvalidFormatException e) {
throw new InvalidPropertyFormatException(e, argument);
} catch (Exception e) {
throw new SerdeException("Error decoding property [" + argument + "] of type [" + instrospection.getBeanType() + "]: " + e.getMessage(), e);
}
}

private P deserializeValue(Decoder objectDecoder, Deserializer.DecoderContext decoderContext) throws IOException {
P value = deserializer.deserializeNullable(objectDecoder, decoderContext, argument);
if (value != null || nullable) {
return value;
}
if (explicitlyRequired) {
throw new SerdeException("Unable to deserialize type [" + instrospection.getBeanType().getName() + "]. Required property [" + argument +
"] is not present in supplied data");
}
return provideDefaultValue(decoderContext);
}

private P provideDefaultValue(Deserializer.DecoderContext decoderContext) {
return provideDefaultValue(decoderContext, mustSetField);
}

private P provideDefaultValue(Deserializer.DecoderContext decoderContext, boolean mustSetField) {
P value = defaultValue;
if (value == null && mustSetField) {
value = deserializer.getDefaultValue(decoderContext, argument);
}
return value;
}

}

Expand Down
Loading
Loading