Introduce JacksonObjectReader and JacksonObjectWriter function interfaces to customize JSON (de)serialization.
We now encapsulate serialization and deserialization operations as JacksonObjectWriter and JacksonObjectReader functions to allow customization of Jackson serialization. Closes: #2322 Original Pull Request: #2332
This commit is contained in:
committed by
Christoph Strobl
parent
862decfdbe
commit
eb7dfbc7ef
@@ -25,7 +25,6 @@ import org.springframework.util.StringUtils;
|
||||
import com.fasterxml.jackson.annotation.JsonTypeInfo;
|
||||
import com.fasterxml.jackson.annotation.JsonTypeInfo.As;
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper.DefaultTyping;
|
||||
import com.fasterxml.jackson.databind.SerializerProvider;
|
||||
@@ -37,6 +36,9 @@ import com.fasterxml.jackson.databind.ser.std.StdSerializer;
|
||||
|
||||
/**
|
||||
* Generic Jackson 2-based {@link RedisSerializer} that maps {@link Object objects} to JSON using dynamic typing.
|
||||
* <p>
|
||||
* JSON reading and writing can be customized by configuring {@link JacksonObjectReader} respective
|
||||
* {@link JacksonObjectWriter}.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @author Mark Paluch
|
||||
@@ -47,6 +49,10 @@ public class GenericJackson2JsonRedisSerializer implements RedisSerializer<Objec
|
||||
|
||||
private final ObjectMapper mapper;
|
||||
|
||||
private final JacksonObjectReader reader;
|
||||
|
||||
private final JacksonObjectWriter writer;
|
||||
|
||||
/**
|
||||
* Creates {@link GenericJackson2JsonRedisSerializer} and configures {@link ObjectMapper} for default typing.
|
||||
*/
|
||||
@@ -59,13 +65,30 @@ public class GenericJackson2JsonRedisSerializer implements RedisSerializer<Objec
|
||||
* given {@literal name}. In case of an {@literal empty} or {@literal null} String the default
|
||||
* {@link JsonTypeInfo.Id#CLASS} will be used.
|
||||
*
|
||||
* @param classPropertyTypeName Name of the JSON property holding type information. Can be {@literal null}.
|
||||
* @param classPropertyTypeName name of the JSON property holding type information. Can be {@literal null}.
|
||||
* @see ObjectMapper#activateDefaultTypingAsProperty(PolymorphicTypeValidator, DefaultTyping, String)
|
||||
* @see ObjectMapper#activateDefaultTyping(PolymorphicTypeValidator, DefaultTyping, As)
|
||||
*/
|
||||
public GenericJackson2JsonRedisSerializer(@Nullable String classPropertyTypeName) {
|
||||
this(classPropertyTypeName, JacksonObjectReader.create(), JacksonObjectWriter.create());
|
||||
}
|
||||
|
||||
this(new ObjectMapper());
|
||||
/**
|
||||
* Creates {@link GenericJackson2JsonRedisSerializer} and configures {@link ObjectMapper} for default typing using the
|
||||
* given {@literal name}. In case of an {@literal empty} or {@literal null} String the default
|
||||
* {@link JsonTypeInfo.Id#CLASS} will be used.
|
||||
*
|
||||
* @param classPropertyTypeName name of the JSON property holding type information. Can be {@literal null}.
|
||||
* @param reader the {@link JacksonObjectReader} function to read objects using {@link ObjectMapper}.
|
||||
* @param writer the {@link JacksonObjectWriter} function to write objects using {@link ObjectMapper}.
|
||||
* @see ObjectMapper#activateDefaultTypingAsProperty(PolymorphicTypeValidator, DefaultTyping, String)
|
||||
* @see ObjectMapper#activateDefaultTyping(PolymorphicTypeValidator, DefaultTyping, As)
|
||||
* @since 3.0
|
||||
*/
|
||||
public GenericJackson2JsonRedisSerializer(@Nullable String classPropertyTypeName, JacksonObjectReader reader,
|
||||
JacksonObjectWriter writer) {
|
||||
|
||||
this(new ObjectMapper(), reader, writer);
|
||||
|
||||
// simply setting {@code mapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS)} does not help here since we need
|
||||
// the type hint embedded for deserialization using the default typing feature.
|
||||
@@ -87,9 +110,29 @@ public class GenericJackson2JsonRedisSerializer implements RedisSerializer<Objec
|
||||
* @param mapper must not be {@literal null}.
|
||||
*/
|
||||
public GenericJackson2JsonRedisSerializer(ObjectMapper mapper) {
|
||||
this(mapper, JacksonObjectReader.create(), JacksonObjectWriter.create());
|
||||
}
|
||||
|
||||
/**
|
||||
* Setting a custom-configured {@link ObjectMapper} is one way to take further control of the JSON serialization
|
||||
* process. For example, an extended {@link SerializerFactory} can be configured that provides custom serializers for
|
||||
* specific types.
|
||||
*
|
||||
* @param mapper must not be {@literal null}.
|
||||
* @param reader the {@link JacksonObjectReader} function to read objects using {@link ObjectMapper}.
|
||||
* @param writer the {@link JacksonObjectWriter} function to write objects using {@link ObjectMapper}.
|
||||
* @since 3.0
|
||||
*/
|
||||
public GenericJackson2JsonRedisSerializer(ObjectMapper mapper, JacksonObjectReader reader,
|
||||
JacksonObjectWriter writer) {
|
||||
|
||||
Assert.notNull(mapper, "ObjectMapper must not be null");
|
||||
Assert.notNull(reader, "Reader must not be null");
|
||||
Assert.notNull(writer, "Writer must not be null");
|
||||
|
||||
this.mapper = mapper;
|
||||
this.reader = reader;
|
||||
this.writer = writer;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -116,8 +159,8 @@ public class GenericJackson2JsonRedisSerializer implements RedisSerializer<Objec
|
||||
}
|
||||
|
||||
try {
|
||||
return mapper.writeValueAsBytes(source);
|
||||
} catch (JsonProcessingException e) {
|
||||
return writer.write(mapper, source);
|
||||
} catch (IOException e) {
|
||||
throw new SerializationException("Could not write JSON: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
@@ -134,6 +177,7 @@ public class GenericJackson2JsonRedisSerializer implements RedisSerializer<Objec
|
||||
* @throws SerializationException
|
||||
*/
|
||||
@Nullable
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T deserialize(@Nullable byte[] source, Class<T> type) throws SerializationException {
|
||||
|
||||
Assert.notNull(type,
|
||||
@@ -144,7 +188,7 @@ public class GenericJackson2JsonRedisSerializer implements RedisSerializer<Objec
|
||||
}
|
||||
|
||||
try {
|
||||
return mapper.readValue(source, type);
|
||||
return (T) reader.read(mapper, source, mapper.getTypeFactory().constructType(type));
|
||||
} catch (Exception ex) {
|
||||
throw new SerializationException("Could not read JSON: " + ex.getMessage(), ex);
|
||||
}
|
||||
@@ -172,8 +216,7 @@ public class GenericJackson2JsonRedisSerializer implements RedisSerializer<Objec
|
||||
}
|
||||
|
||||
@Override
|
||||
public void serialize(NullValue value, JsonGenerator jgen, SerializerProvider provider)
|
||||
throws IOException {
|
||||
public void serialize(NullValue value, JsonGenerator jgen, SerializerProvider provider) throws IOException {
|
||||
|
||||
jgen.writeStartObject();
|
||||
jgen.writeStringField(classIdentifier, NullValue.class.getName());
|
||||
@@ -186,4 +229,5 @@ public class GenericJackson2JsonRedisSerializer implements RedisSerializer<Objec
|
||||
serialize(value, gen, serializers);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -31,10 +31,14 @@ import com.fasterxml.jackson.databind.type.TypeFactory;
|
||||
* <a href="https://github.com/FasterXML/jackson-core">Jackson's</a> and
|
||||
* <a href="https://github.com/FasterXML/jackson-databind">Jackson Databind</a> {@link ObjectMapper}.
|
||||
* <p>
|
||||
* This converter can be used to bind to typed beans, or untyped {@link java.util.HashMap HashMap} instances.
|
||||
* This serializer can be used to bind to typed beans, or untyped {@link java.util.HashMap HashMap} instances.
|
||||
* <b>Note:</b>Null objects are serialized as empty arrays and vice versa.
|
||||
* <p>
|
||||
* JSON reading and writing can be customized by configuring {@link JacksonObjectReader} respective
|
||||
* {@link JacksonObjectWriter}.
|
||||
*
|
||||
* @author Thomas Darimont
|
||||
* @author Mark Paluch
|
||||
* @since 1.2
|
||||
*/
|
||||
public class Jackson2JsonRedisSerializer<T> implements RedisSerializer<T> {
|
||||
@@ -45,6 +49,10 @@ public class Jackson2JsonRedisSerializer<T> implements RedisSerializer<T> {
|
||||
|
||||
private ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
private JacksonObjectReader reader = JacksonObjectReader.create();
|
||||
|
||||
private JacksonObjectWriter writer = JacksonObjectWriter.create();
|
||||
|
||||
/**
|
||||
* Creates a new {@link Jackson2JsonRedisSerializer} for the given target {@link Class}.
|
||||
*
|
||||
@@ -70,7 +78,7 @@ public class Jackson2JsonRedisSerializer<T> implements RedisSerializer<T> {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return (T) this.objectMapper.readValue(bytes, 0, bytes.length, javaType);
|
||||
return (T) this.reader.read(this.objectMapper, bytes, javaType);
|
||||
} catch (Exception ex) {
|
||||
throw new SerializationException("Could not read JSON: " + ex.getMessage(), ex);
|
||||
}
|
||||
@@ -83,7 +91,7 @@ public class Jackson2JsonRedisSerializer<T> implements RedisSerializer<T> {
|
||||
return SerializationUtils.EMPTY_ARRAY;
|
||||
}
|
||||
try {
|
||||
return this.objectMapper.writeValueAsBytes(t);
|
||||
return this.writer.write(this.objectMapper, t);
|
||||
} catch (Exception ex) {
|
||||
throw new SerializationException("Could not write JSON: " + ex.getMessage(), ex);
|
||||
}
|
||||
@@ -104,6 +112,26 @@ public class Jackson2JsonRedisSerializer<T> implements RedisSerializer<T> {
|
||||
this.objectMapper = objectMapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link JacksonObjectReader} for this serializer. Setting the reader allows customization of the JSON
|
||||
* deserialization.
|
||||
*
|
||||
* @since 3.0
|
||||
*/
|
||||
public void setReader(JacksonObjectReader reader) {
|
||||
this.reader = reader;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link JacksonObjectWriter} for this serializer. Setting the reader allows customization of the JSON
|
||||
* serialization.
|
||||
*
|
||||
* @since 3.0
|
||||
*/
|
||||
public void setWriter(JacksonObjectWriter writer) {
|
||||
this.writer = writer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Jackson {@link JavaType} for the specific class.
|
||||
* <p>
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Copyright 2022 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.data.redis.serializer;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import com.fasterxml.jackson.databind.JavaType;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
/**
|
||||
* Defines the contract for Object Mapping readers. Implementations of this interface can deserialize a given byte array
|
||||
* holding JSON to an Object considering the target type.
|
||||
* <p>
|
||||
* Reader functions can customize how the actual JSON is being deserialized by e.g. obtaining a customized
|
||||
* {@link com.fasterxml.jackson.databind.ObjectReader} applying serialization features, date formats, or views.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 3.0
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface JacksonObjectReader {
|
||||
|
||||
/**
|
||||
* Read an object graph from the given root JSON into a Java object considering the {@link JavaType}.
|
||||
*
|
||||
* @param mapper the object mapper to use.
|
||||
* @param source the JSON to deserialize.
|
||||
* @param type the Java target type
|
||||
* @return the deserialized Java object.
|
||||
* @throws IOException if an I/O error or JSON deserialization error occurs.
|
||||
*/
|
||||
Object read(ObjectMapper mapper, byte[] source, JavaType type) throws IOException;
|
||||
|
||||
/**
|
||||
* Create a default {@link JacksonObjectReader} delegating to {@link ObjectMapper#readValue(InputStream, JavaType)}.
|
||||
*
|
||||
* @return the default {@link JacksonObjectReader}.
|
||||
*/
|
||||
static JacksonObjectReader create() {
|
||||
return (mapper, source, type) -> mapper.readValue(source, 0, source.length, type);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright 2022 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.data.redis.serializer;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
/**
|
||||
* Defines the contract for Object Mapping writers. Implementations of this interface can serialize a given Object to a
|
||||
* {@code byte[]} containing JSON.
|
||||
* <p>
|
||||
* Writer functions can customize how the actual JSON is being written by e.g. obtaining a customized
|
||||
* {@link com.fasterxml.jackson.databind.ObjectWriter} applying serialization features, date formats, or views.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 3.0
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface JacksonObjectWriter {
|
||||
|
||||
/**
|
||||
* Write the object graph with the given root {@code source} as byte array.
|
||||
*
|
||||
* @param mapper the object mapper to use.
|
||||
* @param source the root of the object graph to marshal.
|
||||
* @return a byte array containing the serialized object graph.
|
||||
* @throws IOException if an I/O error or JSON serialization error occurs.
|
||||
*/
|
||||
byte[] write(ObjectMapper mapper, Object source) throws IOException;
|
||||
|
||||
/**
|
||||
* Create a default {@link JacksonObjectWriter} delegating to {@link ObjectMapper#writeValueAsBytes(Object)}.
|
||||
*
|
||||
* @return the default {@link JacksonObjectWriter}.
|
||||
*/
|
||||
static JacksonObjectWriter create() {
|
||||
return ObjectMapper::writeValueAsBytes;
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user