Skip to content

Commit

Permalink
Small improvements, refactoring + more tests (#749)
Browse files Browse the repository at this point in the history
  • Loading branch information
dstepanov authored Feb 1, 2024
1 parent cf00261 commit e63074b
Show file tree
Hide file tree
Showing 5 changed files with 211 additions and 143 deletions.
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

0 comments on commit e63074b

Please sign in to comment.