55import com .fasterxml .jackson .databind .DeserializationContext ;
66import com .fasterxml .jackson .databind .JsonDeserializer ;
77import com .fasterxml .jackson .databind .JsonMappingException ;
8+ import com .google .common .annotations .Beta ;
89import java .io .IOException ;
910import java .util .ArrayList ;
1011import java .util .List ;
1819 * explicitly. If deserialization fails for all candidates, a {@link JsonMappingException} is thrown
1920 * with suppressed exceptions.
2021 *
22+ * @since 1.2.0
2123 * @param <T> The base type for deserialization.
2224 */
25+ @ Beta
2326public class PolymorphicFallbackDeserializer <T > extends JsonDeserializer <T > {
2427
2528 @ Nonnull private final List <Class <? extends T >> candidates ;
26- Class <T > baseClass ;
29+ @ Nonnull private final Class <T > baseClass ;
30+
31+ private PolymorphicFallbackDeserializer (
32+ @ Nonnull final Class <T > baseClass , @ Nonnull final List <Class <? extends T >> candidates ) {
33+ this .baseClass = baseClass ;
34+ this .candidates = candidates ;
35+ }
2736
2837 /**
29- * Constructs the deserializer using the {@link JsonSubTypes} annotation.
38+ * Constructs the deserializer using candidates inferred from the {@link JsonSubTypes} annotation.
3039 *
3140 * @param baseClass The base class or interface to be resolved.
3241 * @throws IllegalStateException If no subtypes are found.
3342 */
34- protected PolymorphicFallbackDeserializer ( @ Nonnull final Class < T > baseClass ) {
35- this . baseClass = baseClass ;
36-
43+ @ Nonnull
44+ protected static < T > PolymorphicFallbackDeserializer < T > fromJsonSubTypes (
45+ @ Nonnull final Class < T > baseClass ) {
3746 final var subTypes = baseClass .getAnnotation (JsonSubTypes .class );
3847 if (subTypes == null || subTypes .value ().length == 0 ) {
3948 throw new IllegalStateException ("No subtypes found for " + baseClass .getName ());
4049 }
4150
42- candidates = new ArrayList <>();
51+ final var candidates = new ArrayList <Class <? extends T > >();
4352 for (final var subType : subTypes .value ()) {
4453 candidates .add ((Class <? extends T >) subType .value ());
4554 }
55+
56+ return new PolymorphicFallbackDeserializer <>(baseClass , candidates );
4657 }
4758
4859 /**
49- * Constructs the deserializer with an explicit list of candidate types.
60+ * Constructs the deserializer with an explicit given list of candidate types.
5061 *
5162 * @param baseClass The base class or interface to be resolved.
5263 * @param candidates A list of candidate classes to try deserialization.
5364 */
54- protected PolymorphicFallbackDeserializer (
65+ @ Nonnull
66+ protected static <T > PolymorphicFallbackDeserializer <T > fromCandidates (
5567 @ Nonnull final Class <T > baseClass , @ Nonnull final List <Class <? extends T >> candidates ) {
56- this .baseClass = baseClass ;
57- this .candidates = candidates ;
68+ return new PolymorphicFallbackDeserializer <>(baseClass , candidates );
5869 }
5970
6071 /**
@@ -74,19 +85,25 @@ public T deserialize(
7485 throws IOException {
7586
7687 final var root = jsonParser .readValueAsTree ();
77- final var throwable =
78- JsonMappingException .from (
79- jsonParser ,
80- "PolymorphicFallbackDeserializer failed to deserialize " + this .baseClass .getName ());
88+ final var suppressed = new ArrayList <JsonMappingException >();
8189
8290 for (final var candidate : candidates ) {
8391 try {
8492 return jsonParser .getCodec ().treeToValue (root , candidate );
8593 } catch (JsonMappingException e ) {
86- throwable . addSuppressed (e );
94+ suppressed . add (e );
8795 }
8896 }
8997
90- throw throwable ;
98+ final var aggregateException =
99+ JsonMappingException .from (
100+ jsonParser ,
101+ "PolymorphicFallbackDeserializer failed to deserialize "
102+ + this .baseClass .getName ()
103+ + ". Attempted candidates: "
104+ + candidates .stream ().map (Class ::getName ).toList ());
105+
106+ suppressed .forEach (aggregateException ::addSuppressed );
107+ throw aggregateException ;
91108 }
92109}
0 commit comments