From e93985236251c704e96434304b3f23ede0b2c424 Mon Sep 17 00:00:00 2001 From: Oliver Drotbohm Date: Wed, 14 Apr 2021 09:14:28 +0200 Subject: [PATCH] #1515 - 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 1b32b538..d025e12a 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; @@ -41,6 +40,8 @@ import org.springframework.hateoas.mediatype.hal.HalConfiguration.RenderSingleLi import org.springframework.hateoas.server.LinkRelationProvider; 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; @@ -145,7 +146,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; @@ -178,10 +179,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); } @@ -190,12 +188,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 ec4d133a..44306f67 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 + 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;