Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions release-notes/CREDITS
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,9 @@ Lee Jiwon (@dlwldnjs1009)
* Contributed #3064: `@JsonPropertyOrder(alphabetic=true)` is ignored in case indices
are defined for `@JsonProperty` -- add `MapperFeature.SORT_PROPERTIES_BY_INDEX`
[3.2.0]
* Implemented #3166: Ability to sort `Set`s before serialization (add
`SerializationFeature.ORDER_SET_ELEMENTS`)
[3.2.0]

Garret Wilson (@garretwilson)
* Suggested #4157: Add `MapperFeature.INFER_RECORD_GETTERS_FROM_COMPONENTS_ONLY` to ignore
Expand Down
4 changes: 4 additions & 0 deletions release-notes/VERSION
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,10 @@ Versions: 3.x (for earlier see VERSION-2.x)
#3083: `@JsonIncludeProperties` could be used like `@JsonPropertyOrder`
(suggested by Marcel O)
(fix by @cowtowncoder, w/ Claude code)
#3166: Ability to sort `Set`s before serialization (add
`SerializationFeature.ORDER_SET_ELEMENTS`)
(requested by @mjustin)
(implemented by @Lee Jiwon)
#3194: Deserialization of 2-dimensional arrays of final types fails when
using `DefaultTyping.NON_FINAL`
(reported by Klaas D)
Expand Down
33 changes: 33 additions & 0 deletions src/main/java/tools/jackson/databind/SerializationFeature.java
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,39 @@ public enum SerializationFeature implements ConfigFeature
*/
FAIL_ON_ORDER_MAP_BY_INCOMPARABLE_KEY(false),

/**
* Feature that determines whether entries of {@link java.util.Set}s
* are first sorted before serialization or not: if enabled, additional sorting step
* is performed if necessary (not necessary for
* {@link java.util.SortedSet}s or {@link java.util.EnumSet}s),
* if disabled, no additional sorting is needed.
*<p>
* Note that sorting requires set elements to implement {@link java.lang.Comparable};
* behavior when encountering non-Comparable elements is controlled by
* {@link #FAIL_ON_ORDER_SET_BY_INCOMPARABLE_ELEMENT}.
*<p>
* Feature is disabled by default.
*
* @since 3.2
*/
ORDER_SET_ELEMENTS(false),

/**
* Feature that determines whether to fail when attempting to sort
* {@link java.util.Set} entries with non-Comparable or mutually incomparable elements.
*<p>
* If enabled, will throw an exception when set elements cannot be sorted.
* If disabled, will silently skip sorting and use the original iteration order.
*<p>
* Note that this feature has only effect when set entry ordering is enabled via
* {@link #ORDER_SET_ELEMENTS}.
*<p>
* Feature is disabled by default.
*
* @since 3.2
*/
FAIL_ON_ORDER_SET_BY_INCOMPARABLE_ELEMENT(false),

/**
* Feature that determines whether {@code JsonInclude#content()} configured
* filtering is applied to elements of {@link java.util.Collection} and
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package tools.jackson.databind.ser.jdk;

import java.util.Collection;
import java.util.EnumSet;
import java.util.Iterator;
import java.util.*;

import tools.jackson.core.*;
import tools.jackson.databind.BeanProperty;
Expand All @@ -11,6 +9,7 @@
import tools.jackson.databind.SerializationContext;
import tools.jackson.databind.ValueSerializer;
import tools.jackson.databind.jsontype.TypeSerializer;
import tools.jackson.databind.util.ClassUtil;
import tools.jackson.databind.ser.impl.PropertySerializerMap;
import tools.jackson.databind.ser.std.AsArraySerializerBase;
import tools.jackson.databind.ser.std.StdContainerSerializer;
Expand Down Expand Up @@ -132,11 +131,21 @@ private void serializeContentsImpl(Collection<?> value, JsonGenerator g,
SerializationContext ctxt)
throws JacksonException
{
// [databind#3166]: sort Set elements if feature enabled
Collection<?> toSerialize = value;
if (value instanceof Set<?> set
&& ctxt.isEnabled(SerializationFeature.ORDER_SET_ELEMENTS)
&& !(set instanceof SortedSet<?>
|| set instanceof EnumSet<?>
|| set.isEmpty())) {
toSerialize = _orderElements(value, ctxt);
}

if (_elementSerializer != null) {
serializeContentsUsingImpl(value, g, ctxt, _elementSerializer);
serializeContentsUsingImpl(toSerialize, g, ctxt, _elementSerializer);
return;
}
Iterator<?> it = value.iterator();
Iterator<?> it = toSerialize.iterator();
if (!it.hasNext()) {
return;
}
Expand Down Expand Up @@ -185,6 +194,49 @@ private void serializeContentsImpl(Collection<?> value, JsonGenerator g,
}
}

/**
* Helper method to sort Set elements for deterministic serialization.
*
* @since 3.2
*/
@SuppressWarnings("unchecked")
protected Collection<?> _orderElements(Collection<?> input,
SerializationContext ctxt)
throws JacksonException
{
// [databind#3166] Quick pre-check: first non-null element must be Comparable
// (same pattern as MapSerializer._orderEntries; first element is a good enough sample)
for (Object elem : input) {
if (!(elem instanceof Comparable<?>) && (elem != null)) {
if (!ctxt.isEnabled(
SerializationFeature.FAIL_ON_ORDER_SET_BY_INCOMPARABLE_ELEMENT)) {
return input;
}
ctxt.reportBadDefinition(input.getClass(),
String.format(
"Cannot order `Set` entries: element of type %s is not `java.util.Comparable`,"
+" consider disabling `SerializationFeature.FAIL_ON_ORDER_SET_BY_INCOMPARABLE_ELEMENT`"
+" to simply skip sorting",
ClassUtil.classNameOf(elem)));
}
break;
}
try {
List<Object> sorted = new ArrayList<>(input);
sorted.sort((Comparator<Object>)(Comparator<?>)
Comparator.nullsLast(Comparator.naturalOrder()));
return sorted;
} catch (ClassCastException e) {
if (!ctxt.isEnabled(SerializationFeature.FAIL_ON_ORDER_SET_BY_INCOMPARABLE_ELEMENT)) {
return input;
}
return ctxt.reportBadDefinition(input.getClass(),
"Cannot order `Set` entries: elements are not mutually `java.util.Comparable`,"
+" consider disabling `SerializationFeature.FAIL_ON_ORDER_SET_BY_INCOMPARABLE_ELEMENT`"
+" to simply skip sorting");
}
}

private void serializeContentsUsingImpl(Collection<?> value, JsonGenerator g,
SerializationContext ctxt, ValueSerializer<Object> ser)
throws JacksonException
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,10 +138,19 @@ private final void serializeContentsImpl(Collection<String> value, JsonGenerator
SerializationContext ctxt, boolean filtered)
throws JacksonException
{
int i = 0;
// [databind#3166]: sort Set<String> if feature enabled
Collection<String> toSerialize = value;
if (value instanceof Set<?> && !(value instanceof SortedSet<?>)) {
if (ctxt.isEnabled(SerializationFeature.ORDER_SET_ELEMENTS)) {
List<String> sorted = new ArrayList<>(value);
sorted.sort(Comparator.nullsLast(Comparator.naturalOrder()));
toSerialize = sorted;
}
}

int i = 0;
try {
for (String str : value) {
for (String str : toSerialize) {
if (str == null) {
if (filtered && _suppressNulls) {
++i;
Expand Down
Loading
Loading