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
This commit is contained in:
parent
a3f3de223f
commit
b260c20df0
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -0,0 +1,71 @@
|
|||
/*
|
||||
* 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">
|
||||
* @JsonComponent
|
||||
* public class CustomerJsonComponent {
|
||||
*
|
||||
* public static class Serializer extends JsonSerializer<Customer> {
|
||||
*
|
||||
* // ...
|
||||
*
|
||||
* }
|
||||
*
|
||||
* public static class Deserializer extends JsonDeserializer<Customer> {
|
||||
*
|
||||
* // ...
|
||||
*
|
||||
* }
|
||||
*
|
||||
* }
|
||||
*
|
||||
* </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 "";
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,107 @@
|
|||
/*
|
||||
* 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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,138 @@
|
|||
/*
|
||||
* 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
/*
|
||||
* 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;
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* 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;
|
||||
|
|
@ -0,0 +1,88 @@
|
|||
/*
|
||||
* 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 {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,216 @@
|
|||
/*
|
||||
* 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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* 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}");
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* 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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
Reference in New Issue