Skip to content

Commit bddf391

Browse files
authored
Implement new SerializationFeature.IGNORE_FAILURE_TO_ORDER_MAP_ENTRIES_BY_INCOMPARABLE_KEYS (#4778)
1 parent c775a3f commit bddf391

File tree

5 files changed

+76
-12
lines changed

5 files changed

+76
-12
lines changed

release-notes/VERSION-2.x

+4
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@ Project: jackson-databind
2020
#4676: Support other enum naming strategies than camelCase
2121
(requested by @hajdamak)
2222
(contributed by Lars B)
23+
#4773: `SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS` should not apply to Maps
24+
with uncomparable keys
25+
(requested by @nathanukey)
26+
(fix by Joo-Hyuk K)
2327

2428
2.18.1 (28-Oct-2024)
2529

src/main/java/com/fasterxml/jackson/databind/SerializationFeature.java

+17
Original file line numberDiff line numberDiff line change
@@ -434,6 +434,23 @@ public enum SerializationFeature implements ConfigFeature
434434
*/
435435
ORDER_MAP_ENTRIES_BY_KEYS(false),
436436

437+
/**
438+
* Feature that determines whether to intentionally fail when the mapper attempts to
439+
* order map entries with incomparable keys by accessing the first key of the map.
440+
* So depending on the Map implementation, this may not be the same key every time.
441+
* <p>
442+
* If enabled, will simply fail by throwing an exception.
443+
* If disabled, will not throw an exception and instead simply return the original map.
444+
* <p>
445+
* Note that this feature will apply only when configured to order map entries by keys, either
446+
* through annotation or enabling {@link #ORDER_MAP_ENTRIES_BY_KEYS}.
447+
* <p>
448+
* Feature is enabled by default and will default false in Jackson 3 and later.
449+
*
450+
* @since 2.19
451+
*/
452+
FAIL_ON_ORDER_MAP_BY_INCOMPARABLE_KEY(true),
453+
437454
/*
438455
/******************************************************
439456
/* Other

src/main/java/com/fasterxml/jackson/databind/ser/std/MapSerializer.java

+20
Original file line numberDiff line numberDiff line change
@@ -1166,6 +1166,26 @@ protected Map<?,?> _orderEntries(Map<?,?> input, JsonGenerator gen,
11661166
if (input instanceof SortedMap<?,?>) {
11671167
return input;
11681168
}
1169+
// or is it empty? then no need to sort either
1170+
if (input.isEmpty()) {
1171+
return input;
1172+
}
1173+
// [databind#4773] Since 2.19: We should not try sorting Maps with uncomparable keys
1174+
// And first key is a good enough sample for now.
1175+
Object firstKey = input.keySet().iterator().next();
1176+
if (!Comparable.class.isInstance(firstKey)) {
1177+
// We cannot sort incomparable keys, should we fail or just skip sorting?
1178+
if (!provider.isEnabled(SerializationFeature.FAIL_ON_ORDER_MAP_BY_INCOMPARABLE_KEY)) {
1179+
return input;
1180+
} else {
1181+
Class<?> clazz = firstKey == null ? Object.class : firstKey.getClass();
1182+
provider.reportBadDefinition(clazz,
1183+
String.format("Cannot order Map entries by key of incomparable type %s, consider disabling " +
1184+
"`SerializationFeature.FAIL_ON_ORDER_MAP_BY_INCOMPARABLE_KEY` to simply skip sorting",
1185+
ClassUtil.classNameOf(firstKey)));
1186+
}
1187+
}
1188+
11691189
// [databind#1411]: TreeMap does not like null key... (although note that
11701190
// check above should prevent this code from being called in that case)
11711191
// [databind#153]: but, apparently, some custom Maps do manage hit this
+33-11
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package com.fasterxml.jackson.databind.tofix;
1+
package com.fasterxml.jackson.databind.deser;
22

33
import org.junit.jupiter.api.Test;
44

@@ -8,11 +8,10 @@
88

99
import com.fasterxml.jackson.databind.ObjectMapper;
1010
import com.fasterxml.jackson.databind.SerializationFeature;
11+
import com.fasterxml.jackson.databind.exc.InvalidDefinitionException;
1112
import com.fasterxml.jackson.databind.testutil.DatabindTestUtil;
12-
import com.fasterxml.jackson.databind.testutil.failure.JacksonTestFailureExpected;
1313

14-
import static org.junit.jupiter.api.Assertions.assertEquals;
15-
import static org.junit.jupiter.api.Assertions.assertTrue;
14+
import static org.junit.jupiter.api.Assertions.*;
1615

1716
// [databind#4773] Test to verify `SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS` behavior
1817
// when serializing `Map` instances with un-comparable keys.
@@ -32,9 +31,8 @@ public static class ObjectContainer4773 {
3231
.configure(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS, true)
3332
.build();
3433

35-
@JacksonTestFailureExpected
3634
@Test
37-
void testSerializationWithIncomparableKeys()
35+
void testSerializationFailureWhenEnabledWithIncomparableKeys()
3836
throws Exception
3937
{
4038
// Given
@@ -44,11 +42,15 @@ void testSerializationWithIncomparableKeys()
4442

4543
// When : Throws exception
4644
// com.fasterxml.jackson.databind.JsonMappingException: class java.util.Currency cannot be cast to class java.lang.Comparable
47-
String jsonResult = objectMapper.writeValueAsString(entity);
48-
49-
// Then : Order should not matter, just plain old serialize
50-
assertTrue(jsonResult.contains("GBP"));
51-
assertTrue(jsonResult.contains("AUD"));
45+
try {
46+
objectMapper.writer()
47+
.with(SerializationFeature.FAIL_ON_ORDER_MAP_BY_INCOMPARABLE_KEY)
48+
.writeValueAsString(entity);
49+
fail("Should not pass");
50+
} catch (InvalidDefinitionException e) {
51+
// Then
52+
verifyException(e, "Cannot order Map entries by key of incomparable type");
53+
}
5254
}
5355

5456
@Test
@@ -75,4 +77,24 @@ void testSerializationWithGenericObjectKeys()
7577
"'5':'N_TEXT'}}"), jsonResult);
7678
}
7779

80+
@Test
81+
void testSerWithNullType()
82+
throws Exception
83+
{
84+
// Given : Mixed keys with incomparable `Currency` and comparable `Integer`
85+
ObjectContainer4773 entity = new ObjectContainer4773();
86+
entity.exampleMap.put(null, "AUD_TEXT");
87+
88+
// When : Throws exception
89+
try {
90+
objectMapper.writer()
91+
.with(SerializationFeature.FAIL_ON_ORDER_MAP_BY_INCOMPARABLE_KEY)
92+
.writeValueAsString(entity);
93+
fail("Should not pass");
94+
} catch (InvalidDefinitionException e) {
95+
// Then
96+
verifyException(e, "Cannot order Map entries by key of incomparable type [null]");
97+
}
98+
}
99+
78100
}

src/test/java/com/fasterxml/jackson/databind/ser/jdk/MapSerializationTest.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,8 @@ public void testOrderByKey() throws IOException
164164
@Test
165165
public void testOrderByWithNulls() throws IOException
166166
{
167-
ObjectWriter sortingW = MAPPER.writer(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS);
167+
ObjectWriter sortingW = MAPPER.writer(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS)
168+
.without(SerializationFeature.FAIL_ON_ORDER_MAP_BY_INCOMPARABLE_KEY);
168169
// 16-Oct-2016, tatu: but mind the null key, if any
169170
Map<String,Integer> mapWithNullKey = new LinkedHashMap<String,Integer>();
170171
mapWithNullKey.put(null, 1);

0 commit comments

Comments
 (0)