Commit b260c20d authored by Phillip Webb's avatar Phillip Webb

Support JsonSerializer/JsonDeserializer Beans

Add a `@JsonComponent` annotation that can be used to indicate that a
Bean is a JsonSerializer and/or JsonDeserializer that should be
registered with Jackson.

Support is provide via a new `JsonComponentModule` which is
auto-configured in `JacksonAutoConfiguration`.

Also add `JsonObjectSerializer` and `JsonObjectDeserializer` classes
which provided as a convenient base for serializers that work with
Objects.

Fixes gh-3898
parent a3f3de22
......@@ -46,6 +46,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnJava;
import org.springframework.boot.autoconfigure.condition.ConditionalOnJava.JavaVersion;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.jackson.JsonComponentModule;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
......@@ -75,6 +76,11 @@ import org.springframework.util.ReflectionUtils;
@ConditionalOnClass(ObjectMapper.class)
public class JacksonAutoConfiguration {
@Bean
public JsonComponentModule jsonComponentModule() {
return new JsonComponentModule();
}
@Configuration
@ConditionalOnClass({ ObjectMapper.class, Jackson2ObjectMapperBuilder.class })
static class JacksonObjectMapperConfiguration {
......
......@@ -50,10 +50,13 @@ import org.junit.Before;
import org.junit.Test;
import org.springframework.boot.autoconfigure.web.HttpMessageConvertersAutoConfiguration;
import org.springframework.boot.jackson.JsonComponent;
import org.springframework.boot.jackson.JsonObjectSerializer;
import org.springframework.boot.test.EnvironmentTestUtils;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.Primary;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
......@@ -347,6 +350,7 @@ public class JacksonAutoConfigurationTests {
assertThat(this.context.getBean(CustomModule.class).getOwners())
.contains((ObjectCodec) objectMapper);
assertThat(objectMapper.canSerialize(LocalDateTime.class)).isTrue();
assertThat(objectMapper.canSerialize(Baz.class)).isTrue();
}
@Test
......@@ -456,12 +460,14 @@ public class JacksonAutoConfigurationTests {
}
@Configuration
@Import(BazSerializer.class)
protected static class ModuleConfig {
@Bean
public CustomModule jacksonModule() {
return new CustomModule();
}
}
@Configuration
......@@ -538,6 +544,20 @@ public class JacksonAutoConfigurationTests {
}
}
@JsonComponent
private static class BazSerializer extends JsonObjectSerializer<Baz> {
@Override
protected void serializeObject(Baz value, JsonGenerator jgen,
SerializerProvider provider) throws IOException {
}
}
private static class Baz {
}
private static class CustomModule extends SimpleModule {
private Set<ObjectCodec> owners = new HashSet<ObjectCodec>();
......@@ -552,4 +572,5 @@ public class JacksonAutoConfigurationTests {
}
}
}
......@@ -1470,6 +1470,51 @@ converters. You can also override default converters that way.
[[boot-features-json-components]]
==== Custom JSON Serializers and Deserializers
If you're using Jackson to serialize and deserialize JSON data, you might want to write
your own `JsonSerializer` and `JsonDeserializer` classes. Custom serializers are usually
http://wiki.fasterxml.com/JacksonHowToCustomDeserializers[registered with Jackson via a Module],
but Spring Boot provides an alternative `@JsonComponent` annotation which makes it easier
to directly register Spring Beans.
You can use `@JsonComponent` directly on `JsonSerializer` or `JsonDeserializer`
implementations. You can also use it on classes that contains serializers/deserializers as
inner-classes. For example:
[source,java,indent=0]
----
import java.io.*;
import com.fasterxml.jackson.core.*;
import com.fasterxml.jackson.databind.*;
import org.springframework.boot.jackson.*;
@JsonComponent
public class Example {
public static class Serializer extends JsonSerializer<SomeObject> {
// ...
}
public static class Deserializer extends JsonDeserializer<SomeObject> {
// ...
}
}
----
All `@JsonComponent` beans in the `ApplicationContext` will be automatically registered with
Jackson, and since `@JsonComponent` is meta-annotated with `@Component`, the usual
component-scanning rules apply.
Spring Boot also provides
{sc-spring-boot}/jackson/JsonObjectSerializer.{sc-ext}[`JsonObjectSerializer`] and
{sc-spring-boot}/jackson/JsonObjectDeserializer.{sc-ext}[`JsonObjectDeserializer`] base
classes which provide useful alternatives to the standard Jackson versions when
serializing Objects. See the Javadoc for details.
[[boot-features-spring-message-codes]]
==== MessageCodesResolver
Spring MVC has a strategy for generating error codes for rendering error messages
......
/*
* Copyright 2012-2016 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
*
* http://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.boot.jackson;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonSerializer;
import org.springframework.stereotype.Component;
/**
* {@link Component} that provides {@link JsonSerializer} and/or {@link JsonDeserializer}
* implementations to be registered with Jackson when {@link JsonComponentModule} is in
* use. Can be used to annotate {@link JsonSerializer} or {@link JsonDeserializer}
* implementations directly or a class that contains them as inner-classes. For example:
* <pre class="code">
* &#064;JsonComponent
* public class CustomerJsonComponent {
*
* public static class Serializer extends JsonSerializer&lt;Customer&gt; {
*
* // ...
*
* }
*
* public static class Deserializer extends JsonDeserializer&lt;Customer&gt; {
*
* // ...
*
* }
*
* }
*
* </pre>
*
* @see JsonComponentModule
* @since 1.4.0
* @author Phillip Webb
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface JsonComponent {
/**
* The value may indicate a suggestion for a logical component name, to be turned into
* a Spring bean in case of an autodetected component.
*/
String value() default "";
}
/*
* Copyright 2012-2016 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
*
* http://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.boot.jackson;
import java.util.Map;
import javax.annotation.PostConstruct;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.Module;
import com.fasterxml.jackson.databind.module.SimpleModule;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.HierarchicalBeanFactory;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.core.ResolvableType;
/**
* Spring Bean and Jackson {@link Module} to register {@link JsonComponent} annotated
* beans.
*
* @author Phillip Webb
* @since 1.4.0
* @see JsonComponent
*/
public class JsonComponentModule extends SimpleModule implements BeanFactoryAware {
private BeanFactory beanFactory;
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory;
}
@PostConstruct
public void registerJsonComponents() {
BeanFactory beanFactory = this.beanFactory;
while (beanFactory != null) {
if (beanFactory instanceof ListableBeanFactory) {
addJsonBeans((ListableBeanFactory) beanFactory);
}
beanFactory = (beanFactory instanceof HierarchicalBeanFactory
? ((HierarchicalBeanFactory) beanFactory).getParentBeanFactory()
: null);
}
}
private void addJsonBeans(ListableBeanFactory beanFactory) {
Map<String, Object> beans = beanFactory
.getBeansWithAnnotation(JsonComponent.class);
for (Object bean : beans.values()) {
addJsonBean(bean);
}
}
private void addJsonBean(Object bean) {
if (bean instanceof JsonSerializer) {
addSerializerWithDeducedType((JsonSerializer<?>) bean);
}
if (bean instanceof JsonDeserializer) {
addDeserializerWithDeducedType((JsonDeserializer<?>) bean);
}
for (Class<?> innerClass : bean.getClass().getDeclaredClasses()) {
if (JsonSerializer.class.isAssignableFrom(innerClass)
|| JsonDeserializer.class.isAssignableFrom(innerClass)) {
try {
addJsonBean(innerClass.newInstance());
}
catch (Exception ex) {
throw new IllegalStateException(ex);
}
}
}
}
@SuppressWarnings({ "unchecked" })
private <T> void addSerializerWithDeducedType(JsonSerializer<T> serializer) {
ResolvableType type = ResolvableType.forClass(JsonSerializer.class,
serializer.getClass());
addSerializer((Class<T>) type.resolveGeneric(), serializer);
}
@SuppressWarnings({ "unchecked" })
private <T> void addDeserializerWithDeducedType(JsonDeserializer<T> deserializer) {
ResolvableType type = ResolvableType.forClass(JsonDeserializer.class,
deserializer.getClass());
addDeserializer((Class<T>) type.resolveGeneric(), deserializer);
}
}
/*
* Copyright 2012-2016 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
*
* http://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.boot.jackson;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.BigInteger;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.ObjectCodec;
import com.fasterxml.jackson.core.TreeNode;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.NullNode;
import org.springframework.util.Assert;
/**
* Helper base class for {@link JsonDeserializer} implementations that deserialize
* objects.
*
* @param <T> The supported object type
* @author Phillip Webb
* @since 1.4.0
* @see JsonObjectSerializer
*/
public abstract class JsonObjectDeserializer<T>
extends com.fasterxml.jackson.databind.JsonDeserializer<T> {
@Override
public final T deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException {
try {
ObjectCodec codec = jp.getCodec();
JsonNode tree = codec.readTree(jp);
return deserializeObject(jp, ctxt, codec, tree);
}
catch (Exception ex) {
if (ex instanceof IOException) {
throw (IOException) ex;
}
throw new JsonMappingException(jp, "Object deserialize error", ex);
}
}
/**
* Deserialize JSON content into the value type this serializer handles.
* @param jasonParser the source parser used for reading JSON content
* @param context Context that can be used to access information about this
* deserialization activity
* @param codec the {@link ObjectCodec} associated with the parser
* @param tree deserialized JSON content as tree expressed using set of
* {@link TreeNode} instances
* @return the dserialized object
* @throws IOException on error
* @see #deserialize(JsonParser, DeserializationContext)
*/
protected abstract T deserializeObject(JsonParser jasonParser,
DeserializationContext context, ObjectCodec codec, JsonNode tree)
throws IOException;
/**
* Helper method to extract a value from the given jsonNode or return {@code null}
* when the node itself is {@code null}.
* @param jsonNode the source node (may be {@code null}
* @param type the data type. May be {@link String}, {@link Boolean}, {@link Long},
* {@link Integer}, {@link Short}, {@link Double}, {@link Float}, {@link BigDecimal}
* or {@link BigInteger}.
* @param <D> the data type requested
* @return the node value or {@code null}
*/
@SuppressWarnings({ "unchecked" })
protected final <D> D nullSafeValue(JsonNode jsonNode, Class<D> type) {
Assert.notNull(type, "Type must not be null");
if (jsonNode == null) {
return null;
}
if (type == String.class) {
return (D) jsonNode.textValue();
}
if (type == Boolean.class) {
return (D) Boolean.valueOf(jsonNode.booleanValue());
}
if (type == Long.class) {
return (D) Long.valueOf(jsonNode.longValue());
}
if (type == Integer.class) {
return (D) Integer.valueOf(jsonNode.intValue());
}
if (type == Short.class) {
return (D) Short.valueOf(jsonNode.shortValue());
}
if (type == Double.class) {
return (D) Double.valueOf(jsonNode.doubleValue());
}
if (type == Float.class) {
return (D) Float.valueOf(jsonNode.floatValue());
}
if (type == BigDecimal.class) {
return (D) jsonNode.decimalValue();
}
if (type == BigInteger.class) {
return (D) jsonNode.bigIntegerValue();
}
throw new IllegalArgumentException("Unsupported value type " + type.getName());
}
/**
* Helper method to return a {@link JsonNode} from the tree.
* @param tree the source tree
* @param fieldName the field name to extract
* @return the {@link JsonNode}
*/
protected final JsonNode getRequiredNode(JsonNode tree, String fieldName) {
Assert.notNull(tree, "Tree must not be null");
JsonNode node = tree.get(fieldName);
Assert.state(node != null && !(node instanceof NullNode),
"Missing JSON field '" + fieldName + "'");
return node;
}
}
/*
* Copyright 2012-2016 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
*
* http://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.boot.jackson;
import java.io.IOException;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
/**
* Helper base class for {@link JsonSerializer} implementations that serialize objects.
*
* @param <T> the supported object type
* @author Phillip Webb
* @since 1.4.0
* @see JsonObjectDeserializer
*/
public abstract class JsonObjectSerializer<T> extends JsonSerializer<T> {
@Override
public final void serialize(T value, JsonGenerator jgen, SerializerProvider provider)
throws IOException {
try {
jgen.writeStartObject();
serializeObject(value, jgen, provider);
jgen.writeEndObject();
}
catch (Exception ex) {
if (ex instanceof IOException) {
throw (IOException) ex;
}
throw new JsonMappingException(jgen, "Object serialize error", ex);
}
}
/**
* Serialize JSON content into the value type this serializer handles.
* @param value the source value
* @param jgen the JSON generator
* @param provider the serializer provider
* @throws IOException on error
*/
protected abstract void serializeObject(T value, JsonGenerator jgen,
SerializerProvider provider) throws IOException;
}
/*
* Copyright 2012-2016 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
*
* http://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.
*/
/**
* Custom enhancements and support for the Jackson Project.
*/
package org.springframework.boot.jackson;
/*
* Copyright 2012-2016 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
*
* http://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.boot.jackson;
import com.fasterxml.jackson.databind.Module;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link JsonComponentModule}.
*
* @author Phillip Webb
*/
public class JsonComponentModuleTests {
@Test
public void moduleShouldRegisterSerializers() throws Exception {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
JsonComponentModule.class, OnlySerializer.class);
JsonComponentModule module = context.getBean(JsonComponentModule.class);
assertSerialize(module);
context.close();
}
@Test
public void moduleShouldRegisterDeserializers() throws Exception {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
JsonComponentModule.class, OnlyDeserializer.class);
JsonComponentModule module = context.getBean(JsonComponentModule.class);
assertDeserialize(module);
context.close();
}
@Test
public void moduleShouldRegisterInnerClasses() throws Exception {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
JsonComponentModule.class, NameAndAgeJsonComponent.class);
JsonComponentModule module = context.getBean(JsonComponentModule.class);
assertSerialize(module);
assertDeserialize(module);
context.close();
}
private void assertSerialize(Module module) throws Exception {
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(module);
String json = mapper.writeValueAsString(new NameAndAge("spring", 100));
assertThat(json).isEqualToIgnoringWhitespace("{\"name\":\"spring\",\"age\":100}");
}
private void assertDeserialize(Module module) throws Exception {
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(module);
NameAndAge nameAndAge = mapper.readValue("{\"name\":\"spring\",\"age\":100}",
NameAndAge.class);
assertThat(nameAndAge.getName()).isEqualTo("spring");
assertThat(nameAndAge.getAge()).isEqualTo(100);
}
@JsonComponent
static class OnlySerializer extends NameAndAgeJsonComponent.Serializer {
}
@JsonComponent
static class OnlyDeserializer extends NameAndAgeJsonComponent.Deserializer {
}
}
/*
* Copyright 2012-2016 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
*
* http://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.boot.jackson;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigDecimal;
import java.math.BigInteger;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.ObjectCodec;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.node.NullNode;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.springframework.boot.jackson.NameAndAgeJsonComponent.Deserializer;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link JsonObjectDeserializer}.
*
* @author Phillip Webb
*/
public class JsonObjectDeserializerTests {
@Rule
public ExpectedException thrown = ExpectedException.none();
private TestJsonObjectDeserializer<Object> testDeserializer = new TestJsonObjectDeserializer<Object>();
@Test
public void deserializeObjectShouldReadJson() throws Exception {
Deserializer deserializer = new NameAndAgeJsonComponent.Deserializer();
SimpleModule module = new SimpleModule();
module.addDeserializer(NameAndAge.class, deserializer);
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(module);
NameAndAge nameAndAge = mapper.readValue("{\"name\":\"spring\",\"age\":100}",
NameAndAge.class);
assertThat(nameAndAge.getName()).isEqualTo("spring");
assertThat(nameAndAge.getAge()).isEqualTo(100);
}
@Test
public void nullSafeValueWhenValueIsNullShouldReturnNull() throws Exception {
String value = this.testDeserializer.testNullSafeValue(null, String.class);
assertThat(value).isNull();
}
@Test
public void nullSafeValueWhenClassIsNullShouldThrowException() throws Exception {
this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage("Type must not be null");
this.testDeserializer.testNullSafeValue(mock(JsonNode.class), null);
}
@Test
public void nullSafeValueWhenClassIsStringShouldReturnString() throws Exception {
JsonNode node = mock(JsonNode.class);
given(node.textValue()).willReturn("abc");
String value = this.testDeserializer.testNullSafeValue(node, String.class);
assertThat(value).isEqualTo("abc");
}
@Test
public void nullSafeValueWhenClassIsBooleanShouldReturnBoolean() throws Exception {
JsonNode node = mock(JsonNode.class);
given(node.booleanValue()).willReturn(true);
Boolean value = this.testDeserializer.testNullSafeValue(node, Boolean.class);
assertThat(value).isTrue();
}
@Test
public void nullSafeValueWhenClassIsLongShouldReturnLong() throws Exception {
JsonNode node = mock(JsonNode.class);
given(node.longValue()).willReturn(10L);
Long value = this.testDeserializer.testNullSafeValue(node, Long.class);
assertThat(value).isEqualTo(10L);
}
@Test
public void nullSafeValueWhenClassIsIntegerShouldReturnInteger() throws Exception {
JsonNode node = mock(JsonNode.class);
given(node.intValue()).willReturn(10);
Integer value = this.testDeserializer.testNullSafeValue(node, Integer.class);
assertThat(value).isEqualTo(10);
}
@Test
public void nullSafeValueWhenClassIsShortShouldReturnShort() throws Exception {
JsonNode node = mock(JsonNode.class);
given(node.shortValue()).willReturn((short) 10);
Short value = this.testDeserializer.testNullSafeValue(node, Short.class);
assertThat(value).isEqualTo((short) 10);
}
@Test
public void nullSafeValueWhenClassIsDoubleShouldReturnDouble() throws Exception {
JsonNode node = mock(JsonNode.class);
given(node.doubleValue()).willReturn(1.1D);
Double value = this.testDeserializer.testNullSafeValue(node, Double.class);
assertThat(value).isEqualTo(1.1D);
}
@Test
public void nullSafeValueWhenClassIsFloatShouldReturnFloat() throws Exception {
JsonNode node = mock(JsonNode.class);
given(node.floatValue()).willReturn(1.1F);
Float value = this.testDeserializer.testNullSafeValue(node, Float.class);
assertThat(value).isEqualTo(1.1F);
}
@Test
public void nullSafeValueWhenClassIsShouldBigDecimalReturnBigDecimal()
throws Exception {
JsonNode node = mock(JsonNode.class);
given(node.decimalValue()).willReturn(BigDecimal.TEN);
BigDecimal value = this.testDeserializer.testNullSafeValue(node,
BigDecimal.class);
assertThat(value).isEqualTo(BigDecimal.TEN);
}
@Test
public void nullSafeValueWhenClassIsBigIntegerShouldReturnBigInteger()
throws Exception {
JsonNode node = mock(JsonNode.class);
given(node.bigIntegerValue()).willReturn(BigInteger.TEN);
BigInteger value = this.testDeserializer.testNullSafeValue(node,
BigInteger.class);
assertThat(value).isEqualTo(BigInteger.TEN);
}
@Test
public void nullSafeValueWhenClassIsUnknownShouldThrowException() throws Exception {
this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage("Unsupported value type java.io.InputStream");
this.testDeserializer.testNullSafeValue(mock(JsonNode.class), InputStream.class);
}
@Test
public void getRequiredNodeWhenTreeIsNullShouldThrowException() throws Exception {
this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage("Tree must not be null");
this.testDeserializer.testGetRequiredNode(null, "test");
}
@Test
public void getRequiredNodeWhenNodeIsNullShouldThrowException() throws Exception {
JsonNode tree = mock(JsonNode.class);
given(tree.get("test")).willReturn(null);
this.thrown.expect(IllegalStateException.class);
this.thrown.expectMessage("Missing JSON field 'test'");
this.testDeserializer.testGetRequiredNode(tree, "test");
}
@Test
public void getRequiredNodeWhenNodeIsNullNodeShouldThrowException() throws Exception {
JsonNode tree = mock(JsonNode.class);
given(tree.get("test")).willReturn(NullNode.instance);
this.thrown.expect(IllegalStateException.class);
this.thrown.expectMessage("Missing JSON field 'test'");
this.testDeserializer.testGetRequiredNode(tree, "test");
}
@Test
public void getRequiredNodeWhenNodeIsFoundShouldReturnNode() throws Exception {
JsonNode node = mock(JsonNode.class);
JsonNode tree = node;
given(tree.get("test")).willReturn(node);
assertThat(this.testDeserializer.testGetRequiredNode(tree, "test"))
.isEqualTo(node);
}
static class TestJsonObjectDeserializer<T> extends JsonObjectDeserializer<T> {
@Override
protected T deserializeObject(JsonParser jasonParser,
DeserializationContext context, ObjectCodec codec, JsonNode tree)
throws IOException {
return null;
}
public <D> D testNullSafeValue(JsonNode jsonNode, Class<D> type) {
return nullSafeValue(jsonNode, type);
}
public JsonNode testGetRequiredNode(JsonNode tree, String fieldName) {
return getRequiredNode(tree, fieldName);
}
}
}
/*
* Copyright 2012-2016 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
*
* http://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.boot.jackson;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import org.junit.Test;
import org.springframework.boot.jackson.NameAndAgeJsonComponent.Serializer;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link JsonObjectSerializer}.
*
* @author Phillip Webb
*/
public class JsonObjectSerializerTests {
@Test
public void serializeObjectShouldWriteJson() throws Exception {
Serializer serializer = new NameAndAgeJsonComponent.Serializer();
SimpleModule module = new SimpleModule();
module.addSerializer(NameAndAge.class, serializer);
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(module);
String json = mapper.writeValueAsString(new NameAndAge("spring", 100));
assertThat(json).isEqualToIgnoringWhitespace("{\"name\":\"spring\",\"age\":100}");
}
}
/*
* Copyright 2012-2016 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
*
* http://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.boot.jackson;
/**
* Sample object used for tests.
*
* @author Phillip Webb
*/
public final class NameAndAge {
private final String name;
private final int age;
public NameAndAge(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return this.name;
}
public int getAge() {
return this.age;
}
}
/*
* Copyright 2012-2016 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
*
* http://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.boot.jackson;
import java.io.IOException;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.ObjectCodec;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.SerializerProvider;
/**
* Sample {@link JsonComponent} used for tests.
*
* @author Phillip Webb
*/
@JsonComponent
public class NameAndAgeJsonComponent {
public static class Serializer extends JsonObjectSerializer<NameAndAge> {
@Override
protected void serializeObject(NameAndAge value, JsonGenerator jgen,
SerializerProvider provider) throws IOException {
jgen.writeStringField("name", value.getName());
jgen.writeNumberField("age", value.getAge());
}
}
public static class Deserializer extends JsonObjectDeserializer<NameAndAge> {
@Override
protected NameAndAge deserializeObject(JsonParser jasonParser,
DeserializationContext context, ObjectCodec codec, JsonNode tree)
throws IOException {
String name = nullSafeValue(tree.get("name"), String.class);
Integer age = nullSafeValue(tree.get("age"), Integer.class);
return new NameAndAge(name, age);
}
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment