From 16b7cf9997edc165e3f7084327c682eda9d6cfac Mon Sep 17 00:00:00 2001 From: Oliver Drotbohm Date: Wed, 14 Apr 2021 09:14:28 +0200 Subject: [PATCH] #1518 - Fix HAL link serialization issues when Jackson map entry sorting is enabled. Prior to this commit, the map of links to be rendered would key by LinkRelation. Unfortunately, Jackson requires `Map` keys to implement `Comparable` in case `SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS` is enabled. We now use the link relation's value as key right aways as Strings are comparable out of the box. Switched to use Spring's MultiValueMap to avoid having to deal with the value list initialization ourselves. --- .../hateoas/mediatype/hal/Jackson2HalModule.java | 14 ++++++-------- .../mediatype/hal/Jackson2HalIntegrationTest.java | 7 +++++++ 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/src/main/java/org/springframework/hateoas/mediatype/hal/Jackson2HalModule.java b/src/main/java/org/springframework/hateoas/mediatype/hal/Jackson2HalModule.java index 44b2b0f7..65452791 100644 --- a/src/main/java/org/springframework/hateoas/mediatype/hal/Jackson2HalModule.java +++ b/src/main/java/org/springframework/hateoas/mediatype/hal/Jackson2HalModule.java @@ -21,7 +21,6 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; -import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.function.Function; @@ -40,6 +39,8 @@ import org.springframework.hateoas.server.LinkRelationProvider; import org.springframework.lang.NonNull; import org.springframework.lang.Nullable; import org.springframework.util.Assert; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude.Include; @@ -148,7 +149,7 @@ public class Jackson2HalModule extends SimpleModule { public void serialize(Links value, JsonGenerator jgen, SerializerProvider provider) throws IOException { // sort links according to their relation - Map> sortedLinks = new LinkedHashMap<>(); + MultiValueMap sortedLinks = new LinkedMultiValueMap<>(); List links = new ArrayList<>(); boolean prefixingRequired = curieProvider != CurieProvider.NONE; @@ -181,10 +182,7 @@ public class Jackson2HalModule extends SimpleModule { curiedLinkPresent = true; } - sortedLinks // - .computeIfAbsent(relation, key -> new ArrayList<>())// - .add(toHalLink(link, relation)); - + sortedLinks.add(relation.value(), toHalLink(link, relation)); links.add(link); } @@ -193,12 +191,12 @@ public class Jackson2HalModule extends SimpleModule { Collection curies = curieProvider.getCurieInformation(Links.of(links)); if (!curies.isEmpty()) { - sortedLinks.put(HalLinkRelation.CURIES, new ArrayList<>(curies)); + sortedLinks.addAll(HalLinkRelation.CURIES.value(), new ArrayList<>(curies)); } } TypeFactory typeFactory = provider.getConfig().getTypeFactory(); - JavaType keyType = typeFactory.constructType(LinkRelation.class); + JavaType keyType = typeFactory.constructType(String.class); JavaType valueType = typeFactory.constructCollectionType(ArrayList.class, Object.class); JavaType mapType = typeFactory.constructMapType(HashMap.class, keyType, valueType); diff --git a/src/test/java/org/springframework/hateoas/mediatype/hal/Jackson2HalIntegrationTest.java b/src/test/java/org/springframework/hateoas/mediatype/hal/Jackson2HalIntegrationTest.java index f2924dba..62ef05fd 100755 --- a/src/test/java/org/springframework/hateoas/mediatype/hal/Jackson2HalIntegrationTest.java +++ b/src/test/java/org/springframework/hateoas/mediatype/hal/Jackson2HalIntegrationTest.java @@ -636,6 +636,13 @@ class Jackson2HalIntegrationTest { .isThrownBy(() -> document.read("$.curies", JSONObject.class)); } + @Test // #1515, #1518 + void rendersLinksWhenMapEntrySortingIsEnabled() throws Exception { + + mapper.enable(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS) + .writeValueAsString(new RepresentationModel<>().add(Link.of("/href"))); + } + @Relation(collectionRelation = "someSample") static class SomeSample { @JsonProperty String name;