Introduce Jackson 3 support for converters
This commit introduces Jackson 3 SmartHttpMessageConverter based variants of the following Jackson 2 classes (and related dependent classes). org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter -> org.springframework.http.converter.AbstractJacksonHttpMessageConverter MappingJackson2HttpMessageConverter -> JacksonJsonHttpMessageConverter MappingJackson2SmileHttpMessageConverter -> JacksonSmileHttpMessageConverter MappingJackson2CborHttpMessageConverter -> JacksonCborHttpMessageConverter MappingJackson2XmlHttpMessageConverter -> JacksonXmlHttpMessageConverter MappingJackson2YamlHttpMessageConverter -> JacksonYamlHttpMessageConverter They use hints instead of MappingJacksonValue and MappingJacksonInputMessage to support `@JsonView` and FilterProvider. Jackson 3 support is configured if found in the classpath otherwise fallback to Jackson 2. JacksonHandlerInstantiator needs to be enabled explicitly if needed. See gh-33798
This commit is contained in:
@@ -0,0 +1,157 @@
|
||||
/*
|
||||
* Copyright 2002-2025 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.http.converter.cbor;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import tools.jackson.dataformat.cbor.CBORMapper;
|
||||
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.web.testfixture.http.MockHttpInputMessage;
|
||||
import org.springframework.web.testfixture.http.MockHttpOutputMessage;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.within;
|
||||
|
||||
/**
|
||||
* Jackson 3.x CBOR converter tests.
|
||||
*
|
||||
* @author Sebastien Deleuze
|
||||
*/
|
||||
class JacksonCborHttpMessageConverterTests {
|
||||
|
||||
private final JacksonCborHttpMessageConverter converter = new JacksonCborHttpMessageConverter();
|
||||
private final CBORMapper mapper = CBORMapper.builder().build();
|
||||
|
||||
|
||||
@Test
|
||||
void canRead() {
|
||||
assertThat(converter.canRead(MyBean.class, MediaType.APPLICATION_CBOR)).isTrue();
|
||||
assertThat(converter.canRead(MyBean.class, new MediaType("application", "json"))).isFalse();
|
||||
assertThat(converter.canRead(MyBean.class, new MediaType("application", "xml"))).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void canWrite() {
|
||||
assertThat(converter.canWrite(MyBean.class, MediaType.APPLICATION_CBOR)).isTrue();
|
||||
assertThat(converter.canWrite(MyBean.class, new MediaType("application", "json"))).isFalse();
|
||||
assertThat(converter.canWrite(MyBean.class, new MediaType("application", "xml"))).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void read() throws IOException {
|
||||
MyBean body = new MyBean();
|
||||
body.setString("Foo");
|
||||
body.setNumber(42);
|
||||
body.setFraction(42F);
|
||||
body.setArray(new String[]{"Foo", "Bar"});
|
||||
body.setBool(true);
|
||||
body.setBytes(new byte[]{0x1, 0x2});
|
||||
MockHttpInputMessage inputMessage = new MockHttpInputMessage(mapper.writeValueAsBytes(body));
|
||||
inputMessage.getHeaders().setContentType(MediaType.APPLICATION_CBOR);
|
||||
MyBean result = (MyBean) converter.read(MyBean.class, inputMessage);
|
||||
assertThat(result.getString()).isEqualTo("Foo");
|
||||
assertThat(result.getNumber()).isEqualTo(42);
|
||||
assertThat(result.getFraction()).isCloseTo(42F, within(0F));
|
||||
|
||||
assertThat(result.getArray()).isEqualTo(new String[]{"Foo", "Bar"});
|
||||
assertThat(result.isBool()).isTrue();
|
||||
assertThat(result.getBytes()).isEqualTo(new byte[]{0x1, 0x2});
|
||||
}
|
||||
|
||||
@Test
|
||||
void write() throws IOException {
|
||||
MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
|
||||
MyBean body = new MyBean();
|
||||
body.setString("Foo");
|
||||
body.setNumber(42);
|
||||
body.setFraction(42F);
|
||||
body.setArray(new String[]{"Foo", "Bar"});
|
||||
body.setBool(true);
|
||||
body.setBytes(new byte[]{0x1, 0x2});
|
||||
converter.write(body, null, outputMessage);
|
||||
assertThat(outputMessage.getBodyAsBytes()).isEqualTo(mapper.writeValueAsBytes(body));
|
||||
assertThat(outputMessage.getHeaders().getContentType())
|
||||
.as("Invalid content-type").isEqualTo(MediaType.APPLICATION_CBOR);
|
||||
}
|
||||
|
||||
|
||||
public static class MyBean {
|
||||
|
||||
private String string;
|
||||
|
||||
private int number;
|
||||
|
||||
private float fraction;
|
||||
|
||||
private String[] array;
|
||||
|
||||
private boolean bool;
|
||||
|
||||
private byte[] bytes;
|
||||
|
||||
public byte[] getBytes() {
|
||||
return bytes;
|
||||
}
|
||||
|
||||
public void setBytes(byte[] bytes) {
|
||||
this.bytes = bytes;
|
||||
}
|
||||
|
||||
public boolean isBool() {
|
||||
return bool;
|
||||
}
|
||||
|
||||
public void setBool(boolean bool) {
|
||||
this.bool = bool;
|
||||
}
|
||||
|
||||
public String getString() {
|
||||
return string;
|
||||
}
|
||||
|
||||
public void setString(String string) {
|
||||
this.string = string;
|
||||
}
|
||||
|
||||
public int getNumber() {
|
||||
return number;
|
||||
}
|
||||
|
||||
public void setNumber(int number) {
|
||||
this.number = number;
|
||||
}
|
||||
|
||||
public float getFraction() {
|
||||
return fraction;
|
||||
}
|
||||
|
||||
public void setFraction(float fraction) {
|
||||
this.fraction = fraction;
|
||||
}
|
||||
|
||||
public String[] getArray() {
|
||||
return array;
|
||||
}
|
||||
|
||||
public void setArray(String[] array) {
|
||||
this.array = array;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,843 @@
|
||||
/*
|
||||
* Copyright 2002-2025 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.http.converter.json;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Type;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonFilter;
|
||||
import com.fasterxml.jackson.annotation.JsonSubTypes;
|
||||
import com.fasterxml.jackson.annotation.JsonTypeInfo;
|
||||
import com.fasterxml.jackson.annotation.JsonView;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.skyscreamer.jsonassert.JSONAssert;
|
||||
import tools.jackson.databind.JavaType;
|
||||
import tools.jackson.databind.ObjectMapper;
|
||||
import tools.jackson.databind.ObjectReader;
|
||||
import tools.jackson.databind.ObjectWriter;
|
||||
import tools.jackson.databind.SerializationFeature;
|
||||
import tools.jackson.databind.cfg.EnumFeature;
|
||||
import tools.jackson.databind.json.JsonMapper;
|
||||
import tools.jackson.databind.ser.FilterProvider;
|
||||
import tools.jackson.databind.ser.std.SimpleBeanPropertyFilter;
|
||||
import tools.jackson.databind.ser.std.SimpleFilterProvider;
|
||||
|
||||
import org.springframework.core.ParameterizedTypeReference;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.converter.HttpMessageNotReadableException;
|
||||
import org.springframework.web.testfixture.http.MockHttpInputMessage;
|
||||
import org.springframework.web.testfixture.http.MockHttpOutputMessage;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
import static org.assertj.core.api.Assertions.entry;
|
||||
import static org.assertj.core.api.Assertions.within;
|
||||
|
||||
/**
|
||||
* Jackson 3.x converter tests.
|
||||
*
|
||||
* @author Sebastien Deleuze
|
||||
*/
|
||||
class JacksonJsonHttpMessageConverterTests {
|
||||
|
||||
private JacksonJsonHttpMessageConverter converter = new JacksonJsonHttpMessageConverter();
|
||||
|
||||
|
||||
@Test
|
||||
void canRead() {
|
||||
assertThat(converter.canRead(MyBean.class, new MediaType("application", "json"))).isTrue();
|
||||
assertThat(converter.canRead(Map.class, new MediaType("application", "json"))).isTrue();
|
||||
assertThat(converter.canRead(MyBean.class, new MediaType("application", "json", StandardCharsets.UTF_8))).isTrue();
|
||||
assertThat(converter.canRead(MyBean.class, new MediaType("application", "json", StandardCharsets.US_ASCII))).isTrue();
|
||||
assertThat(converter.canRead(MyBean.class, new MediaType("application", "json", StandardCharsets.ISO_8859_1))).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void canReadWithObjectMapperRegistrationForType() {
|
||||
MediaType halJsonMediaType = MediaType.parseMediaType("application/hal+json");
|
||||
MediaType halFormsJsonMediaType = MediaType.parseMediaType("application/prs.hal-forms+json");
|
||||
|
||||
assertThat(converter.canRead(MyBean.class, halJsonMediaType)).isTrue();
|
||||
assertThat(converter.canRead(MyBean.class, MediaType.APPLICATION_JSON)).isTrue();
|
||||
assertThat(converter.canRead(MyBean.class, halFormsJsonMediaType)).isTrue();
|
||||
assertThat(converter.canRead(Map.class, MediaType.APPLICATION_JSON)).isTrue();
|
||||
|
||||
converter.registerObjectMappersForType(MyBean.class, map -> {
|
||||
map.put(halJsonMediaType, new ObjectMapper());
|
||||
map.put(MediaType.APPLICATION_JSON, new ObjectMapper());
|
||||
});
|
||||
|
||||
assertThat(converter.canRead(MyBean.class, halJsonMediaType)).isTrue();
|
||||
assertThat(converter.canRead(MyBean.class, MediaType.APPLICATION_JSON)).isTrue();
|
||||
assertThat(converter.canRead(Map.class, MediaType.APPLICATION_JSON)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void canWrite() {
|
||||
assertThat(converter.canWrite(MyBean.class, MediaType.APPLICATION_JSON)).isTrue();
|
||||
assertThat(converter.canWrite(Map.class, MediaType.APPLICATION_JSON)).isTrue();
|
||||
assertThat(converter.canWrite(MyBean.class, new MediaType("application", "json", StandardCharsets.UTF_8))).isTrue();
|
||||
assertThat(converter.canWrite(MyBean.class, new MediaType("application", "json", StandardCharsets.US_ASCII))).isTrue();
|
||||
assertThat(converter.canWrite(MyBean.class, new MediaType("application", "json", StandardCharsets.ISO_8859_1))).isFalse();
|
||||
assertThatThrownBy(() -> converter.canWrite(MappingJacksonValue.class, MediaType.APPLICATION_JSON)).isInstanceOf(UnsupportedOperationException.class);
|
||||
}
|
||||
|
||||
@Test // SPR-7905
|
||||
void canReadAndWriteMicroformats() {
|
||||
assertThat(converter.canRead(MyBean.class, new MediaType("application", "vnd.test-micro-type+json"))).isTrue();
|
||||
assertThat(converter.canWrite(MyBean.class, new MediaType("application", "vnd.test-micro-type+json"))).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void getSupportedMediaTypes() {
|
||||
MediaType[] defaultMediaTypes = {MediaType.APPLICATION_JSON, MediaType.parseMediaType("application/*+json")};
|
||||
assertThat(converter.getSupportedMediaTypes()).containsExactly(defaultMediaTypes);
|
||||
assertThat(converter.getSupportedMediaTypes(MyBean.class)).containsExactly(defaultMediaTypes);
|
||||
|
||||
MediaType halJson = MediaType.parseMediaType("application/hal+json");
|
||||
converter.registerObjectMappersForType(MyBean.class, map -> {
|
||||
map.put(halJson, new ObjectMapper());
|
||||
map.put(MediaType.APPLICATION_JSON, new ObjectMapper());
|
||||
});
|
||||
|
||||
assertThat(converter.getSupportedMediaTypes(MyBean.class)).containsExactly(halJson, MediaType.APPLICATION_JSON);
|
||||
assertThat(converter.getSupportedMediaTypes(Map.class)).containsExactly(defaultMediaTypes);
|
||||
}
|
||||
|
||||
@Test
|
||||
void readTyped() throws IOException {
|
||||
String body = "{" +
|
||||
"\"bytes\":\"AQI=\"," +
|
||||
"\"array\":[\"Foo\",\"Bar\"]," +
|
||||
"\"number\":42," +
|
||||
"\"string\":\"Foo\"," +
|
||||
"\"bool\":true," +
|
||||
"\"fraction\":42.0}";
|
||||
MockHttpInputMessage inputMessage = new MockHttpInputMessage(body.getBytes(StandardCharsets.UTF_8));
|
||||
inputMessage.getHeaders().setContentType(new MediaType("application", "json"));
|
||||
MyBean result = (MyBean) converter.read(MyBean.class, inputMessage);
|
||||
assertThat(result.getString()).isEqualTo("Foo");
|
||||
assertThat(result.getNumber()).isEqualTo(42);
|
||||
assertThat(result.getFraction()).isCloseTo(42F, within(0F));
|
||||
assertThat(result.getArray()).isEqualTo(new String[] {"Foo", "Bar"});
|
||||
assertThat(result.isBool()).isTrue();
|
||||
assertThat(result.getBytes()).isEqualTo(new byte[] {0x1, 0x2});
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
void readUntyped() throws IOException {
|
||||
String body = "{" +
|
||||
"\"bytes\":\"AQI=\"," +
|
||||
"\"array\":[\"Foo\",\"Bar\"]," +
|
||||
"\"number\":42," +
|
||||
"\"string\":\"Foo\"," +
|
||||
"\"bool\":true," +
|
||||
"\"fraction\":42.0}";
|
||||
MockHttpInputMessage inputMessage = new MockHttpInputMessage(body.getBytes(StandardCharsets.UTF_8));
|
||||
inputMessage.getHeaders().setContentType(new MediaType("application", "json"));
|
||||
HashMap<String, Object> result = (HashMap<String, Object>) converter.read(HashMap.class, inputMessage);
|
||||
assertThat(result).containsEntry("string", "Foo");
|
||||
assertThat(result).containsEntry("number", 42);
|
||||
assertThat((Double) result.get("fraction")).isCloseTo(42D, within(0D));
|
||||
List<String> array = new ArrayList<>();
|
||||
array.add("Foo");
|
||||
array.add("Bar");
|
||||
assertThat(result).containsEntry("array", array);
|
||||
assertThat(result).containsEntry("bool", Boolean.TRUE);
|
||||
assertThat(result).containsEntry("bytes", "AQI=");
|
||||
}
|
||||
|
||||
@Test
|
||||
void write() throws IOException {
|
||||
MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
|
||||
MyBean body = new MyBean();
|
||||
body.setString("Foo");
|
||||
body.setNumber(42);
|
||||
body.setFraction(42F);
|
||||
body.setArray(new String[] {"Foo", "Bar"});
|
||||
body.setBool(true);
|
||||
body.setBytes(new byte[] {0x1, 0x2});
|
||||
converter.write(body, null, outputMessage);
|
||||
String result = outputMessage.getBodyAsString(StandardCharsets.UTF_8);
|
||||
assertThat(result).contains("\"string\":\"Foo\"");
|
||||
assertThat(result).contains("\"number\":42");
|
||||
assertThat(result).contains("fraction\":42.0");
|
||||
assertThat(result).contains("\"array\":[\"Foo\",\"Bar\"]");
|
||||
assertThat(result).contains("\"bool\":true");
|
||||
assertThat(result).contains("\"bytes\":\"AQI=\"");
|
||||
assertThat(outputMessage.getHeaders().getContentType())
|
||||
.as("Invalid content-type").isEqualTo(MediaType.APPLICATION_JSON);
|
||||
}
|
||||
|
||||
@Test
|
||||
void writeWithBaseType() throws IOException {
|
||||
MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
|
||||
MyBean body = new MyBean();
|
||||
body.setString("Foo");
|
||||
body.setNumber(42);
|
||||
body.setFraction(42F);
|
||||
body.setArray(new String[] {"Foo", "Bar"});
|
||||
body.setBool(true);
|
||||
body.setBytes(new byte[] {0x1, 0x2});
|
||||
converter.write(body, ResolvableType.forClass(MyBase.class), null, outputMessage, null);
|
||||
String result = outputMessage.getBodyAsString(StandardCharsets.UTF_8);
|
||||
assertThat(result).contains("\"string\":\"Foo\"");
|
||||
assertThat(result).contains("\"number\":42");
|
||||
assertThat(result).contains("fraction\":42.0");
|
||||
assertThat(result).contains("\"array\":[\"Foo\",\"Bar\"]");
|
||||
assertThat(result).contains("\"bool\":true");
|
||||
assertThat(result).contains("\"bytes\":\"AQI=\"");
|
||||
assertThat(outputMessage.getHeaders().getContentType())
|
||||
.as("Invalid content-type").isEqualTo(MediaType.APPLICATION_JSON);
|
||||
}
|
||||
|
||||
@Test
|
||||
void writeUTF16() throws IOException {
|
||||
MediaType contentType = new MediaType("application", "json", StandardCharsets.UTF_16BE);
|
||||
MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
|
||||
String body = "H\u00e9llo W\u00f6rld";
|
||||
converter.write(body, contentType, outputMessage);
|
||||
assertThat(outputMessage.getBodyAsString(StandardCharsets.UTF_16BE)).as("Invalid result").isEqualTo(("\"" + body + "\""));
|
||||
assertThat(outputMessage.getHeaders().getContentType()).as("Invalid content-type").isEqualTo(contentType);
|
||||
}
|
||||
|
||||
@Test
|
||||
void readInvalidJson() {
|
||||
String body = "FooBar";
|
||||
MockHttpInputMessage inputMessage = new MockHttpInputMessage(body.getBytes(StandardCharsets.UTF_8));
|
||||
inputMessage.getHeaders().setContentType(new MediaType("application", "json"));
|
||||
assertThatExceptionOfType(HttpMessageNotReadableException.class)
|
||||
.isThrownBy(() -> converter.read(MyBean.class, inputMessage));
|
||||
}
|
||||
|
||||
@Test
|
||||
void readValidJsonWithUnknownProperty() throws IOException {
|
||||
String body = "{\"string\":\"string\",\"unknownProperty\":\"value\"}";
|
||||
MockHttpInputMessage inputMessage = new MockHttpInputMessage(body.getBytes(StandardCharsets.UTF_8));
|
||||
inputMessage.getHeaders().setContentType(new MediaType("application", "json"));
|
||||
converter.read(MyBean.class, inputMessage);
|
||||
// Assert no HttpMessageNotReadableException is thrown
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
void readAndWriteGenerics() throws Exception {
|
||||
JacksonJsonHttpMessageConverter converter = new JacksonJsonHttpMessageConverter() {
|
||||
@Override
|
||||
protected JavaType getJavaType(Type type, @Nullable Class<?> contextClass) {
|
||||
if (type instanceof Class && List.class.isAssignableFrom((Class<?>)type)) {
|
||||
return new ObjectMapper().getTypeFactory().constructCollectionType(ArrayList.class, MyBean.class);
|
||||
}
|
||||
else {
|
||||
return super.getJavaType(type, contextClass);
|
||||
}
|
||||
}
|
||||
};
|
||||
String body = "[{" +
|
||||
"\"bytes\":\"AQI=\"," +
|
||||
"\"array\":[\"Foo\",\"Bar\"]," +
|
||||
"\"number\":42," +
|
||||
"\"string\":\"Foo\"," +
|
||||
"\"bool\":true," +
|
||||
"\"fraction\":42.0}]";
|
||||
MockHttpInputMessage inputMessage = new MockHttpInputMessage(body.getBytes(StandardCharsets.UTF_8));
|
||||
inputMessage.getHeaders().setContentType(new MediaType("application", "json"));
|
||||
|
||||
List<MyBean> results = (List<MyBean>) converter.read(List.class, inputMessage);
|
||||
assertThat(results).hasSize(1);
|
||||
MyBean result = results.get(0);
|
||||
assertThat(result.getString()).isEqualTo("Foo");
|
||||
assertThat(result.getNumber()).isEqualTo(42);
|
||||
assertThat(result.getFraction()).isCloseTo(42F, within(0F));
|
||||
assertThat(result.getArray()).isEqualTo(new String[] {"Foo", "Bar"});
|
||||
assertThat(result.isBool()).isTrue();
|
||||
assertThat(result.getBytes()).isEqualTo(new byte[] {0x1, 0x2});
|
||||
|
||||
MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
|
||||
converter.write(results, MediaType.APPLICATION_JSON, outputMessage);
|
||||
JSONAssert.assertEquals(body, outputMessage.getBodyAsString(StandardCharsets.UTF_8), true);
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
void readAndWriteParameterizedType() throws Exception {
|
||||
ParameterizedTypeReference<List<MyBean>> beansList = new ParameterizedTypeReference<>() {};
|
||||
|
||||
String body = "[{" +
|
||||
"\"bytes\":\"AQI=\"," +
|
||||
"\"array\":[\"Foo\",\"Bar\"]," +
|
||||
"\"number\":42," +
|
||||
"\"string\":\"Foo\"," +
|
||||
"\"bool\":true," +
|
||||
"\"fraction\":42.0}]";
|
||||
MockHttpInputMessage inputMessage = new MockHttpInputMessage(body.getBytes(StandardCharsets.UTF_8));
|
||||
inputMessage.getHeaders().setContentType(MediaType.APPLICATION_JSON);
|
||||
|
||||
JacksonJsonHttpMessageConverter converter = new JacksonJsonHttpMessageConverter();
|
||||
List<MyBean> results = (List<MyBean>) converter.read(ResolvableType.forType(beansList), inputMessage, null);
|
||||
assertThat(results).hasSize(1);
|
||||
MyBean result = results.get(0);
|
||||
assertThat(result.getString()).isEqualTo("Foo");
|
||||
assertThat(result.getNumber()).isEqualTo(42);
|
||||
assertThat(result.getFraction()).isCloseTo(42F, within(0F));
|
||||
assertThat(result.getArray()).isEqualTo(new String[] {"Foo", "Bar"});
|
||||
assertThat(result.isBool()).isTrue();
|
||||
assertThat(result.getBytes()).isEqualTo(new byte[] {0x1, 0x2});
|
||||
|
||||
MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
|
||||
converter.write(results, ResolvableType.forType(beansList), MediaType.APPLICATION_JSON, outputMessage, null);
|
||||
JSONAssert.assertEquals(body, outputMessage.getBodyAsString(StandardCharsets.UTF_8), true);
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
void writeParameterizedBaseType() throws Exception {
|
||||
ParameterizedTypeReference<List<MyBean>> beansList = new ParameterizedTypeReference<>() {};
|
||||
ParameterizedTypeReference<List<MyBase>> baseList = new ParameterizedTypeReference<>() {};
|
||||
|
||||
String body = "[{" +
|
||||
"\"bytes\":\"AQI=\"," +
|
||||
"\"array\":[\"Foo\",\"Bar\"]," +
|
||||
"\"number\":42," +
|
||||
"\"string\":\"Foo\"," +
|
||||
"\"bool\":true," +
|
||||
"\"fraction\":42.0}]";
|
||||
MockHttpInputMessage inputMessage = new MockHttpInputMessage(body.getBytes(StandardCharsets.UTF_8));
|
||||
inputMessage.getHeaders().setContentType(MediaType.APPLICATION_JSON);
|
||||
|
||||
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
|
||||
List<MyBean> results = (List<MyBean>) converter.read(beansList.getType(), null, inputMessage);
|
||||
assertThat(results).hasSize(1);
|
||||
MyBean result = results.get(0);
|
||||
assertThat(result.getString()).isEqualTo("Foo");
|
||||
assertThat(result.getNumber()).isEqualTo(42);
|
||||
assertThat(result.getFraction()).isCloseTo(42F, within(0F));
|
||||
assertThat(result.getArray()).isEqualTo(new String[] {"Foo", "Bar"});
|
||||
assertThat(result.isBool()).isTrue();
|
||||
assertThat(result.getBytes()).isEqualTo(new byte[] {0x1, 0x2});
|
||||
|
||||
MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
|
||||
converter.write(results, baseList.getType(), MediaType.APPLICATION_JSON, outputMessage);
|
||||
JSONAssert.assertEquals(body, outputMessage.getBodyAsString(StandardCharsets.UTF_8), true);
|
||||
}
|
||||
|
||||
// gh-24498
|
||||
@Test
|
||||
void writeOptional() throws IOException {
|
||||
ParameterizedTypeReference<Optional<MyParent>> optionalParent = new ParameterizedTypeReference<>() {};
|
||||
Optional<MyParent> result = Optional.of(new Impl1());
|
||||
MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
|
||||
converter.write(result, ResolvableType.forType(optionalParent.getType()),
|
||||
MediaType.APPLICATION_JSON, outputMessage, null);
|
||||
|
||||
assertThat(outputMessage.getBodyAsString(StandardCharsets.UTF_8))
|
||||
.contains("@type");
|
||||
}
|
||||
|
||||
@Test
|
||||
void prettyPrint() throws Exception {
|
||||
MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
|
||||
PrettyPrintBean bean = new PrettyPrintBean();
|
||||
bean.setName("Jason");
|
||||
|
||||
ObjectMapper mapper = JsonMapper.builder().enable(SerializationFeature.INDENT_OUTPUT).build();
|
||||
this.converter = new JacksonJsonHttpMessageConverter(mapper);
|
||||
this.converter.write(bean, ResolvableType.forType(PrettyPrintBean.class),
|
||||
MediaType.APPLICATION_JSON, outputMessage, null);
|
||||
String result = outputMessage.getBodyAsString(StandardCharsets.UTF_8);
|
||||
|
||||
assertThat(result).isEqualToNormalizingNewlines("""
|
||||
{
|
||||
\s "name" : "Jason"
|
||||
}""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void prettyPrintWithSse() throws Exception {
|
||||
MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
|
||||
outputMessage.getHeaders().setContentType(MediaType.TEXT_EVENT_STREAM);
|
||||
PrettyPrintBean bean = new PrettyPrintBean();
|
||||
bean.setName("Jason");
|
||||
|
||||
ObjectMapper mapper = JsonMapper.builder().enable(SerializationFeature.INDENT_OUTPUT).build();
|
||||
this.converter = new JacksonJsonHttpMessageConverter(mapper);
|
||||
this.converter.write(bean, ResolvableType.forType(PrettyPrintBean.class),
|
||||
MediaType.APPLICATION_JSON, outputMessage, null);
|
||||
String result = outputMessage.getBodyAsString(StandardCharsets.UTF_8);
|
||||
|
||||
assertThat(result).isEqualTo("{\ndata: \"name\" : \"Jason\"\ndata:}");
|
||||
}
|
||||
|
||||
@Test
|
||||
void prefixJson() throws Exception {
|
||||
MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
|
||||
this.converter.setPrefixJson(true);
|
||||
this.converter.write("foo", ResolvableType.forType(String.class), MediaType.APPLICATION_JSON,
|
||||
outputMessage, null);
|
||||
|
||||
assertThat(outputMessage.getBodyAsString(StandardCharsets.UTF_8)).isEqualTo(")]}', \"foo\"");
|
||||
}
|
||||
|
||||
@Test
|
||||
void prefixJsonCustom() throws Exception {
|
||||
MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
|
||||
this.converter.setJsonPrefix(")))");
|
||||
this.converter.write("foo", ResolvableType.forType(String.class), MediaType.APPLICATION_JSON,
|
||||
outputMessage, null);
|
||||
|
||||
assertThat(outputMessage.getBodyAsString(StandardCharsets.UTF_8)).isEqualTo(")))\"foo\"");
|
||||
}
|
||||
|
||||
@Test
|
||||
void fieldLevelJsonView() throws Exception {
|
||||
MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
|
||||
JacksonViewBean bean = new JacksonViewBean();
|
||||
bean.setWithView1("with");
|
||||
bean.setWithView2("with");
|
||||
bean.setWithoutView("without");
|
||||
|
||||
Map<String, Object> hints = Collections.singletonMap(JsonView.class.getName(), MyJacksonView1.class);
|
||||
this.converter.write(bean, ResolvableType.forType(JacksonViewBean.class), MediaType.APPLICATION_JSON,
|
||||
outputMessage, hints);
|
||||
|
||||
String result = outputMessage.getBodyAsString(StandardCharsets.UTF_8);
|
||||
assertThat(result).contains("\"withView1\":\"with\"");
|
||||
assertThat(result).doesNotContain("\"withView2\":\"with\"");
|
||||
assertThat(result).doesNotContain("\"withoutView\":\"without\"");
|
||||
}
|
||||
|
||||
@Test
|
||||
void classLevelJsonView() throws Exception {
|
||||
MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
|
||||
JacksonViewBean bean = new JacksonViewBean();
|
||||
bean.setWithView1("with");
|
||||
bean.setWithView2("with");
|
||||
bean.setWithoutView("without");
|
||||
|
||||
Map<String, Object> hints = Collections.singletonMap(JsonView.class.getName(), MyJacksonView3.class);
|
||||
this.converter.write(bean, ResolvableType.forType(JacksonViewBean.class), MediaType.APPLICATION_JSON,
|
||||
outputMessage, hints);
|
||||
|
||||
String result = outputMessage.getBodyAsString(StandardCharsets.UTF_8);
|
||||
assertThat(result).doesNotContain("\"withView1\":\"with\"");
|
||||
assertThat(result).doesNotContain("\"withView2\":\"with\"");
|
||||
assertThat(result).contains("\"withoutView\":\"without\"");
|
||||
}
|
||||
|
||||
@Test
|
||||
void filters() throws Exception {
|
||||
MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
|
||||
JacksonFilteredBean bean = new JacksonFilteredBean();
|
||||
bean.setProperty1("value");
|
||||
bean.setProperty2("value");
|
||||
|
||||
|
||||
FilterProvider filters = new SimpleFilterProvider().addFilter("myJacksonFilter",
|
||||
SimpleBeanPropertyFilter.serializeAllExcept("property2"));
|
||||
Map<String, Object> hints = Collections.singletonMap(FilterProvider.class.getName(), filters);
|
||||
this.converter.write(bean, ResolvableType.forType(JacksonFilteredBean.class), MediaType.APPLICATION_JSON,
|
||||
outputMessage, hints);
|
||||
|
||||
String result = outputMessage.getBodyAsString(StandardCharsets.UTF_8);
|
||||
assertThat(result).contains("\"property1\":\"value\"");
|
||||
assertThat(result).doesNotContain("\"property2\":\"value\"");
|
||||
}
|
||||
|
||||
@Test // SPR-13318
|
||||
void writeSubType() throws Exception {
|
||||
MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
|
||||
MyBean bean = new MyBean();
|
||||
bean.setString("Foo");
|
||||
bean.setNumber(42);
|
||||
|
||||
this.converter.write(bean, ResolvableType.forType(MyInterface.class),
|
||||
MediaType.APPLICATION_JSON, outputMessage, null);
|
||||
|
||||
String result = outputMessage.getBodyAsString(StandardCharsets.UTF_8);
|
||||
assertThat(result).contains("\"string\":\"Foo\"");
|
||||
assertThat(result).contains("\"number\":42");
|
||||
}
|
||||
|
||||
@Test // SPR-13318
|
||||
void writeSubTypeList() throws Exception {
|
||||
MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
|
||||
List<MyBean> beans = new ArrayList<>();
|
||||
MyBean foo = new MyBean();
|
||||
foo.setString("Foo");
|
||||
foo.setNumber(42);
|
||||
beans.add(foo);
|
||||
MyBean bar = new MyBean();
|
||||
bar.setString("Bar");
|
||||
bar.setNumber(123);
|
||||
beans.add(bar);
|
||||
ParameterizedTypeReference<List<MyInterface>> typeReference =
|
||||
new ParameterizedTypeReference<>() {};
|
||||
|
||||
this.converter.write(beans, ResolvableType.forType(typeReference), MediaType.APPLICATION_JSON,
|
||||
outputMessage, null);
|
||||
|
||||
String result = outputMessage.getBodyAsString(StandardCharsets.UTF_8);
|
||||
assertThat(result).contains("\"string\":\"Foo\"");
|
||||
assertThat(result).contains("\"number\":42");
|
||||
assertThat(result).contains("\"string\":\"Bar\"");
|
||||
assertThat(result).contains("\"number\":123");
|
||||
}
|
||||
|
||||
@Test // gh-27511
|
||||
void readWithNoDefaultConstructor() throws Exception {
|
||||
String body = "{\"property1\":\"foo\",\"property2\":\"bar\"}";
|
||||
MockHttpInputMessage inputMessage = new MockHttpInputMessage(body.getBytes(StandardCharsets.UTF_8));
|
||||
inputMessage.getHeaders().setContentType(MediaType.APPLICATION_JSON);
|
||||
BeanWithNoDefaultConstructor bean =
|
||||
(BeanWithNoDefaultConstructor)converter.read(BeanWithNoDefaultConstructor.class, inputMessage);
|
||||
assertThat(bean.property1).isEqualTo("foo");
|
||||
assertThat(bean.property2).isEqualTo("bar");
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
void readNonUnicode() throws Exception {
|
||||
String body = "{\"føø\":\"bår\"}";
|
||||
Charset charset = StandardCharsets.ISO_8859_1;
|
||||
MockHttpInputMessage inputMessage = new MockHttpInputMessage(body.getBytes(charset));
|
||||
inputMessage.getHeaders().setContentType(new MediaType("application", "json", charset));
|
||||
HashMap<String, Object> result = (HashMap<String, Object>) this.converter.read(HashMap.class, inputMessage);
|
||||
|
||||
assertThat(result).containsExactly(entry("føø", "bår"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
void readAscii() throws Exception {
|
||||
String body = "{\"foo\":\"bar\"}";
|
||||
Charset charset = StandardCharsets.US_ASCII;
|
||||
MockHttpInputMessage inputMessage = new MockHttpInputMessage(body.getBytes(charset));
|
||||
inputMessage.getHeaders().setContentType(new MediaType("application", "json", charset));
|
||||
HashMap<String, Object> result = (HashMap<String, Object>) this.converter.read(HashMap.class, inputMessage);
|
||||
|
||||
assertThat(result).containsExactly(entry("foo", "bar"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void writeAscii() throws Exception {
|
||||
MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
|
||||
Map<String,Object> body = new HashMap<>();
|
||||
body.put("foo", "bar");
|
||||
Charset charset = StandardCharsets.US_ASCII;
|
||||
MediaType contentType = new MediaType("application", "json", charset);
|
||||
converter.write(body, contentType, outputMessage);
|
||||
|
||||
String result = outputMessage.getBodyAsString(charset);
|
||||
assertThat(result).isEqualTo("{\"foo\":\"bar\"}");
|
||||
assertThat(outputMessage.getHeaders().getContentType()).as("Invalid content-type").isEqualTo(contentType);
|
||||
}
|
||||
|
||||
@Test
|
||||
void readWithCustomized() throws IOException {
|
||||
JacksonJsonHttpMessageConverterWithCustomization customizedConverter =
|
||||
new JacksonJsonHttpMessageConverterWithCustomization();
|
||||
String body = "{\"property\":\"Value1\"}";
|
||||
MockHttpInputMessage inputMessage1 = new MockHttpInputMessage(body.getBytes(StandardCharsets.UTF_8));
|
||||
MockHttpInputMessage inputMessage2 = new MockHttpInputMessage(body.getBytes(StandardCharsets.UTF_8));
|
||||
inputMessage1.getHeaders().setContentType(new MediaType("application", "json"));
|
||||
inputMessage2.getHeaders().setContentType(new MediaType("application", "json"));
|
||||
|
||||
assertThatExceptionOfType(HttpMessageNotReadableException.class)
|
||||
.isThrownBy(() -> customizedConverter.read(MyCustomizedBean.class, inputMessage1));
|
||||
|
||||
MyCustomizedBean customizedResult = (MyCustomizedBean) converter.read(MyCustomizedBean.class, inputMessage2);
|
||||
assertThat(customizedResult.getProperty()).isEqualTo(MyCustomEnum.VAL1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void writeWithCustomized() throws IOException {
|
||||
JacksonJsonHttpMessageConverterWithCustomization customizedConverter =
|
||||
new JacksonJsonHttpMessageConverterWithCustomization();
|
||||
MockHttpOutputMessage outputMessage1 = new MockHttpOutputMessage();
|
||||
MockHttpOutputMessage outputMessage2 = new MockHttpOutputMessage();
|
||||
MyCustomizedBean body = new MyCustomizedBean();
|
||||
body.setProperty(MyCustomEnum.VAL2);
|
||||
converter.write(body, null, outputMessage1);
|
||||
customizedConverter.write(body, null, outputMessage2);
|
||||
String result1 = outputMessage1.getBodyAsString(StandardCharsets.UTF_8);
|
||||
assertThat(result1).contains("\"property\":\"Value2\"");
|
||||
String result2 = outputMessage2.getBodyAsString(StandardCharsets.UTF_8);
|
||||
assertThat(result2).contains("\"property\":\"VAL2\"");
|
||||
}
|
||||
|
||||
@Test
|
||||
void repeatableWrites() throws IOException {
|
||||
MockHttpOutputMessage outputMessage1 = new MockHttpOutputMessage();
|
||||
MyBean body = new MyBean();
|
||||
body.setString("Foo");
|
||||
converter.write(body, null, outputMessage1);
|
||||
String result = outputMessage1.getBodyAsString(StandardCharsets.UTF_8);
|
||||
assertThat(result).contains("\"string\":\"Foo\"");
|
||||
|
||||
MockHttpOutputMessage outputMessage2 = new MockHttpOutputMessage();
|
||||
converter.write(body, null, outputMessage2);
|
||||
result = outputMessage2.getBodyAsString(StandardCharsets.UTF_8);
|
||||
assertThat(result).contains("\"string\":\"Foo\"");
|
||||
}
|
||||
|
||||
|
||||
|
||||
interface MyInterface {
|
||||
|
||||
String getString();
|
||||
|
||||
void setString(String string);
|
||||
}
|
||||
|
||||
|
||||
public static class MyBase implements MyInterface {
|
||||
|
||||
private String string;
|
||||
|
||||
@Override
|
||||
public String getString() {
|
||||
return string;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setString(String string) {
|
||||
this.string = string;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static class MyBean extends MyBase {
|
||||
|
||||
private int number;
|
||||
|
||||
private float fraction;
|
||||
|
||||
private String[] array;
|
||||
|
||||
private boolean bool;
|
||||
|
||||
private byte[] bytes;
|
||||
|
||||
public int getNumber() {
|
||||
return number;
|
||||
}
|
||||
|
||||
public void setNumber(int number) {
|
||||
this.number = number;
|
||||
}
|
||||
|
||||
public float getFraction() {
|
||||
return fraction;
|
||||
}
|
||||
|
||||
public void setFraction(float fraction) {
|
||||
this.fraction = fraction;
|
||||
}
|
||||
|
||||
public String[] getArray() {
|
||||
return array;
|
||||
}
|
||||
|
||||
public void setArray(String[] array) {
|
||||
this.array = array;
|
||||
}
|
||||
|
||||
public boolean isBool() {
|
||||
return bool;
|
||||
}
|
||||
|
||||
public void setBool(boolean bool) {
|
||||
this.bool = bool;
|
||||
}
|
||||
|
||||
public byte[] getBytes() {
|
||||
return bytes;
|
||||
}
|
||||
|
||||
public void setBytes(byte[] bytes) {
|
||||
this.bytes = bytes;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static class PrettyPrintBean {
|
||||
|
||||
private String name;
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private interface MyJacksonView1 {}
|
||||
|
||||
private interface MyJacksonView2 {}
|
||||
|
||||
private interface MyJacksonView3 {}
|
||||
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
@JsonView(MyJacksonView3.class)
|
||||
private static class JacksonViewBean {
|
||||
|
||||
@JsonView(MyJacksonView1.class)
|
||||
private String withView1;
|
||||
|
||||
@JsonView(MyJacksonView2.class)
|
||||
private String withView2;
|
||||
|
||||
private String withoutView;
|
||||
|
||||
public String getWithView1() {
|
||||
return withView1;
|
||||
}
|
||||
|
||||
public void setWithView1(String withView1) {
|
||||
this.withView1 = withView1;
|
||||
}
|
||||
|
||||
public String getWithView2() {
|
||||
return withView2;
|
||||
}
|
||||
|
||||
public void setWithView2(String withView2) {
|
||||
this.withView2 = withView2;
|
||||
}
|
||||
|
||||
public String getWithoutView() {
|
||||
return withoutView;
|
||||
}
|
||||
|
||||
public void setWithoutView(String withoutView) {
|
||||
this.withoutView = withoutView;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@JsonFilter("myJacksonFilter")
|
||||
@SuppressWarnings("unused")
|
||||
private static class JacksonFilteredBean {
|
||||
|
||||
private String property1;
|
||||
|
||||
private String property2;
|
||||
|
||||
public String getProperty1() {
|
||||
return property1;
|
||||
}
|
||||
|
||||
public void setProperty1(String property1) {
|
||||
this.property1 = property1;
|
||||
}
|
||||
|
||||
public String getProperty2() {
|
||||
return property2;
|
||||
}
|
||||
|
||||
public void setProperty2(String property2) {
|
||||
this.property2 = property2;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static class BeanWithNoDefaultConstructor {
|
||||
|
||||
private final String property1;
|
||||
|
||||
private final String property2;
|
||||
|
||||
public BeanWithNoDefaultConstructor(String property1, String property2) {
|
||||
this.property1 = property1;
|
||||
this.property2 = property2;
|
||||
}
|
||||
|
||||
public String getProperty1() {
|
||||
return property1;
|
||||
}
|
||||
|
||||
public String getProperty2() {
|
||||
return property2;
|
||||
}
|
||||
}
|
||||
|
||||
public static class MyCustomizedBean {
|
||||
|
||||
private MyCustomEnum property;
|
||||
|
||||
public MyCustomEnum getProperty() {
|
||||
return property;
|
||||
}
|
||||
|
||||
public void setProperty(MyCustomEnum property) {
|
||||
this.property = property;
|
||||
}
|
||||
}
|
||||
|
||||
public enum MyCustomEnum {
|
||||
VAL1,
|
||||
VAL2;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this == VAL1 ? "Value1" : "Value2";
|
||||
}
|
||||
}
|
||||
|
||||
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME)
|
||||
@JsonSubTypes(value = {@JsonSubTypes.Type(value = Impl1.class),
|
||||
@JsonSubTypes.Type(value = Impl2.class)})
|
||||
public interface MyParent {
|
||||
}
|
||||
|
||||
public static class Impl1 implements MyParent {
|
||||
}
|
||||
|
||||
public static class Impl2 implements MyParent {
|
||||
}
|
||||
|
||||
private static class JacksonJsonHttpMessageConverterWithCustomization extends JacksonJsonHttpMessageConverter {
|
||||
|
||||
@Override
|
||||
protected ObjectReader customizeReader(ObjectReader reader, JavaType javaType) {
|
||||
return reader.without(EnumFeature.READ_ENUMS_USING_TO_STRING);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ObjectWriter customizeWriter(ObjectWriter writer, @Nullable JavaType javaType, @Nullable MediaType contentType) {
|
||||
return writer.without(EnumFeature.WRITE_ENUMS_USING_TO_STRING);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
/*
|
||||
* Copyright 2002-2025 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.http.converter.smile;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import tools.jackson.dataformat.smile.SmileMapper;
|
||||
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.web.testfixture.http.MockHttpInputMessage;
|
||||
import org.springframework.web.testfixture.http.MockHttpOutputMessage;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.within;
|
||||
|
||||
/**
|
||||
* Jackson 3.x Smile converter tests.
|
||||
*
|
||||
* @author Sebastien Deleuze
|
||||
*/
|
||||
class JacksonSmileHttpMessageConverterTests {
|
||||
|
||||
private final JacksonSmileHttpMessageConverter converter = new JacksonSmileHttpMessageConverter();
|
||||
private final SmileMapper mapper = SmileMapper.builder().build();
|
||||
|
||||
|
||||
@Test
|
||||
void canRead() {
|
||||
assertThat(converter.canRead(MyBean.class, new MediaType("application", "x-jackson-smile"))).isTrue();
|
||||
assertThat(converter.canRead(MyBean.class, new MediaType("application", "json"))).isFalse();
|
||||
assertThat(converter.canRead(MyBean.class, new MediaType("application", "xml"))).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void canWrite() {
|
||||
assertThat(converter.canWrite(MyBean.class, new MediaType("application", "x-jackson-smile"))).isTrue();
|
||||
assertThat(converter.canWrite(MyBean.class, new MediaType("application", "json"))).isFalse();
|
||||
assertThat(converter.canWrite(MyBean.class, new MediaType("application", "xml"))).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void read() throws IOException {
|
||||
MyBean body = new MyBean();
|
||||
body.setString("Foo");
|
||||
body.setNumber(42);
|
||||
body.setFraction(42F);
|
||||
body.setArray(new String[]{"Foo", "Bar"});
|
||||
body.setBool(true);
|
||||
body.setBytes(new byte[]{0x1, 0x2});
|
||||
MockHttpInputMessage inputMessage = new MockHttpInputMessage(mapper.writeValueAsBytes(body));
|
||||
inputMessage.getHeaders().setContentType(new MediaType("application", "x-jackson-smile"));
|
||||
MyBean result = (MyBean) converter.read(MyBean.class, inputMessage);
|
||||
assertThat(result.getString()).isEqualTo("Foo");
|
||||
assertThat(result.getNumber()).isEqualTo(42);
|
||||
assertThat(result.getFraction()).isCloseTo(42F, within(0F));
|
||||
|
||||
assertThat(result.getArray()).isEqualTo(new String[]{"Foo", "Bar"});
|
||||
assertThat(result.isBool()).isTrue();
|
||||
assertThat(result.getBytes()).isEqualTo(new byte[]{0x1, 0x2});
|
||||
}
|
||||
|
||||
@Test
|
||||
void write() throws IOException {
|
||||
MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
|
||||
MyBean body = new MyBean();
|
||||
body.setString("Foo");
|
||||
body.setNumber(42);
|
||||
body.setFraction(42F);
|
||||
body.setArray(new String[]{"Foo", "Bar"});
|
||||
body.setBool(true);
|
||||
body.setBytes(new byte[]{0x1, 0x2});
|
||||
converter.write(body, null, outputMessage);
|
||||
assertThat(outputMessage.getBodyAsBytes()).isEqualTo(mapper.writeValueAsBytes(body));
|
||||
assertThat(outputMessage.getHeaders().getContentType())
|
||||
.as("Invalid content-type").isEqualTo(new MediaType("application", "x-jackson-smile"));
|
||||
}
|
||||
|
||||
|
||||
public static class MyBean {
|
||||
|
||||
private String string;
|
||||
|
||||
private int number;
|
||||
|
||||
private float fraction;
|
||||
|
||||
private String[] array;
|
||||
|
||||
private boolean bool;
|
||||
|
||||
private byte[] bytes;
|
||||
|
||||
public byte[] getBytes() {
|
||||
return bytes;
|
||||
}
|
||||
|
||||
public void setBytes(byte[] bytes) {
|
||||
this.bytes = bytes;
|
||||
}
|
||||
|
||||
public boolean isBool() {
|
||||
return bool;
|
||||
}
|
||||
|
||||
public void setBool(boolean bool) {
|
||||
this.bool = bool;
|
||||
}
|
||||
|
||||
public String getString() {
|
||||
return string;
|
||||
}
|
||||
|
||||
public void setString(String string) {
|
||||
this.string = string;
|
||||
}
|
||||
|
||||
public int getNumber() {
|
||||
return number;
|
||||
}
|
||||
|
||||
public void setNumber(int number) {
|
||||
this.number = number;
|
||||
}
|
||||
|
||||
public float getFraction() {
|
||||
return fraction;
|
||||
}
|
||||
|
||||
public void setFraction(float fraction) {
|
||||
this.fraction = fraction;
|
||||
}
|
||||
|
||||
public String[] getArray() {
|
||||
return array;
|
||||
}
|
||||
|
||||
public void setArray(String[] array) {
|
||||
this.array = array;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,319 @@
|
||||
/*
|
||||
* Copyright 2002-2025 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.http.converter.xml;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonView;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import tools.jackson.dataformat.xml.XmlMapper;
|
||||
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.converter.HttpMessageNotReadableException;
|
||||
import org.springframework.web.testfixture.http.MockHttpInputMessage;
|
||||
import org.springframework.web.testfixture.http.MockHttpOutputMessage;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
import static org.assertj.core.api.Assertions.within;
|
||||
|
||||
/**
|
||||
* Jackson 3.x XML converter tests.
|
||||
*
|
||||
* @author Sebastien Deleuze
|
||||
*/
|
||||
class JacksonXmlHttpMessageConverterTests {
|
||||
|
||||
private final JacksonXmlHttpMessageConverter converter = new JacksonXmlHttpMessageConverter();
|
||||
|
||||
|
||||
@Test
|
||||
void canRead() {
|
||||
assertThat(converter.canRead(MyBean.class, new MediaType("application", "xml"))).isTrue();
|
||||
assertThat(converter.canRead(MyBean.class, new MediaType("text", "xml"))).isTrue();
|
||||
assertThat(converter.canRead(MyBean.class, new MediaType("application", "soap+xml"))).isTrue();
|
||||
assertThat(converter.canRead(MyBean.class, new MediaType("text", "xml", StandardCharsets.UTF_8))).isTrue();
|
||||
assertThat(converter.canRead(MyBean.class, new MediaType("text", "xml", StandardCharsets.ISO_8859_1))).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void canWrite() {
|
||||
assertThat(converter.canWrite(MyBean.class, new MediaType("application", "xml"))).isTrue();
|
||||
assertThat(converter.canWrite(MyBean.class, new MediaType("text", "xml"))).isTrue();
|
||||
assertThat(converter.canWrite(MyBean.class, new MediaType("application", "soap+xml"))).isTrue();
|
||||
assertThat(converter.canWrite(MyBean.class, new MediaType("text", "xml", StandardCharsets.UTF_8))).isTrue();
|
||||
assertThat(converter.canWrite(MyBean.class, new MediaType("text", "xml", StandardCharsets.ISO_8859_1))).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void read() throws IOException {
|
||||
String body = "<MyBean>" +
|
||||
"<string>Foo</string>" +
|
||||
"<number>42</number>" +
|
||||
"<fraction>42.0</fraction>" +
|
||||
"<array><array>Foo</array>" +
|
||||
"<array>Bar</array></array>" +
|
||||
"<bool>true</bool>" +
|
||||
"<bytes>AQI=</bytes></MyBean>";
|
||||
MockHttpInputMessage inputMessage = new MockHttpInputMessage(body.getBytes(StandardCharsets.UTF_8));
|
||||
inputMessage.getHeaders().setContentType(new MediaType("application", "xml"));
|
||||
MyBean result = (MyBean) converter.read(MyBean.class, inputMessage);
|
||||
assertThat(result.getString()).isEqualTo("Foo");
|
||||
assertThat(result.getNumber()).isEqualTo(42);
|
||||
assertThat(result.getFraction()).isCloseTo(42F, within(0F));
|
||||
assertThat(result.getArray()).isEqualTo(new String[]{"Foo", "Bar"});
|
||||
assertThat(result.isBool()).isTrue();
|
||||
assertThat(result.getBytes()).isEqualTo(new byte[]{0x1, 0x2});
|
||||
}
|
||||
|
||||
@Test
|
||||
void write() throws IOException {
|
||||
MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
|
||||
MyBean body = new MyBean();
|
||||
body.setString("Foo");
|
||||
body.setNumber(42);
|
||||
body.setFraction(42F);
|
||||
body.setArray(new String[]{"Foo", "Bar"});
|
||||
body.setBool(true);
|
||||
body.setBytes(new byte[]{0x1, 0x2});
|
||||
converter.write(body, null, outputMessage);
|
||||
String result = outputMessage.getBodyAsString(StandardCharsets.UTF_8);
|
||||
assertThat(result).contains("<string>Foo</string>");
|
||||
assertThat(result).contains("<number>42</number>");
|
||||
assertThat(result).contains("<fraction>42.0</fraction>");
|
||||
assertThat(result).contains("<array><array>Foo</array><array>Bar</array></array>");
|
||||
assertThat(result).contains("<bool>true</bool>");
|
||||
assertThat(result).contains("<bytes>AQI=</bytes>");
|
||||
assertThat(outputMessage.getHeaders().getContentType())
|
||||
.as("Invalid content-type").isEqualTo(new MediaType("application", "xml", StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
@Test
|
||||
void readInvalidXml() {
|
||||
String body = "FooBar";
|
||||
MockHttpInputMessage inputMessage = new MockHttpInputMessage(body.getBytes(StandardCharsets.UTF_8));
|
||||
inputMessage.getHeaders().setContentType(MediaType.APPLICATION_XML);
|
||||
assertThatExceptionOfType(HttpMessageNotReadableException.class).isThrownBy(() ->
|
||||
converter.read(MyBean.class, inputMessage));
|
||||
}
|
||||
|
||||
@Test
|
||||
void readValidXmlWithUnknownProperty() throws IOException {
|
||||
String body = "<MyBean><string>string</string><unknownProperty>value</unknownProperty></MyBean>";
|
||||
MockHttpInputMessage inputMessage = new MockHttpInputMessage(body.getBytes(StandardCharsets.UTF_8));
|
||||
inputMessage.getHeaders().setContentType(MediaType.APPLICATION_XML);
|
||||
converter.read(MyBean.class, inputMessage);
|
||||
// Assert no HttpMessageNotReadableException is thrown
|
||||
}
|
||||
|
||||
@Test
|
||||
void jsonView() throws Exception {
|
||||
MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
|
||||
JacksonViewBean bean = new JacksonViewBean();
|
||||
bean.setWithView1("with");
|
||||
bean.setWithView2("with");
|
||||
bean.setWithoutView("without");
|
||||
|
||||
Map<String, Object> hints = Collections.singletonMap(JsonView.class.getName(), MyJacksonView1.class);
|
||||
this.converter.write(bean, ResolvableType.forClass(JacksonViewBean.class), null, outputMessage, hints);
|
||||
|
||||
String result = outputMessage.getBodyAsString(StandardCharsets.UTF_8);
|
||||
assertThat(result).contains("<withView1>with</withView1>");
|
||||
assertThat(result).doesNotContain("<withView2>with</withView2>");
|
||||
assertThat(result).doesNotContain("<withoutView>without</withoutView>");
|
||||
}
|
||||
|
||||
@Test
|
||||
void customXmlMapper() {
|
||||
new JacksonXmlHttpMessageConverter(new MyXmlMapper());
|
||||
// Assert no exception is thrown
|
||||
}
|
||||
|
||||
@Test
|
||||
void readWithExternalReference() throws IOException {
|
||||
String body = "<!DOCTYPE MyBean SYSTEM \"https://192.168.28.42/1.jsp\" [" +
|
||||
" <!ELEMENT root ANY >\n" +
|
||||
" <!ENTITY ext SYSTEM \"" +
|
||||
new ClassPathResource("external.txt", getClass()).getURI() +
|
||||
"\" >]><MyBean><string>&ext;</string></MyBean>";
|
||||
|
||||
MockHttpInputMessage inputMessage = new MockHttpInputMessage(body.getBytes(StandardCharsets.UTF_8));
|
||||
inputMessage.getHeaders().setContentType(MediaType.APPLICATION_XML);
|
||||
|
||||
assertThatExceptionOfType(HttpMessageNotReadableException.class).isThrownBy(() ->
|
||||
this.converter.read(MyBean.class, inputMessage));
|
||||
}
|
||||
|
||||
@Test
|
||||
void readWithXmlBomb() {
|
||||
// https://en.wikipedia.org/wiki/Billion_laughs
|
||||
// https://msdn.microsoft.com/en-us/magazine/ee335713.aspx
|
||||
String body = """
|
||||
<?xml version="1.0"?>
|
||||
<!DOCTYPE lolz [
|
||||
<!ENTITY lol "lol">
|
||||
<!ELEMENT lolz (#PCDATA)>
|
||||
<!ENTITY lol1 "&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;">
|
||||
<!ENTITY lol2 "&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;">
|
||||
<!ENTITY lol3 "&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;">
|
||||
<!ENTITY lol4 "&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;">
|
||||
<!ENTITY lol5 "&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;">
|
||||
<!ENTITY lol6 "&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;">
|
||||
<!ENTITY lol7 "&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;">
|
||||
<!ENTITY lol8 "&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;">
|
||||
<!ENTITY lol9 "&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;">
|
||||
]>
|
||||
<MyBean>&lol9;</MyBean>""";
|
||||
|
||||
MockHttpInputMessage inputMessage = new MockHttpInputMessage(body.getBytes(StandardCharsets.UTF_8));
|
||||
inputMessage.getHeaders().setContentType(MediaType.APPLICATION_XML);
|
||||
|
||||
assertThatExceptionOfType(HttpMessageNotReadableException.class).isThrownBy(() ->
|
||||
this.converter.read(MyBean.class, inputMessage));
|
||||
}
|
||||
|
||||
@Test
|
||||
void readNonUnicode() throws Exception {
|
||||
String body = "<MyBean>" +
|
||||
"<string>føø bår</string>" +
|
||||
"</MyBean>";
|
||||
|
||||
Charset charset = StandardCharsets.ISO_8859_1;
|
||||
MockHttpInputMessage inputMessage = new MockHttpInputMessage(body.getBytes(charset));
|
||||
inputMessage.getHeaders().setContentType(new MediaType("application", "xml", charset));
|
||||
MyBean result = (MyBean) converter.read(MyBean.class, inputMessage);
|
||||
assertThat(result.getString()).isEqualTo("føø bår");
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static class MyBean {
|
||||
|
||||
private String string;
|
||||
|
||||
private int number;
|
||||
|
||||
private float fraction;
|
||||
|
||||
private String[] array;
|
||||
|
||||
private boolean bool;
|
||||
|
||||
private byte[] bytes;
|
||||
|
||||
public byte[] getBytes() {
|
||||
return bytes;
|
||||
}
|
||||
|
||||
public void setBytes(byte[] bytes) {
|
||||
this.bytes = bytes;
|
||||
}
|
||||
|
||||
public boolean isBool() {
|
||||
return bool;
|
||||
}
|
||||
|
||||
public void setBool(boolean bool) {
|
||||
this.bool = bool;
|
||||
}
|
||||
|
||||
public String getString() {
|
||||
return string;
|
||||
}
|
||||
|
||||
public void setString(String string) {
|
||||
this.string = string;
|
||||
}
|
||||
|
||||
public int getNumber() {
|
||||
return number;
|
||||
}
|
||||
|
||||
public void setNumber(int number) {
|
||||
this.number = number;
|
||||
}
|
||||
|
||||
public float getFraction() {
|
||||
return fraction;
|
||||
}
|
||||
|
||||
public void setFraction(float fraction) {
|
||||
this.fraction = fraction;
|
||||
}
|
||||
|
||||
public String[] getArray() {
|
||||
return array;
|
||||
}
|
||||
|
||||
public void setArray(String[] array) {
|
||||
this.array = array;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private interface MyJacksonView1 {}
|
||||
|
||||
private interface MyJacksonView2 {}
|
||||
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static class JacksonViewBean {
|
||||
|
||||
@JsonView(MyJacksonView1.class)
|
||||
private String withView1;
|
||||
|
||||
@JsonView(MyJacksonView2.class)
|
||||
private String withView2;
|
||||
|
||||
private String withoutView;
|
||||
|
||||
public String getWithView1() {
|
||||
return withView1;
|
||||
}
|
||||
|
||||
public void setWithView1(String withView1) {
|
||||
this.withView1 = withView1;
|
||||
}
|
||||
|
||||
public String getWithView2() {
|
||||
return withView2;
|
||||
}
|
||||
|
||||
public void setWithView2(String withView2) {
|
||||
this.withView2 = withView2;
|
||||
}
|
||||
|
||||
public String getWithoutView() {
|
||||
return withoutView;
|
||||
}
|
||||
|
||||
public void setWithoutView(String withoutView) {
|
||||
this.withoutView = withoutView;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static class MyXmlMapper extends XmlMapper {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
/*
|
||||
* Copyright 2002-2025 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.http.converter.yaml;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import tools.jackson.dataformat.yaml.YAMLMapper;
|
||||
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.web.testfixture.http.MockHttpInputMessage;
|
||||
import org.springframework.web.testfixture.http.MockHttpOutputMessage;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.within;
|
||||
|
||||
/**
|
||||
* Jackson 3.x YAML converter tests.
|
||||
*
|
||||
* @author Sebastien Deleuze
|
||||
*/
|
||||
class JacksonYamlHttpMessageConverterTests {
|
||||
|
||||
private final JacksonYamlHttpMessageConverter converter = new JacksonYamlHttpMessageConverter();
|
||||
private final YAMLMapper mapper = YAMLMapper.builder().build();
|
||||
|
||||
|
||||
@Test
|
||||
void canRead() {
|
||||
assertThat(converter.canRead(MyBean.class, MediaType.APPLICATION_YAML)).isTrue();
|
||||
assertThat(converter.canRead(MyBean.class, new MediaType("application", "json"))).isFalse();
|
||||
assertThat(converter.canRead(MyBean.class, new MediaType("application", "xml"))).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void canWrite() {
|
||||
assertThat(converter.canWrite(MyBean.class, MediaType.APPLICATION_YAML)).isTrue();
|
||||
assertThat(converter.canWrite(MyBean.class, new MediaType("application", "json"))).isFalse();
|
||||
assertThat(converter.canWrite(MyBean.class, new MediaType("application", "xml"))).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void read() throws IOException {
|
||||
MyBean body = new MyBean();
|
||||
body.setString("Foo");
|
||||
body.setNumber(42);
|
||||
body.setFraction(42F);
|
||||
body.setArray(new String[]{"Foo", "Bar"});
|
||||
body.setBool(true);
|
||||
body.setBytes(new byte[]{0x1, 0x2});
|
||||
MockHttpInputMessage inputMessage = new MockHttpInputMessage(mapper.writeValueAsBytes(body));
|
||||
inputMessage.getHeaders().setContentType(MediaType.APPLICATION_YAML);
|
||||
MyBean result = (MyBean) converter.read(MyBean.class, inputMessage);
|
||||
assertThat(result.getString()).isEqualTo("Foo");
|
||||
assertThat(result.getNumber()).isEqualTo(42);
|
||||
assertThat(result.getFraction()).isCloseTo(42F, within(0F));
|
||||
|
||||
assertThat(result.getArray()).isEqualTo(new String[]{"Foo", "Bar"});
|
||||
assertThat(result.isBool()).isTrue();
|
||||
assertThat(result.getBytes()).isEqualTo(new byte[]{0x1, 0x2});
|
||||
}
|
||||
|
||||
@Test
|
||||
void write() throws IOException {
|
||||
MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
|
||||
MyBean body = new MyBean();
|
||||
body.setString("Foo");
|
||||
body.setNumber(42);
|
||||
body.setFraction(42F);
|
||||
body.setArray(new String[]{"Foo", "Bar"});
|
||||
body.setBool(true);
|
||||
body.setBytes(new byte[]{0x1, 0x2});
|
||||
converter.write(body, null, outputMessage);
|
||||
assertThat(outputMessage.getBodyAsBytes()).isEqualTo(mapper.writeValueAsBytes(body));
|
||||
assertThat(outputMessage.getHeaders().getContentType())
|
||||
.as("Invalid content-type").isEqualTo(MediaType.APPLICATION_YAML);
|
||||
}
|
||||
|
||||
|
||||
public static class MyBean {
|
||||
|
||||
private String string;
|
||||
|
||||
private int number;
|
||||
|
||||
private float fraction;
|
||||
|
||||
private String[] array;
|
||||
|
||||
private boolean bool;
|
||||
|
||||
private byte[] bytes;
|
||||
|
||||
public byte[] getBytes() {
|
||||
return bytes;
|
||||
}
|
||||
|
||||
public void setBytes(byte[] bytes) {
|
||||
this.bytes = bytes;
|
||||
}
|
||||
|
||||
public boolean isBool() {
|
||||
return bool;
|
||||
}
|
||||
|
||||
public void setBool(boolean bool) {
|
||||
this.bool = bool;
|
||||
}
|
||||
|
||||
public String getString() {
|
||||
return string;
|
||||
}
|
||||
|
||||
public void setString(String string) {
|
||||
this.string = string;
|
||||
}
|
||||
|
||||
public int getNumber() {
|
||||
return number;
|
||||
}
|
||||
|
||||
public void setNumber(int number) {
|
||||
this.number = number;
|
||||
}
|
||||
|
||||
public float getFraction() {
|
||||
return fraction;
|
||||
}
|
||||
|
||||
public void setFraction(float fraction) {
|
||||
this.fraction = fraction;
|
||||
}
|
||||
|
||||
public String[] getArray() {
|
||||
return array;
|
||||
}
|
||||
|
||||
public void setArray(String[] array) {
|
||||
this.array = array;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -522,7 +522,7 @@ class RestClientIntegrationTests {
|
||||
expectRequestCount(1);
|
||||
expectRequest(request -> {
|
||||
assertThat(request.getPath()).isEqualTo("/pojo/capitalize");
|
||||
assertThat(request.getBody().readUtf8()).isEqualTo("{\"foo\":\"foofoo\",\"bar\":\"barbar\"}");
|
||||
assertThat(request.getBody().readUtf8()).isEqualTo("{\"bar\":\"barbar\",\"foo\":\"foofoo\"}");
|
||||
assertThat(request.getHeader(HttpHeaders.ACCEPT)).isEqualTo("application/json");
|
||||
assertThat(request.getHeader(HttpHeaders.CONTENT_TYPE)).isEqualTo("application/json");
|
||||
});
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -30,6 +30,7 @@ import java.util.stream.Stream;
|
||||
import com.fasterxml.jackson.annotation.JsonTypeInfo;
|
||||
import com.fasterxml.jackson.annotation.JsonTypeName;
|
||||
import com.fasterxml.jackson.annotation.JsonView;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.extension.RegisterExtension;
|
||||
import org.junit.jupiter.api.extension.TestExecutionExceptionHandler;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
@@ -407,7 +408,8 @@ class RestTemplateIntegrationTests extends AbstractMockWebServerTests {
|
||||
}
|
||||
|
||||
@ParameterizedRestTemplateTest
|
||||
void jsonPostForObjectWithJacksonView(ClientHttpRequestFactory clientHttpRequestFactory) {
|
||||
@Disabled("Use RestClient + upcoming hint management instead")
|
||||
void jsonPostForObjectWithJacksonJsonView(ClientHttpRequestFactory clientHttpRequestFactory) {
|
||||
setUpClient(clientHttpRequestFactory);
|
||||
|
||||
HttpHeaders entityHeaders = new HttpHeaders();
|
||||
|
||||
@@ -53,8 +53,8 @@ import org.springframework.http.converter.GenericHttpMessageConverter;
|
||||
import org.springframework.http.converter.HttpMessageConverter;
|
||||
import org.springframework.http.converter.SmartHttpMessageConverter;
|
||||
import org.springframework.http.converter.StringHttpMessageConverter;
|
||||
import org.springframework.http.converter.json.JacksonJsonHttpMessageConverter;
|
||||
import org.springframework.http.converter.json.KotlinSerializationJsonHttpMessageConverter;
|
||||
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
|
||||
import org.springframework.util.FileCopyUtils;
|
||||
import org.springframework.web.util.DefaultUriBuilderFactory;
|
||||
|
||||
@@ -113,7 +113,7 @@ class RestTemplateTests {
|
||||
RestTemplate restTemplate = new RestTemplate();
|
||||
List<HttpMessageConverter<?>> httpMessageConverters = restTemplate.getMessageConverters();
|
||||
assertThat(httpMessageConverters).extracting("class").containsOnlyOnce(
|
||||
MappingJackson2HttpMessageConverter.class
|
||||
JacksonJsonHttpMessageConverter.class
|
||||
);
|
||||
assertThat(httpMessageConverters).extracting("class").doesNotContain(
|
||||
KotlinSerializationJsonHttpMessageConverter.class
|
||||
|
||||
Reference in New Issue
Block a user