Skip to content

Exception on HAL serialization with object mapper configured to sort map entries by keys #1515

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
frank256 opened this issue Apr 13, 2021 · 9 comments
Assignees
Labels
in: mediatypes Media type related functionality type: bug
Milestone

Comments

@frank256
Copy link

This concerns JSON HAL serialization using Spring Boot 2.4.4 / Spring HATEOAS 1.2.5.

When using an object mapper which has the feature SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS enabled (which is, unfortunately, mandatory for me in a legacy project), I will get an HttpMessageNotWritableException due to a JsonMappingException with the cause being that org.springframework.hateoas.mediatype.hal.HalLinkRelation cannot be cast to java.lang.Comparable.
This is due to the Jackson MapSerializer being used to serialize the links, which expects the map keys to be comparable if the feature is enabled. However, HalLinkRelation does not implement Comparable nor is the map (I understand the links are already sorted?) that is passed into it implementing SortedMap (this is checked).

I tried to customize the object mapper with a custom map serializer that adds the links to a temporary TreeMap so that they are not sorted again by the MapSerializer, however, as the map serializer is constructed directly within the Jackson2HalModule (lines 202-5), I currently do not see how I can intervene here.

I'd consider this more or less a bug (?), however, if you have a hint for a workaround, I'd also appreciate that very much!

Disabling the feature is, unfortunately, not an option for me. It worked with Spring HATEOAS before 1.0.0.

@odrotbohm odrotbohm self-assigned this Apr 13, 2021
@odrotbohm odrotbohm added in: configuration Configuration and setup in: mediatypes Media type related functionality process: waiting for feedback labels Apr 13, 2021
@odrotbohm
Copy link
Member

Can you provide an example that's reproducing the error? I think we should be able to work around this Jackson limitation by mapping the LinkRelation keys to their String representation.

@frank256
Copy link
Author

frank256 commented Apr 13, 2021

Thanks for the fast reply! I cannot attach code from the original project, so I tried to prepare a very minimal demo using Spring Data Rest. Of course, there would not be any meaningful sorting taking place here. But you can see the exception.

The original project also makes heavy use of custom RepresentationModels, RepresentationModelProcessors and other HATEOAS functionality not related to Spring Data Rest.

spring-hateoas-hal-map-sort-demo.zip

edit: See SelfLinkTest for exceptions.

@odrotbohm
Copy link
Member

Actually, it should be reproducible using an ObjectMapper with the HAL customizations registered and the serialization settings tweaked to what you described. I'll have a look at your sample and see where I can take this.

odrotbohm added a commit that referenced this issue Apr 16, 2021
…ing 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.
@odrotbohm odrotbohm added this to the 1.4 M1 milestone Apr 16, 2021
@odrotbohm
Copy link
Member

This is in place for 1.4, 1.3 and 1.2 snapshots. Feel free to give it a spin.

@frank256
Copy link
Author

Thank you very much!

Tried the 1.2.6 snapshot today. Unfortunately, there is still an exception of the same type thrown when rendering embedded collections (via line 338 of Jackson2HalModule). Sorry, I did not catch this one when analyzing it at first.

Stack trace:

Caused by: java.lang.ClassCastException: class org.springframework.hateoas.mediatype.hal.HalLinkRelation cannot be cast to class java.lang.Comparable (org.springframework.hateoas.mediatype.hal.HalLinkRelation is in unnamed module of loader 'app'; java.lang.Comparable is in module java.base of loader 'bootstrap')
	at java.base/java.util.TreeMap.compare(TreeMap.java:1291) ~[na:na]
	at java.base/java.util.TreeMap.put(TreeMap.java:536) ~[na:na]
	at java.base/java.util.AbstractMap.putAll(AbstractMap.java:281) ~[na:na]
	at java.base/java.util.TreeMap.putAll(TreeMap.java:325) ~[na:na]
	at java.base/java.util.TreeMap.<init>(TreeMap.java:185) ~[na:na]
	at com.fasterxml.jackson.databind.ser.std.MapSerializer._orderEntries(MapSerializer.java:1092) ~[jackson-databind-2.11.4.jar:2.11.4]
	at com.fasterxml.jackson.databind.ser.std.MapSerializer.serializeWithoutTypeInfo(MapSerializer.java:671) ~[jackson-databind-2.11.4.jar:2.11.4]
	at com.fasterxml.jackson.databind.ser.std.MapSerializer.serialize(MapSerializer.java:637) ~[jackson-databind-2.11.4.jar:2.11.4]
	at com.fasterxml.jackson.databind.ser.std.MapSerializer.serialize(MapSerializer.java:33) ~[jackson-databind-2.11.4.jar:2.11.4]
	at org.springframework.hateoas.mediatype.hal.Jackson2HalModule$HalResourcesSerializer.serialize(Jackson2HalModule.java:338) ~[spring-hateoas-1.2.6-20210416.141502-3.jar:1.2.6-SNAPSHOT]
	at org.springframework.hateoas.mediatype.hal.Jackson2HalModule$HalResourcesSerializer.serialize(Jackson2HalModule.java:290) ~[spring-hateoas-1.2.6-20210416.141502-3.jar:1.2.6-SNAPSHOT]
	at com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:728) ~[jackson-databind-2.11.4.jar:2.11.4]
	at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:755) ~[jackson-databind-2.11.4.jar:2.11.4]

@frank256
Copy link
Author

@odrotbohm Ping, since I'm not sure whether you see this. : )

@odrotbohm
Copy link
Member

I get notified by GitHub, thanks 🙃.

@odrotbohm odrotbohm reopened this Apr 20, 2021
odrotbohm added a commit that referenced this issue Apr 20, 2021
We now also eagerly map the keys for the _embedded map in a HAL response to Strings to make sure they work properly when sorting of map keys is activated for the Jackson ObjectMapper.
odrotbohm added a commit that referenced this issue Apr 20, 2021
We now also eagerly map the keys for the _embedded map in a HAL response to Strings to make sure they work properly when sorting of map keys is activated for the Jackson ObjectMapper.
odrotbohm added a commit that referenced this issue Apr 20, 2021
We now also eagerly map the keys for the _embedded map in a HAL response to Strings to make sure they work properly when sorting of map keys is activated for the Jackson ObjectMapper.
@odrotbohm
Copy link
Member

Fixes pushed for that. Would be cool if you could take the snapshots for another spin.

@frank256
Copy link
Author

Tried the new snapshot. Everything seems to work now.
Awesome! Thank you! 👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: mediatypes Media type related functionality type: bug
Projects
None yet
Development

No branches or pull requests

2 participants