CollectionSerializationOrderTest.java
package tools.jackson.databind.ser.jdk;
import java.util.*;
import org.junit.jupiter.api.Test;
import tools.jackson.core.JsonGenerator;
import tools.jackson.databind.*;
import tools.jackson.databind.annotation.JsonSerialize;
import tools.jackson.databind.testutil.DatabindTestUtil;
import tools.jackson.databind.testutil.NoCheckSubTypeValidator;
import static org.junit.jupiter.api.Assertions.*;
/**
* Tests for {@link SerializationFeature#ORDER_SET_ELEMENTS}
* and {@link SerializationFeature#FAIL_ON_ORDER_SET_BY_INCOMPARABLE_ELEMENT}.
*/
public class CollectionSerializationOrderTest extends DatabindTestUtil
{
// Helper bean wrapping Set<String> ��� routes through StringCollectionSerializer
static class StringSetBean {
public Set<String> values;
StringSetBean(Set<String> v) { values = v; }
}
// Non-Comparable type for testing incomparable scenarios
static class NonComparable {
public final String name;
NonComparable(String n) { name = n; }
}
// Custom String serializer that wraps output in angle brackets ��� used to verify
// StaticListSerializerBase.createContextual() fallback to CollectionSerializer
static class WrappingStringSerializer extends ValueSerializer<String> {
@Override
public void serialize(String value, JsonGenerator g, SerializationContext ctxt) {
g.writeString("<" + value + ">");
}
}
// Bean with custom content serializer to trigger fallback path
static class CustomSerStringSetBean {
@JsonSerialize(contentUsing = WrappingStringSerializer.class)
public Set<String> values;
CustomSerStringSetBean(Set<String> v) { values = v; }
}
private ObjectMapper orderedMapper() {
return jsonMapperBuilder()
.enable(SerializationFeature.ORDER_SET_ELEMENTS)
.build();
}
/*
/**********************************************************************
/* Test methods: basic ordering
/**********************************************************************
*/
// #1: Feature disabled (default) ��� no sorting applied
@Test
public void testFeatureDisabledNoSorting() throws Exception {
ObjectMapper mapper = newJsonMapper(); // default: feature disabled
// LinkedHashSet preserves insertion order: c, a, b
Set<String> set = new LinkedHashSet<>(Arrays.asList("c", "a", "b"));
assertEquals("[\"c\",\"a\",\"b\"]", mapper.writeValueAsString(set));
}
// #2: HashSet<Integer> sorted numerically (CollectionSerializer path)
@Test
public void testOrderedHashSetWithIntegers() throws Exception {
ObjectMapper mapper = orderedMapper();
Set<Integer> set = new LinkedHashSet<>(Arrays.asList(3, 1, 2));
assertEquals("[1,2,3]", mapper.writeValueAsString(set));
}
// #3: Set<String> bean property ��� verifies StringCollectionSerializer path
@Test
public void testSetStringBeanProperty() throws Exception {
ObjectMapper mapper = orderedMapper();
Set<String> set = new LinkedHashSet<>(Arrays.asList("z", "a", "m"));
assertEquals(a2q("{'values':['a','m','z']}"),
mapper.writeValueAsString(new StringSetBean(set)));
}
// #4: SortedSet is already sorted ��� no additional sorting needed
@Test
public void testSortedSetUnchanged() throws Exception {
ObjectMapper mapper = orderedMapper();
Set<String> set = new TreeSet<>(Arrays.asList("c", "a", "b"));
assertEquals("[\"a\",\"b\",\"c\"]", mapper.writeValueAsString(set));
}
// #4b: SortedSet with custom comparator ��� preserve comparator order
@Test
public void testSortedSetWithCustomComparatorPreserved() throws Exception {
ObjectMapper mapper = orderedMapper();
Set<String> set = new TreeSet<>(Comparator.reverseOrder());
set.addAll(Arrays.asList("a", "b", "c"));
// Even with ordering enabled, SortedSet keeps its own comparator order
assertEquals("[\"c\",\"b\",\"a\"]", mapper.writeValueAsString(set));
}
// #5: EnumSet preserves ordinal order ��� not re-sorted
@Test
public void testEnumSetOrderUnchanged() throws Exception {
ObjectMapper mapper = orderedMapper();
// EnumSet iteration is always by ordinal, verify it stays that way
Set<Thread.State> set = EnumSet.of(
Thread.State.TERMINATED, Thread.State.NEW, Thread.State.RUNNABLE);
String json = mapper.writeValueAsString(set);
// NEW(0), RUNNABLE(1), TERMINATED(5) ��� ordinal order
assertEquals("[\"NEW\",\"RUNNABLE\",\"TERMINATED\"]", json);
}
// #6: empty set ��� no issues
@Test
public void testEmptySet() throws Exception {
ObjectMapper mapper = orderedMapper();
assertEquals("[]", mapper.writeValueAsString(new HashSet<>()));
}
/*
/**********************************************************************
/* Test methods: incomparable elements
/**********************************************************************
*/
// #7: non-Comparable elements + FAIL enabled ��� exception
@Test
public void testNonComparableElementsFail() throws Exception {
ObjectMapper mapper = jsonMapperBuilder()
.enable(SerializationFeature.ORDER_SET_ELEMENTS)
.enable(SerializationFeature.FAIL_ON_ORDER_SET_BY_INCOMPARABLE_ELEMENT)
.build();
Set<Object> set = new LinkedHashSet<>();
set.add(new NonComparable("x"));
set.add(new NonComparable("y"));
assertThrows(DatabindException.class,
() -> mapper.writeValueAsString(set));
}
// #8: non-Comparable elements + FAIL disabled ��� skip sorting, original order
@Test
public void testNonComparableElementsSkip() throws Exception {
ObjectMapper mapper = jsonMapperBuilder()
.enable(SerializationFeature.ORDER_SET_ELEMENTS)
.disable(SerializationFeature.FAIL_ON_ORDER_SET_BY_INCOMPARABLE_ELEMENT)
.build();
Set<Object> set = new LinkedHashSet<>();
set.add(new NonComparable("x"));
set.add(new NonComparable("y"));
// Sorting skipped; LinkedHashSet insertion order preserved
assertEquals(a2q("[{'name':'x'},{'name':'y'}]"),
mapper.writeValueAsString(set));
}
// #9: null elements in Set ��� nullsLast sorting (CollectionSerializer path ��� root level)
@Test
public void testSetWithNullElements() throws Exception {
ObjectMapper mapper = orderedMapper();
Set<String> set = new LinkedHashSet<>(Arrays.asList(null, "b", "a"));
// nulls sorted to end
assertEquals("[\"a\",\"b\",null]", mapper.writeValueAsString(set));
}
// #10: DayOfWeek (original issue use case)
@Test
public void testDayOfWeekSet() throws Exception {
ObjectMapper mapper = orderedMapper();
Set<java.time.DayOfWeek> set = new LinkedHashSet<>(Arrays.asList(
java.time.DayOfWeek.FRIDAY,
java.time.DayOfWeek.MONDAY,
java.time.DayOfWeek.WEDNESDAY));
String json = mapper.writeValueAsString(set);
// DayOfWeek implements Comparable; natural order is MONDAY < WEDNESDAY < FRIDAY
assertEquals("[\"MONDAY\",\"WEDNESDAY\",\"FRIDAY\"]", json);
}
/*
/**********************************************************************
/* Test methods: non-Set collections not affected
/**********************************************************************
*/
// #11: List is NOT affected (Set-specific feature)
@Test
public void testListNotAffected() throws Exception {
ObjectMapper mapper = orderedMapper();
List<String> list = Arrays.asList("c", "a", "b");
// List order preserved, not sorted
assertEquals("[\"c\",\"a\",\"b\"]", mapper.writeValueAsString(list));
}
// #12: LinkedHashSet (insertion order) ��� sorting overrides insertion order
@Test
public void testLinkedHashSetSorted() throws Exception {
ObjectMapper mapper = orderedMapper();
Set<String> set = new LinkedHashSet<>(Arrays.asList("c", "a", "b"));
assertEquals("[\"a\",\"b\",\"c\"]", mapper.writeValueAsString(set));
}
/*
/**********************************************************************
/* Test methods: polymorphic and edge cases
/**********************************************************************
*/
// #13: @JsonTypeInfo + Set ��� serializeWithType() path also sorts
@Test
public void testPolymorphicSetSorted() throws Exception {
ObjectMapper mapper = jsonMapperBuilder()
.enable(SerializationFeature.ORDER_SET_ELEMENTS)
.activateDefaultTyping(NoCheckSubTypeValidator.instance,
DefaultTyping.NON_FINAL)
.build();
Set<String> set = new LinkedHashSet<>(Arrays.asList("c", "a", "b"));
String json = mapper.writeValueAsString(set);
// With default typing, output includes type info but elements should be sorted
assertTrue(json.contains("\"a\""));
// Verify order: a before b before c
int posA = json.indexOf("\"a\"");
int posB = json.indexOf("\"b\"");
int posC = json.indexOf("\"c\"");
assertTrue(posA < posB && posB < posC,
"Elements should be sorted: " + json);
}
// #14: mixed incomparable types (Set<Object> with String + Integer)
@Test
public void testMixedIncomparableTypes() throws Exception {
ObjectMapper mapper = jsonMapperBuilder()
.enable(SerializationFeature.ORDER_SET_ELEMENTS)
.disable(SerializationFeature.FAIL_ON_ORDER_SET_BY_INCOMPARABLE_ELEMENT)
.build();
Set<Object> set = new LinkedHashSet<>();
set.add("hello");
set.add(42);
// ClassCastException caught, sorting skipped, insertion order preserved
assertEquals("[\"hello\",42]", mapper.writeValueAsString(set));
}
// #15: null + multiple non-Comparable ��� ClassCastException caught, skip
@Test
public void testNullAndNonComparableMixed() throws Exception {
ObjectMapper mapper = jsonMapperBuilder()
.enable(SerializationFeature.ORDER_SET_ELEMENTS)
.disable(SerializationFeature.FAIL_ON_ORDER_SET_BY_INCOMPARABLE_ELEMENT)
.build();
Set<Object> set = new LinkedHashSet<>();
set.add(null);
set.add(new NonComparable("x"));
set.add(new NonComparable("y"));
// ClassCastException caught, sorting skipped, insertion order preserved
assertEquals(a2q("[null,{'name':'x'},{'name':'y'}]"),
mapper.writeValueAsString(set));
}
// #16: Set<String> bean property with null ��� StringCollectionSerializer path nullsLast
@Test
public void testSetStringBeanPropertyWithNull() throws Exception {
ObjectMapper mapper = orderedMapper();
Set<String> set = new LinkedHashSet<>(Arrays.asList(null, "b", "a"));
// Bean property routes through StringCollectionSerializer; nulls sorted to end
assertEquals(a2q("{'values':['a','b',null]}"),
mapper.writeValueAsString(new StringSetBean(set)));
}
// #17: Set<String> + custom serializer ��� fallback to CollectionSerializer, still sorted
@Test
public void testSetStringWithCustomSerializerFallback() throws Exception {
ObjectMapper mapper = orderedMapper();
Set<String> set = new LinkedHashSet<>(Arrays.asList("c", "a", "b"));
CustomSerStringSetBean bean = new CustomSerStringSetBean(set);
String json = mapper.writeValueAsString(bean);
// Custom serializer wraps in <>, AND elements are sorted
assertEquals(a2q("{'values':['<a>','<b>','<c>']}"), json);
}
}