Add Gson converter immediately before default Jackson converter

Previously, when the preferred json mapper was set to Gson, the Gson
HTTP message converter was added before any other converters. This
changed the form of String responses that were already valid. When
Jackson is in use, a string converter is used as it appears earlier
in the list than the Jackson converter. When the mapper is switched
to Gson, the Gson converter is added first in the list of converters
and the Strong converter is no longer used. This results in the
String, that was already valid JSON, being converted again. This
changes its form as quotes are escaped, etc.

This commit updates HttpMessageConverters so that the Gson converter
is added to the list immediately before the default Jackson
converter. This is done by considering the Gson converter to be an
equivalent of the Jackson converter.

Fixes gh-27354
pull/27537/head
Andy Wilkinson 3 years ago
parent 2f54198e31
commit fe081b1742

@ -1,5 +1,5 @@
/* /*
* Copyright 2012-2020 the original author or authors. * Copyright 2012-2021 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -20,8 +20,10 @@ import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map;
import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter; import org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter;
@ -63,6 +65,15 @@ public class HttpMessageConverters implements Iterable<HttpMessageConverter<?>>
NON_REPLACING_CONVERTERS = Collections.unmodifiableList(nonReplacingConverters); NON_REPLACING_CONVERTERS = Collections.unmodifiableList(nonReplacingConverters);
} }
private static final Map<Class<?>, Class<?>> EQUIVALENT_CONVERTERS;
static {
Map<Class<?>, Class<?>> equivalentConverters = new HashMap<>();
putIfExists(equivalentConverters, "org.springframework.http.converter.json.MappingJackson2HttpMessageConverter",
"org.springframework.http.converter.json.GsonHttpMessageConverter");
EQUIVALENT_CONVERTERS = Collections.unmodifiableMap(equivalentConverters);
}
private final List<HttpMessageConverter<?>> converters; private final List<HttpMessageConverter<?>> converters;
/** /**
@ -132,7 +143,12 @@ public class HttpMessageConverters implements Iterable<HttpMessageConverter<?>>
return false; return false;
} }
} }
return ClassUtils.isAssignableValue(defaultConverter.getClass(), candidate); Class<?> converterClass = defaultConverter.getClass();
if (ClassUtils.isAssignableValue(converterClass, candidate)) {
return true;
}
Class<?> equivalentClass = EQUIVALENT_CONVERTERS.get(converterClass);
return equivalentClass != null && ClassUtils.isAssignableValue(equivalentClass, candidate);
} }
private void configurePartConverters(AllEncompassingFormHttpMessageConverter formConverter, private void configurePartConverters(AllEncompassingFormHttpMessageConverter formConverter,
@ -220,4 +236,13 @@ public class HttpMessageConverters implements Iterable<HttpMessageConverter<?>>
} }
} }
private static void putIfExists(Map<Class<?>, Class<?>> map, String keyClassName, String valueClassName) {
try {
map.put(Class.forName(keyClassName), Class.forName(valueClassName));
}
catch (ClassNotFoundException | NoClassDefFoundError ex) {
// Ignore
}
}
} }

@ -1,5 +1,5 @@
/* /*
* Copyright 2012-2019 the original author or authors. * Copyright 2012-2021 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -19,6 +19,7 @@ package org.springframework.boot.autoconfigure.http;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.stream.Collectors;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
@ -28,6 +29,7 @@ import org.springframework.http.converter.ResourceHttpMessageConverter;
import org.springframework.http.converter.ResourceRegionHttpMessageConverter; import org.springframework.http.converter.ResourceRegionHttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter; import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.http.converter.cbor.MappingJackson2CborHttpMessageConverter; import org.springframework.http.converter.cbor.MappingJackson2CborHttpMessageConverter;
import org.springframework.http.converter.json.GsonHttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.http.converter.smile.MappingJackson2SmileHttpMessageConverter; import org.springframework.http.converter.smile.MappingJackson2SmileHttpMessageConverter;
import org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter; import org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter;
@ -81,6 +83,16 @@ class HttpMessageConvertersTests {
assertThat(converters.getConverters().indexOf(converter1)).isNotEqualTo(0); assertThat(converters.getConverters().indexOf(converter1)).isNotEqualTo(0);
} }
@Test
void addBeforeExistingEquivalentConverter() {
GsonHttpMessageConverter converter1 = new GsonHttpMessageConverter();
HttpMessageConverters converters = new HttpMessageConverters(converter1);
List<Class<?>> converterClasses = converters.getConverters().stream().map(HttpMessageConverter::getClass)
.collect(Collectors.toList());
assertThat(converterClasses).containsSequence(GsonHttpMessageConverter.class,
MappingJackson2HttpMessageConverter.class);
}
@Test @Test
void addNewConverters() { void addNewConverters() {
HttpMessageConverter<?> converter1 = mock(HttpMessageConverter.class); HttpMessageConverter<?> converter1 = mock(HttpMessageConverter.class);

Loading…
Cancel
Save