Browse Source
The JsonConverterDelegate interface replaces usages of HttpMessageContentConverter to provides the flexibility to use either message converters or WebFlux codecs. HttpMessageContentConverter is deprecated, and replaced with a package private copy (DefaultJsonConverterDelegate) in the org.springframework.test.json package that is accessible through a static method on JsonConverterDelegate. See gh-35737pull/35768/head
19 changed files with 384 additions and 129 deletions
@ -0,0 +1,151 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2002-present the original author or authors. |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
|
||||||
|
package org.springframework.test.json; |
||||||
|
|
||||||
|
import java.io.IOException; |
||||||
|
import java.lang.reflect.Type; |
||||||
|
import java.nio.charset.StandardCharsets; |
||||||
|
import java.util.List; |
||||||
|
import java.util.stream.StreamSupport; |
||||||
|
|
||||||
|
import org.springframework.core.ResolvableType; |
||||||
|
import org.springframework.http.HttpHeaders; |
||||||
|
import org.springframework.http.HttpInputMessage; |
||||||
|
import org.springframework.http.MediaType; |
||||||
|
import org.springframework.http.converter.GenericHttpMessageConverter; |
||||||
|
import org.springframework.http.converter.HttpMessageConverter; |
||||||
|
import org.springframework.http.converter.HttpMessageNotReadableException; |
||||||
|
import org.springframework.http.converter.SmartHttpMessageConverter; |
||||||
|
import org.springframework.mock.http.MockHttpInputMessage; |
||||||
|
import org.springframework.mock.http.MockHttpOutputMessage; |
||||||
|
import org.springframework.util.Assert; |
||||||
|
import org.springframework.util.function.SingletonSupplier; |
||||||
|
|
||||||
|
/** |
||||||
|
* Default {@link JsonConverterDelegate} based on {@link HttpMessageConverter}s. |
||||||
|
* |
||||||
|
* @author Stephane Nicoll |
||||||
|
* @author Rossen Stoyanchev |
||||||
|
* @since 7.0 |
||||||
|
*/ |
||||||
|
final class DefaultJsonConverterDelegate implements JsonConverterDelegate { |
||||||
|
|
||||||
|
private static final MediaType JSON = MediaType.APPLICATION_JSON; |
||||||
|
|
||||||
|
private final List<HttpMessageConverter<?>> messageConverters; |
||||||
|
|
||||||
|
|
||||||
|
DefaultJsonConverterDelegate(Iterable<HttpMessageConverter<?>> messageConverters) { |
||||||
|
this.messageConverters = StreamSupport.stream(messageConverters.spliterator(), false).toList(); |
||||||
|
Assert.notEmpty(this.messageConverters, "At least one message converter needs to be specified"); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
@Override |
||||||
|
public <T> T read(String content, ResolvableType targetType) throws IOException{ |
||||||
|
HttpInputMessage message = new MockHttpInputMessage(content.getBytes(StandardCharsets.UTF_8)); |
||||||
|
message.getHeaders().add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE); |
||||||
|
return read(message, MediaType.APPLICATION_JSON, targetType); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Convert the given {@link HttpInputMessage} whose content must match the |
||||||
|
* given {@link MediaType} to the requested {@code targetType}. |
||||||
|
* @param message an input message |
||||||
|
* @param mediaType the media type of the input |
||||||
|
* @param targetType the target type |
||||||
|
* @param <T> the converted object type |
||||||
|
* @return a value of the given {@code targetType} |
||||||
|
*/ |
||||||
|
@SuppressWarnings("unchecked") |
||||||
|
<T> T read(HttpInputMessage message, MediaType mediaType, ResolvableType targetType) |
||||||
|
throws IOException, HttpMessageNotReadableException { |
||||||
|
|
||||||
|
Class<?> contextClass = targetType.getRawClass(); |
||||||
|
SingletonSupplier<Type> javaType = SingletonSupplier.of(targetType::getType); |
||||||
|
for (HttpMessageConverter<?> messageConverter : this.messageConverters) { |
||||||
|
if (messageConverter instanceof GenericHttpMessageConverter<?> genericMessageConverter) { |
||||||
|
Type type = javaType.obtain(); |
||||||
|
if (genericMessageConverter.canRead(type, contextClass, mediaType)) { |
||||||
|
return (T) genericMessageConverter.read(type, contextClass, message); |
||||||
|
} |
||||||
|
} |
||||||
|
else if (messageConverter instanceof SmartHttpMessageConverter<?> smartMessageConverter) { |
||||||
|
if (smartMessageConverter.canRead(targetType, mediaType)) { |
||||||
|
return (T) smartMessageConverter.read(targetType, message, null); |
||||||
|
} |
||||||
|
} |
||||||
|
else { |
||||||
|
Class<?> targetClass = (contextClass != null ? contextClass : Object.class); |
||||||
|
if (messageConverter.canRead(targetClass, mediaType)) { |
||||||
|
HttpMessageConverter<T> simpleMessageConverter = (HttpMessageConverter<T>) messageConverter; |
||||||
|
Class<? extends T> clazz = (Class<? extends T>) targetClass; |
||||||
|
return simpleMessageConverter.read(clazz, message); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
throw new IllegalStateException("No converter found to read [%s] to [%s]".formatted(mediaType, targetType)); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Convert the given raw value to the given {@code targetType} by writing |
||||||
|
* it first to JSON and reading it back. |
||||||
|
* @param value the value to convert |
||||||
|
* @param targetType the target type |
||||||
|
* @param <T> the converted object type |
||||||
|
* @return a value of the given {@code targetType} |
||||||
|
*/ |
||||||
|
@Override |
||||||
|
public <T> T map(Object value, ResolvableType targetType) throws IOException { |
||||||
|
MockHttpOutputMessage outputMessage = writeToJson(value, ResolvableType.forInstance(value)); |
||||||
|
return read(fromHttpOutputMessage(outputMessage), JSON, targetType); |
||||||
|
} |
||||||
|
|
||||||
|
@SuppressWarnings({ "rawtypes", "unchecked" }) |
||||||
|
private MockHttpOutputMessage writeToJson(Object value, ResolvableType valueType) throws IOException { |
||||||
|
MockHttpOutputMessage outputMessage = new MockHttpOutputMessage(); |
||||||
|
Class<?> valueClass = value.getClass(); |
||||||
|
SingletonSupplier<Type> javaType = SingletonSupplier.of(valueType::getType); |
||||||
|
for (HttpMessageConverter<?> messageConverter : this.messageConverters) { |
||||||
|
if (messageConverter instanceof GenericHttpMessageConverter genericMessageConverter) { |
||||||
|
Type type = javaType.obtain(); |
||||||
|
if (genericMessageConverter.canWrite(type, valueClass, JSON)) { |
||||||
|
genericMessageConverter.write(value, type, JSON, outputMessage); |
||||||
|
return outputMessage; |
||||||
|
} |
||||||
|
} |
||||||
|
else if (messageConverter instanceof SmartHttpMessageConverter smartMessageConverter) { |
||||||
|
if (smartMessageConverter.canWrite(valueType, valueClass, JSON)) { |
||||||
|
smartMessageConverter.write(value, valueType, JSON, outputMessage, null); |
||||||
|
return outputMessage; |
||||||
|
} |
||||||
|
} |
||||||
|
else if (messageConverter.canWrite(valueClass, JSON)) { |
||||||
|
((HttpMessageConverter<Object>) messageConverter).write(value, JSON, outputMessage); |
||||||
|
return outputMessage; |
||||||
|
} |
||||||
|
} |
||||||
|
throw new IllegalStateException("No converter found to convert [%s] to JSON".formatted(valueType)); |
||||||
|
} |
||||||
|
|
||||||
|
private static HttpInputMessage fromHttpOutputMessage(MockHttpOutputMessage message) { |
||||||
|
MockHttpInputMessage inputMessage = new MockHttpInputMessage(message.getBodyAsBytes()); |
||||||
|
inputMessage.getHeaders().addAll(message.getHeaders()); |
||||||
|
return inputMessage; |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -0,0 +1,61 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2002-present the original author or authors. |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
|
||||||
|
package org.springframework.test.json; |
||||||
|
|
||||||
|
import java.io.IOException; |
||||||
|
|
||||||
|
import org.springframework.core.ResolvableType; |
||||||
|
import org.springframework.http.converter.HttpMessageConverter; |
||||||
|
|
||||||
|
/** |
||||||
|
* Delegate to abstract JSON type conversion in AssertJ support clases. |
||||||
|
* |
||||||
|
* @author Rossen Stoyanchev |
||||||
|
* @since 7.0 |
||||||
|
*/ |
||||||
|
public interface JsonConverterDelegate { |
||||||
|
|
||||||
|
/** |
||||||
|
* Convert JSON content to the given {@code targetType}. |
||||||
|
* @param content the JSON content |
||||||
|
* @param targetType the target type |
||||||
|
* @return the decoded object |
||||||
|
* @param <T> the target type |
||||||
|
*/ |
||||||
|
<T> T read(String content, ResolvableType targetType) throws IOException; |
||||||
|
|
||||||
|
/** |
||||||
|
* Map the given Object value to the given {@code targetType}, via |
||||||
|
* serialization and deserialization to and from JSON. This is useful for |
||||||
|
* mapping generic maps and lists to higher level Objects. |
||||||
|
* @param value the value to map |
||||||
|
* @param targetType the target tyep |
||||||
|
* @return the decoded object |
||||||
|
* @param <T> the target type |
||||||
|
*/ |
||||||
|
<T> T map(Object value, ResolvableType targetType) throws IOException; |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* Create a {@link JsonConverterDelegate} from message converters. |
||||||
|
* @param candidates the candidates |
||||||
|
*/ |
||||||
|
static JsonConverterDelegate of(Iterable<HttpMessageConverter<?>> candidates) { |
||||||
|
return new DefaultJsonConverterDelegate(candidates); |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
Loading…
Reference in new issue