@ -21,8 +21,10 @@ import java.lang.reflect.Type;
@@ -21,8 +21,10 @@ import java.lang.reflect.Type;
import java.util.Arrays ;
import java.util.Collections ;
import java.util.HashMap ;
import java.util.LinkedHashMap ;
import java.util.List ;
import java.util.Map ;
import java.util.function.Consumer ;
import com.fasterxml.jackson.annotation.JsonView ;
import com.fasterxml.jackson.databind.JavaType ;
@ -40,6 +42,7 @@ import org.springframework.http.server.reactive.ServerHttpRequest;
@@ -40,6 +42,7 @@ import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse ;
import org.springframework.lang.Nullable ;
import org.springframework.util.Assert ;
import org.springframework.util.CollectionUtils ;
import org.springframework.util.MimeType ;
import org.springframework.util.ObjectUtils ;
@ -80,7 +83,10 @@ public abstract class Jackson2CodecSupport {
@@ -80,7 +83,10 @@ public abstract class Jackson2CodecSupport {
protected final Log logger = HttpLogging . forLogName ( getClass ( ) ) ;
private final ObjectMapper objectMapper ;
private final ObjectMapper defaultObjectMapper ;
@Nullable
private Map < Class < ? > , Map < MimeType , ObjectMapper > > objectMapperRegistrations ;
private final List < MimeType > mimeTypes ;
@ -90,14 +96,60 @@ public abstract class Jackson2CodecSupport {
@@ -90,14 +96,60 @@ public abstract class Jackson2CodecSupport {
* /
protected Jackson2CodecSupport ( ObjectMapper objectMapper , MimeType . . . mimeTypes ) {
Assert . notNull ( objectMapper , "ObjectMapper must not be null" ) ;
this . o bjectMapper = objectMapper ;
this . defaultO bjectMapper = objectMapper ;
this . mimeTypes = ! ObjectUtils . isEmpty ( mimeTypes ) ?
Collections . unmodifiableList ( Arrays . asList ( mimeTypes ) ) : DEFAULT_MIME_TYPES ;
}
public ObjectMapper getObjectMapper ( ) {
return this . objectMapper ;
return this . defaultObjectMapper ;
}
/ * *
* Configure the { @link ObjectMapper } instances to use for the given
* { @link Class } . This is useful when you want to deviate from the
* { @link # getObjectMapper ( ) default } ObjectMapper or have the
* { @code ObjectMapper } vary by { @code MediaType } .
* < p > < strong > Note : < / strong > Use of this method effectively turns off use of
* the default { @link # getObjectMapper ( ) ObjectMapper } and supported
* { @link # getMimeTypes ( ) MimeTypes } for the given class . Therefore it is
* important for the mappings configured here to
* { @link MediaType # includes ( MediaType ) include } every MediaType that must
* be supported for the given class .
* @param clazz the type of Object to register ObjectMapper instances for
* @param registrar a consumer to populate or otherwise update the
* MediaType - to - ObjectMapper associations for the given Class
* @since 5 . 3 . 4
* /
public void registerObjectMappersForType ( Class < ? > clazz , Consumer < Map < MimeType , ObjectMapper > > registrar ) {
if ( this . objectMapperRegistrations = = null ) {
this . objectMapperRegistrations = new LinkedHashMap < > ( ) ;
}
Map < MimeType , ObjectMapper > registrations =
this . objectMapperRegistrations . computeIfAbsent ( clazz , c - > new LinkedHashMap < > ( ) ) ;
registrar . accept ( registrations ) ;
}
/ * *
* Return ObjectMapper registrations for the given class , if any .
* @param clazz the class to look up for registrations for
* @return a map with registered MediaType - to - ObjectMapper registrations ,
* or empty if in case of no registrations for the given class .
* @since 5 . 3 . 4
* /
@Nullable
public Map < MimeType , ObjectMapper > getObjectMappersForType ( Class < ? > clazz ) {
for ( Map . Entry < Class < ? > , Map < MimeType , ObjectMapper > > entry : getObjectMapperRegistrations ( ) . entrySet ( ) ) {
if ( entry . getKey ( ) . isAssignableFrom ( clazz ) ) {
return entry . getValue ( ) ;
}
}
return Collections . emptyMap ( ) ;
}
private Map < Class < ? > , Map < MimeType , ObjectMapper > > getObjectMapperRegistrations ( ) {
return ( this . objectMapperRegistrations ! = null ? this . objectMapperRegistrations : Collections . emptyMap ( ) ) ;
}
/ * *
@ -140,7 +192,7 @@ public abstract class Jackson2CodecSupport {
@@ -140,7 +192,7 @@ public abstract class Jackson2CodecSupport {
}
protected JavaType getJavaType ( Type type , @Nullable Class < ? > contextClass ) {
return this . o bjectMapper. constructType ( GenericTypeResolver . resolveType ( type , contextClass ) ) ;
return this . defaultO bjectMapper. constructType ( GenericTypeResolver . resolveType ( type , contextClass ) ) ;
}
protected Map < String , Object > getHints ( ResolvableType resolvableType ) {
@ -173,4 +225,31 @@ public abstract class Jackson2CodecSupport {
@@ -173,4 +225,31 @@ public abstract class Jackson2CodecSupport {
@Nullable
protected abstract < A extends Annotation > A getAnnotation ( MethodParameter parameter , Class < A > annotType ) ;
/ * *
* Select an ObjectMapper to use , either the main ObjectMapper or another
* if the handling for the given Class has been customized through
* { @link # registerObjectMappersForType ( Class , Consumer ) } .
* @since 5 . 3 . 4
* /
@Nullable
protected ObjectMapper selectObjectMapper ( ResolvableType targetType , @Nullable MimeType targetMimeType ) {
if ( targetMimeType = = null | | CollectionUtils . isEmpty ( this . objectMapperRegistrations ) ) {
return this . defaultObjectMapper ;
}
Class < ? > targetClass = targetType . toClass ( ) ;
for ( Map . Entry < Class < ? > , Map < MimeType , ObjectMapper > > typeEntry : getObjectMapperRegistrations ( ) . entrySet ( ) ) {
if ( typeEntry . getKey ( ) . isAssignableFrom ( targetClass ) ) {
for ( Map . Entry < MimeType , ObjectMapper > objectMapperEntry : typeEntry . getValue ( ) . entrySet ( ) ) {
if ( objectMapperEntry . getKey ( ) . includes ( targetMimeType ) ) {
return objectMapperEntry . getValue ( ) ;
}
}
// No matching registrations
return null ;
}
}
// No registrations
return this . defaultObjectMapper ;
}
}