Skip to content

Commit

Permalink
Backport fixes from 4.0.x and fix ConvertibleValuesDeserializer (#8489)
Browse files Browse the repository at this point in the history
* Don't include write only properties in serialization (#8457)

Co-authored-by: yawkat <jonas.konrad@oracle.com>
  • Loading branch information
graemerocher and yawkat committed Dec 15, 2022
1 parent aa22661 commit 1e98d85
Show file tree
Hide file tree
Showing 9 changed files with 258 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
Expand Down Expand Up @@ -137,4 +138,20 @@ protected Map<CharSequence, List<V>> wrapValues(Map<CharSequence, List<V>> value
return Collections.unmodifiableMap(values);
}

@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
ConvertibleMultiValuesMap<?> that = (ConvertibleMultiValuesMap<?>) o;
return values.equals(that.values);
}

@Override
public int hashCode() {
return Objects.hash(values);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
Expand Down Expand Up @@ -104,4 +105,21 @@ public Collection<V> values() {
public static <V> ConvertibleValues<V> empty() {
return EMPTY;
}

@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
ConvertibleValuesMap<?> that = (ConvertibleValuesMap<?>) o;
return map.equals(that.map);
}

@Override
public int hashCode() {
return Objects.hash(map);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -422,7 +422,7 @@ public P get(@NonNull B bean) {
throw new IllegalArgumentException("Invalid bean [" + bean + "] for type: " + beanType);
}
if (isWriteOnly()) {
throw new UnsupportedOperationException("Cannot read from a write-only property");
throw new UnsupportedOperationException("Cannot read from a write-only property: " + getName());
}
return dispatchOne(ref.getMethodIndex, bean, null);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -347,7 +347,9 @@ public JsonSerializer<?> build() {
final List<BeanPropertyWriter> newProperties = new ArrayList<>(properties);
Map<String, BeanProperty<Object, Object>> named = new LinkedHashMap<>();
for (BeanProperty<Object, Object> beanProperty : beanProperties) {
named.put(getName(config, namingStrategy, beanProperty), beanProperty);
if (!beanProperty.isWriteOnly()) {
named.put(getName(config, namingStrategy, beanProperty), beanProperty);
}
}
for (int i = 0; i < properties.size(); i++) {
final BeanPropertyWriter existing = properties.get(i);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,12 @@ public void serialize(ConvertibleMultiValues<?> value, JsonGenerator gen, Serial
if (len > 0) {
gen.writeFieldName(fieldName);
if (len == 1) {
gen.writeObject(v.get(0));
serializers.defaultSerializeValue(v.get(0), gen);
} else {
gen.writeStartArray();

for (Object o : v) {
gen.writeObject(o);
serializers.defaultSerializeValue(o, gen);
}
gen.writeEndArray();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ public void serialize(ConvertibleValues<?> value, JsonGenerator gen, SerializerP
Object v = entry.getValue();
if (v != null) {
gen.writeFieldName(fieldName);
gen.writeObject(v);
serializers.defaultSerializeValue(v, gen);
}
}
gen.writeEndObject();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,16 +32,145 @@ import io.micronaut.http.hateoas.JsonError
import io.micronaut.jackson.JacksonConfiguration
import io.micronaut.jackson.modules.testcase.EmailTemplate
import io.micronaut.jackson.modules.testcase.Notification
import io.micronaut.jackson.modules.wrappers.*
import io.micronaut.jackson.modules.testclasses.HTTPCheck
import io.micronaut.jackson.modules.testclasses.InstanceInfo
import io.micronaut.jackson.modules.wrappers.BooleanWrapper
import io.micronaut.jackson.modules.wrappers.DoubleWrapper
import io.micronaut.jackson.modules.wrappers.IntWrapper
import io.micronaut.jackson.modules.wrappers.IntegerWrapper
import io.micronaut.jackson.modules.wrappers.LongWrapper
import io.micronaut.jackson.modules.wrappers.StringWrapper
import spock.lang.Issue
import spock.lang.Unroll
import spock.lang.Specification
import spock.lang.Unroll

import java.beans.ConstructorProperties
import java.time.LocalDateTime

class BeanIntrospectionModuleSpec extends Specification {

void "test serialize/deserialize wrap/unwrap - simple"() {
given:
ApplicationContext ctx = ApplicationContext.run(
'jackson.deserialization.UNWRAP_ROOT_VALUE': true,
'jackson.serialization.WRAP_ROOT_VALUE': true
)
ObjectMapper objectMapper = ctx.getBean(ObjectMapper)

when:
Author author = new Author(name:"Bob")

def result = objectMapper.writeValueAsString(author)

then:
result == '{"Author":{"name":"Bob"}}'

when:
def read = objectMapper.readValue(result, Author)

then:
author == read

}

void "test serialize/deserialize wrap/unwrap -* complex"() {
given:
ApplicationContext ctx = ApplicationContext.run(
'jackson.deserialization.UNWRAP_ROOT_VALUE': true,
'jackson.serialization.WRAP_ROOT_VALUE': true
)
ObjectMapper objectMapper = ctx.getBean(ObjectMapper)

when:
HTTPCheck check = new HTTPCheck(headers:[
Accept:['application/json', 'application/xml']
] )

def result = objectMapper.writeValueAsString(check)

then:
result == '{"HTTPCheck":{"Header":{"Accept":["application/json","application/xml"]}}}'

when:
def read = objectMapper.readValue(result, HTTPCheck)

then:
check == read

}

void "test serialize/deserialize wrap/unwrap -* constructors"() {
given:
ApplicationContext ctx = ApplicationContext.run(
'jackson.deserialization.UNWRAP_ROOT_VALUE': true,
'jackson.serialization.WRAP_ROOT_VALUE': true
)
ObjectMapper objectMapper = ctx.getBean(ObjectMapper)

when:
IntrospectionCreator check = new IntrospectionCreator("test")

def result = objectMapper.writeValueAsString(check)

then:
result == '{"IntrospectionCreator":{"label":"TEST"}}'

when:
def read = objectMapper.readValue(result, IntrospectionCreator)

then:
check == read

}

void "test serialize/deserialize wrap/unwrap -* constructors & JsonRootName"() {
given:
ApplicationContext ctx = ApplicationContext.run(
'jackson.deserialization.UNWRAP_ROOT_VALUE': true,
'jackson.serialization.WRAP_ROOT_VALUE': true
)
ObjectMapper objectMapper = ctx.getBean(ObjectMapper)

when:
InstanceInfo check = new InstanceInfo("test")

def result = objectMapper.writeValueAsString(check)

then:
result == '{"instance":{"hostName":"test"}}'

when:
def read = objectMapper.readValue(result, InstanceInfo)

then:
check == read

}


void "test serialize/deserialize convertible values"() {
given:
ApplicationContext ctx = ApplicationContext.run()
ObjectMapper objectMapper = ctx.getBean(ObjectMapper)

when:
HTTPCheck check = new HTTPCheck(headers:[
Accept:['application/json', 'application/xml']
] )

def result = objectMapper.writeValueAsString(check)

then:
result == '{"Header":{"Accept":["application/json","application/xml"]}}'

when:
def read = objectMapper.readValue(result, HTTPCheck)

then:
check.header.getAll("Accept") == read.header.getAll("Accept")

}

void "Bean introspection works with a bean without JsonIgnore annotations"() {
given:
ApplicationContext ctx = ApplicationContext.run()
Expand Down Expand Up @@ -598,6 +727,7 @@ class BeanIntrospectionModuleSpec extends Specification {
}

@Introspected
@EqualsAndHashCode
static class Author {
String name
}
Expand Down Expand Up @@ -826,6 +956,7 @@ class BeanIntrospectionModuleSpec extends Specification {
}

@Introspected
@EqualsAndHashCode
static class IntrospectionCreator {
private final String name

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package io.micronaut.jackson.modules.testclasses;

import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.PropertyNamingStrategies;
import com.fasterxml.jackson.databind.annotation.JsonNaming;
import io.micronaut.core.annotation.Introspected;
import io.micronaut.core.convert.value.ConvertibleMultiValues;

import java.util.List;
import java.util.Map;
import java.util.Objects;

@JsonNaming(PropertyNamingStrategies.UpperCamelCaseStrategy.class)
@Introspected
public class HTTPCheck {
private ConvertibleMultiValues<String> headers = ConvertibleMultiValues.empty();

public ConvertibleMultiValues<String> getHeader() {
return headers;
}

/**
* @param headers The headers
*/
@JsonProperty("Header")
public void setHeaders(Map<CharSequence, List<String>> headers) {
if (headers == null) {
this.headers = ConvertibleMultiValues.empty();
} else {
this.headers = ConvertibleMultiValues.of(headers);
}
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
HTTPCheck httpCheck = (HTTPCheck) o;
return headers.equals(httpCheck.headers);
}

@Override
public int hashCode() {
return Objects.hash(headers);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package io.micronaut.jackson.modules.testclasses;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonRootName;
import io.micronaut.core.annotation.Introspected;

import java.util.Objects;

@JsonRootName("instance")
@Introspected
public class InstanceInfo {
private final String hostName;

@JsonCreator
InstanceInfo(
@JsonProperty("hostName") String hostName) {
this.hostName = hostName;
}

public String getHostName() {
return hostName;
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
InstanceInfo that = (InstanceInfo) o;
return hostName.equals(that.hostName);
}

@Override
public int hashCode() {
return Objects.hash(hostName);
}
}

0 comments on commit 1e98d85

Please sign in to comment.