2929import org .springframework .kafka .support .converter .AbstractJavaTypeMapper ;
3030import org .springframework .kafka .support .converter .DefaultJackson2JavaTypeMapper ;
3131import org .springframework .kafka .support .converter .Jackson2JavaTypeMapper ;
32+ import org .springframework .kafka .support .converter .Jackson2JavaTypeMapper .TypePrecedence ;
33+ import org .springframework .lang .Nullable ;
3234import org .springframework .util .Assert ;
3335import org .springframework .util .ClassUtils ;
3436import org .springframework .util .StringUtils ;
@@ -81,6 +83,12 @@ public class JsonDeserializer<T> implements ExtendedDeserializer<T> {
8183 */
8284 public static final String TRUSTED_PACKAGES = "spring.json.trusted.packages" ;
8385
86+ /**
87+ * Kafka config property to add type mappings to the type mapper:
88+ * 'foo=com.Foo,bar=com.Bar'.
89+ */
90+ public static final String TYPE_MAPPINGS = JsonSerializer .TYPE_MAPPINGS ;
91+
8492 protected final ObjectMapper objectMapper ;
8593
8694 protected Class <T > targetType ;
@@ -91,29 +99,79 @@ public class JsonDeserializer<T> implements ExtendedDeserializer<T> {
9199
92100 private boolean typeMapperExplicitlySet = false ;
93101
102+ /**
103+ * Construct an instance with a default {@link ObjectMapper}.
104+ */
94105 public JsonDeserializer () {
95- this (( Class < T >) null );
106+ this (null , true );
96107 }
97108
98- protected JsonDeserializer (ObjectMapper objectMapper ) {
99- this (null , objectMapper );
109+ /**
110+ * Construct an instance with the provided {@link ObjectMapper}.
111+ * @param objectMapper a custom object mapper.
112+ */
113+ public JsonDeserializer (ObjectMapper objectMapper ) {
114+ this (null , objectMapper , true );
100115 }
101116
117+ /**
118+ * Construct an instance with the provided target type, and a default
119+ * {@link ObjectMapper}.
120+ * @param targetType the target type to use if no type info headers are present.
121+ */
102122 public JsonDeserializer (Class <T > targetType ) {
103- this (targetType , new ObjectMapper ());
123+ this (targetType , true );
124+ }
125+
126+ /**
127+ * Construct an instance with the provided target type, and
128+ * useHeadersIfPresent with a default {@link ObjectMapper}.
129+ * @param targetType the target type.
130+ * @param useHeadersIfPresent true to use headers if present and fall back to target
131+ * type if not.
132+ * @since 2.2
133+ */
134+ public JsonDeserializer (Class <T > targetType , boolean useHeadersIfPresent ) {
135+ this (targetType , new ObjectMapper (), useHeadersIfPresent );
104136 this .objectMapper .configure (MapperFeature .DEFAULT_VIEW_INCLUSION , false );
105137 this .objectMapper .configure (DeserializationFeature .FAIL_ON_UNKNOWN_PROPERTIES , false );
106138 }
107139
108- @ SuppressWarnings ("unchecked" )
140+ /**
141+ * Construct an instance with the provided target type, and {@link ObjectMapper}.
142+ * @param targetType the target type to use if no type info headers are present.
143+ * @param objectMapper the mapper. type if not.
144+ */
109145 public JsonDeserializer (Class <T > targetType , ObjectMapper objectMapper ) {
146+ this (targetType , objectMapper , true );
147+ }
148+
149+ /**
150+ * Construct an instance with the provided target type, {@link ObjectMapper} and
151+ * useHeadersIfPresent.
152+ * @param targetType the target type.
153+ * @param objectMapper the mapper.
154+ * @param useHeadersIfPresent true to use headers if present and fall back to target
155+ * type if not.
156+ * @since 2.2
157+ */
158+ @ SuppressWarnings ("unchecked" )
159+ public JsonDeserializer (@ Nullable Class <T > targetType , ObjectMapper objectMapper , boolean useHeadersIfPresent ) {
110160 Assert .notNull (objectMapper , "'objectMapper' must not be null." );
111161 this .objectMapper = objectMapper ;
112- if (targetType == null ) {
113- targetType = (Class <T >) ResolvableType .forClass (getClass ()).getSuperType ().resolveGeneric (0 );
114- }
115162 this .targetType = targetType ;
163+ if (this .targetType == null ) {
164+ this .targetType = (Class <T >) ResolvableType .forClass (getClass ()).getSuperType ().resolveGeneric (0 );
165+ }
166+ Assert .isTrue (this .targetType != null || useHeadersIfPresent ,
167+ "'targetType' cannot be null if 'useHeadersIfPresent' is false" );
168+
169+ if (this .targetType != null ) {
170+ this .reader = this .objectMapper .readerFor (this .targetType );
171+ }
172+
116173 addTargetPackageToTrusted ();
174+ this .typeMapper .setTypePrecedence (useHeadersIfPresent ? TypePrecedence .TYPE_ID : TypePrecedence .INFERRED );
117175 }
118176
119177 public Jackson2JavaTypeMapper getTypeMapper () {
@@ -185,6 +243,10 @@ else if (configs.get(VALUE_DEFAULT_TYPE) instanceof String) {
185243 throw new IllegalStateException (VALUE_DEFAULT_TYPE + " must be Class or String" );
186244 }
187245 }
246+
247+ if (this .targetType != null ) {
248+ this .reader = this .objectMapper .readerFor (this .targetType );
249+ }
188250 addTargetPackageToTrusted ();
189251 }
190252 catch (ClassNotFoundException | LinkageError e ) {
@@ -196,6 +258,11 @@ else if (configs.get(VALUE_DEFAULT_TYPE) instanceof String) {
196258 StringUtils .commaDelimitedListToStringArray ((String ) configs .get (TRUSTED_PACKAGES )));
197259 }
198260 }
261+ if (configs .containsKey (TYPE_MAPPINGS ) && !this .typeMapperExplicitlySet
262+ && this .typeMapper instanceof AbstractJavaTypeMapper ) {
263+ ((AbstractJavaTypeMapper ) this .typeMapper ).setIdClassMapping (
264+ JsonSerializer .createMappings ((String ) configs .get (JsonSerializer .TYPE_MAPPINGS )));
265+ }
199266 }
200267
201268 /**
@@ -218,30 +285,32 @@ public T deserialize(String topic, Headers headers, byte[] data) {
218285 if (data == null ) {
219286 return null ;
220287 }
221- JavaType javaType = this .typeMapper .toJavaType (headers );
222- if (javaType == null ) {
223- Assert .state (this .targetType != null , "No type information in headers and no default type provided" );
224- return deserialize (topic , data );
225- }
226- else {
227- try {
228- return this .objectMapper .readerFor (javaType ).readValue (data );
229- }
230- catch (IOException e ) {
231- throw new SerializationException ("Can't deserialize data [" + Arrays .toString (data ) +
232- "] from topic [" + topic + "]" , e );
288+ ObjectReader reader = null ;
289+ if (this .typeMapper .getTypePrecedence ().equals (TypePrecedence .TYPE_ID )) {
290+ JavaType javaType = this .typeMapper .toJavaType (headers );
291+ if (javaType != null ) {
292+ reader = this .objectMapper .readerFor (javaType );
233293 }
234294 }
295+ if (reader == null ) {
296+ reader = this .reader ;
297+ }
298+ Assert .state (reader != null , "No type information in headers and no default type provided" );
299+ try {
300+ return reader .readValue (data );
301+ }
302+ catch (IOException e ) {
303+ throw new SerializationException ("Can't deserialize data [" + Arrays .toString (data ) +
304+ "] from topic [" + topic + "]" , e );
305+ }
235306 }
236307
237308 @ Override
238309 public T deserialize (String topic , byte [] data ) {
239310 if (data == null ) {
240311 return null ;
241312 }
242- if (this .reader == null ) {
243- this .reader = this .objectMapper .readerFor (this .targetType );
244- }
313+ Assert .state (this .reader != null , "No headers available and no default type provided" );
245314 try {
246315 T result = null ;
247316 if (data != null ) {
@@ -259,4 +328,5 @@ public T deserialize(String topic, byte[] data) {
259328 public void close () {
260329 // No-op
261330 }
331+
262332}
0 commit comments