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 @@
@@ -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 @@
@@ -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