#1352 - Change in implementation strategy for Maps in EntityModel.

We now use a custom serializer for EntityModel.getContent() as we need that to still return the Map content for programmatic clients. The serializer skips maps entirely and programmatically unwraps all other content.

A simple Converter (@JsonSerialize(convert = …)) to filter Maps unfortunately didn't do the trick as that causes a a delegating serializer to be registered for the property and @JsonUnwrapped stops working as it's designed to work with bean serializers only.

Adapted test cases accordingly.

Original pull request: #1353.
This commit is contained in:
Oliver Drotbohm
2020-08-11 23:27:53 +02:00
parent 69c81f24e4
commit 1607994fd2
2 changed files with 49 additions and 26 deletions

View File

@@ -15,20 +15,24 @@
*/
package org.springframework.hateoas;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import com.fasterxml.jackson.annotation.JsonAnyGetter;
import com.fasterxml.jackson.annotation.JsonAnySetter;
import com.fasterxml.jackson.annotation.JsonUnwrapped;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
import com.fasterxml.jackson.databind.util.NameTransformer;
/**
* A simple {@link EntityModel} wrapping a domain object and adding links to it.
@@ -116,10 +120,11 @@ public class EntityModel<T> extends RepresentationModel<EntityModel<T>> {
*
* @return the content
*/
@JsonUnwrapped
@Nullable
@JsonUnwrapped
@JsonSerialize(using = MapSuppressingUnwrappingSerializer.class)
public T getContent() {
return !Map.class.isInstance(content) ? content : null;
return content;
}
// Hacks to allow deserialization into an EntityModel<Map<String, Object>>
@@ -127,7 +132,7 @@ public class EntityModel<T> extends RepresentationModel<EntityModel<T>> {
@Nullable
@JsonAnyGetter
@SuppressWarnings("unchecked")
public Map<String, Object> getMapContent() {
private Map<String, Object> getMapContent() {
return Map.class.isInstance(content) ? (Map<String, Object>) content : null;
}
@@ -189,4 +194,36 @@ public class EntityModel<T> extends RepresentationModel<EntityModel<T>> {
result += content == null ? 0 : 17 * content.hashCode();
return result;
}
private static class MapSuppressingUnwrappingSerializer extends StdSerializer<Object> {
public MapSuppressingUnwrappingSerializer() {
super(Object.class);
}
/*
* (non-Javadoc)
* @see com.fasterxml.jackson.databind.ser.std.StdSerializer#serialize(java.lang.Object, com.fasterxml.jackson.core.JsonGenerator, com.fasterxml.jackson.databind.SerializerProvider)
*/
@Override
public void serialize(Object value, JsonGenerator gen, SerializerProvider provider) throws IOException {
if (value == null || Map.class.isInstance(value)) {
return;
}
provider.findValueSerializer(value.getClass()) //
.unwrappingSerializer(NameTransformer.NOP) //
.serialize(value, gen, provider);
}
/*
* (non-Javadoc)
* @see com.fasterxml.jackson.databind.JsonSerializer#isUnwrappingSerializer()
*/
@Override
public boolean isUnwrappingSerializer() {
return true;
}
}
}