Skip to content

Commit c013a63

Browse files
mp911dechristophstrobl
authored andcommitted
Refine Jackson2JsonRedisSerializer design.
Deprecate ObjectMapper setter. Introduce constructor accepting the ObjectMapper. Original Pull Request: #2332
1 parent 6f4ac11 commit c013a63

File tree

3 files changed

+118
-75
lines changed

3 files changed

+118
-75
lines changed

src/main/java/org/springframework/data/redis/serializer/GenericJackson2JsonRedisSerializer.java

+43-44
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@
3232
import com.fasterxml.jackson.databind.JsonNode;
3333
import com.fasterxml.jackson.databind.ObjectMapper;
3434
import com.fasterxml.jackson.databind.ObjectMapper.DefaultTyping;
35-
import com.fasterxml.jackson.databind.ObjectReader;
3635
import com.fasterxml.jackson.databind.SerializerProvider;
3736
import com.fasterxml.jackson.databind.jsontype.PolymorphicTypeValidator;
3837
import com.fasterxml.jackson.databind.jsontype.TypeSerializer;
@@ -55,37 +54,16 @@
5554
*/
5655
public class GenericJackson2JsonRedisSerializer implements RedisSerializer<Object> {
5756

58-
private ObjectMapper mapper;
57+
private final ObjectMapper mapper;
5958

6059
private final JacksonObjectReader reader;
6160

6261
private final JacksonObjectWriter writer;
6362

64-
private boolean internalReader = false;
63+
private final Lazy<Boolean> defaultTypingEnabled;
6564

6665
private final TypeResolver typeResolver;
6766

68-
private Lazy<Boolean> defaultTypingEnabled = Lazy
69-
.of(() -> mapper.getSerializationConfig().getDefaultTyper(null) != null);
70-
71-
private Lazy<String> typeHintPropertyName;
72-
73-
{
74-
typeHintPropertyName = Lazy.of(() -> {
75-
if (defaultTypingEnabled.get()) {
76-
return null;
77-
}
78-
79-
return mapper.getDeserializationConfig().getDefaultTyper(null)
80-
.buildTypeDeserializer(mapper.getDeserializationConfig(), mapper.getTypeFactory().constructType(Object.class),
81-
Collections.emptyList())
82-
.getPropertyName();
83-
84-
}).or("@class");
85-
86-
typeResolver = new TypeResolver(Lazy.of(() -> mapper.getTypeFactory()), typeHintPropertyName);
87-
}
88-
8967
/**
9068
* Creates {@link GenericJackson2JsonRedisSerializer} and configures {@link ObjectMapper} for default typing.
9169
*/
@@ -104,7 +82,6 @@ public GenericJackson2JsonRedisSerializer() {
10482
*/
10583
public GenericJackson2JsonRedisSerializer(@Nullable String classPropertyTypeName) {
10684
this(classPropertyTypeName, JacksonObjectReader.create(), JacksonObjectWriter.create());
107-
this.internalReader = true;
10885
}
10986

11087
/**
@@ -122,7 +99,7 @@ public GenericJackson2JsonRedisSerializer(@Nullable String classPropertyTypeName
12299
public GenericJackson2JsonRedisSerializer(@Nullable String classPropertyTypeName, JacksonObjectReader reader,
123100
JacksonObjectWriter writer) {
124101

125-
this(new ObjectMapper(), reader, writer);
102+
this(new ObjectMapper(), reader, writer, classPropertyTypeName);
126103

127104
// simply setting {@code mapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS)} does not help here since we need
128105
// the type hint embedded for deserialization using the default typing feature.
@@ -134,10 +111,6 @@ public GenericJackson2JsonRedisSerializer(@Nullable String classPropertyTypeName
134111
} else {
135112
mapper.activateDefaultTyping(mapper.getPolymorphicTypeValidator(), DefaultTyping.EVERYTHING, As.PROPERTY);
136113
}
137-
138-
if (classPropertyTypeName != null) {
139-
typeHintPropertyName = Lazy.of(classPropertyTypeName);
140-
}
141114
}
142115

143116
/**
@@ -149,7 +122,6 @@ public GenericJackson2JsonRedisSerializer(@Nullable String classPropertyTypeName
149122
*/
150123
public GenericJackson2JsonRedisSerializer(ObjectMapper mapper) {
151124
this(mapper, JacksonObjectReader.create(), JacksonObjectWriter.create());
152-
this.internalReader = true;
153125
}
154126

155127
/**
@@ -164,6 +136,11 @@ public GenericJackson2JsonRedisSerializer(ObjectMapper mapper) {
164136
*/
165137
public GenericJackson2JsonRedisSerializer(ObjectMapper mapper, JacksonObjectReader reader,
166138
JacksonObjectWriter writer) {
139+
this(mapper, reader, writer, null);
140+
}
141+
142+
private GenericJackson2JsonRedisSerializer(ObjectMapper mapper, JacksonObjectReader reader,
143+
JacksonObjectWriter writer, @Nullable String typeHintPropertyName) {
167144

168145
Assert.notNull(mapper, "ObjectMapper must not be null");
169146
Assert.notNull(reader, "Reader must not be null");
@@ -172,6 +149,29 @@ public GenericJackson2JsonRedisSerializer(ObjectMapper mapper, JacksonObjectRead
172149
this.mapper = mapper;
173150
this.reader = reader;
174151
this.writer = writer;
152+
153+
this.defaultTypingEnabled = Lazy.of(() -> mapper.getSerializationConfig().getDefaultTyper(null) != null);
154+
155+
Supplier<String> typeHintPropertyNameSupplier;
156+
157+
if (typeHintPropertyName == null) {
158+
159+
typeHintPropertyNameSupplier = Lazy.of(() -> {
160+
if (defaultTypingEnabled.get()) {
161+
return null;
162+
}
163+
164+
return mapper.getDeserializationConfig().getDefaultTyper(null)
165+
.buildTypeDeserializer(mapper.getDeserializationConfig(),
166+
mapper.getTypeFactory().constructType(Object.class), Collections.emptyList())
167+
.getPropertyName();
168+
169+
}).or("@class");
170+
} else {
171+
typeHintPropertyNameSupplier = () -> typeHintPropertyName;
172+
}
173+
174+
this.typeResolver = new TypeResolver(Lazy.of(mapper::getTypeFactory), typeHintPropertyNameSupplier);
175175
}
176176

177177
/**
@@ -233,21 +233,22 @@ public <T> T deserialize(@Nullable byte[] source, Class<T> type) throws Serializ
233233
}
234234
}
235235

236-
protected JavaType resolveType(byte[] source, Class<?> type) {
236+
protected JavaType resolveType(byte[] source, Class<?> type) throws IOException {
237237

238-
if (internalReader || !type.equals(Object.class) || !defaultTypingEnabled.get()) {
238+
if (!type.equals(Object.class) || !defaultTypingEnabled.get()) {
239239
return typeResolver.constructType(type);
240240
}
241241

242242
return typeResolver.resolveType(source, type);
243243
}
244244

245-
private static class TypeResolver {
245+
static class TypeResolver {
246246

247-
private final ObjectReader objectReader = new ObjectMapper().reader();
247+
// need a separate instance to bypass class hint checks
248+
private final ObjectMapper mapper = new ObjectMapper();
248249

249250
private final Supplier<TypeFactory> typeFactory;
250-
private Supplier<String> hintName;
251+
private final Supplier<String> hintName;
251252

252253
public TypeResolver(Supplier<TypeFactory> typeFactory, Supplier<String> hintName) {
253254

@@ -259,15 +260,13 @@ protected JavaType constructType(Class<?> type) {
259260
return typeFactory.get().constructType(type);
260261
}
261262

262-
protected JavaType resolveType(byte[] source, Class<?> type) {
263+
protected JavaType resolveType(byte[] source, Class<?> type) throws IOException {
263264

264-
try {
265-
TextNode typeName = (TextNode) objectReader.readValue(source, JsonNode.class).get(hintName.get());
266-
if (typeName != null) {
267-
return typeFactory.get().constructFromCanonical(typeName.textValue());
268-
}
269-
} catch (IOException e) {
270-
// TODO: logging?
265+
JsonNode root = mapper.readTree(source);
266+
JsonNode jsonNode = root.get(hintName.get());
267+
268+
if (jsonNode instanceof TextNode && jsonNode.asText() != null) {
269+
return typeFactory.get().constructFromCanonical(jsonNode.asText());
271270
}
272271

273272
return constructType(type);

src/main/java/org/springframework/data/redis/serializer/Jackson2JsonRedisSerializer.java

+69-30
Original file line numberDiff line numberDiff line change
@@ -43,31 +43,86 @@
4343
*/
4444
public class Jackson2JsonRedisSerializer<T> implements RedisSerializer<T> {
4545

46+
/**
47+
* @deprecated since 3.0 for removal.
48+
*/
49+
@Deprecated(since = "3.0", forRemoval = true) //
4650
public static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;
4751

4852
private final JavaType javaType;
4953

50-
private ObjectMapper objectMapper = new ObjectMapper();
54+
private ObjectMapper mapper;
5155

52-
private JacksonObjectReader reader = JacksonObjectReader.create();
56+
private final JacksonObjectReader reader;
5357

54-
private JacksonObjectWriter writer = JacksonObjectWriter.create();
58+
private final JacksonObjectWriter writer;
5559

5660
/**
5761
* Creates a new {@link Jackson2JsonRedisSerializer} for the given target {@link Class}.
5862
*
59-
* @param type
63+
* @param type must not be {@literal null}.
6064
*/
6165
public Jackson2JsonRedisSerializer(Class<T> type) {
62-
this.javaType = getJavaType(type);
66+
this(new ObjectMapper(), type);
6367
}
6468

6569
/**
6670
* Creates a new {@link Jackson2JsonRedisSerializer} for the given target {@link JavaType}.
6771
*
68-
* @param javaType
72+
* @param javaType must not be {@literal null}.
6973
*/
7074
public Jackson2JsonRedisSerializer(JavaType javaType) {
75+
this(new ObjectMapper(), javaType);
76+
}
77+
78+
/**
79+
* Creates a new {@link Jackson2JsonRedisSerializer} for the given target {@link Class}.
80+
*
81+
* @param mapper must not be {@literal null}.
82+
* @param type must not be {@literal null}.
83+
* @since 3.0
84+
*/
85+
public Jackson2JsonRedisSerializer(ObjectMapper mapper, Class<T> type) {
86+
87+
Assert.notNull(mapper, "ObjectMapper must not be null");
88+
Assert.notNull(type, "Java type must not be null");
89+
90+
this.javaType = getJavaType(type);
91+
this.mapper = mapper;
92+
this.reader = JacksonObjectReader.create();
93+
this.writer = JacksonObjectWriter.create();
94+
}
95+
96+
/**
97+
* Creates a new {@link Jackson2JsonRedisSerializer} for the given target {@link JavaType}.
98+
*
99+
* @param mapper must not be {@literal null}.
100+
* @param javaType must not be {@literal null}.
101+
* @since 3.0
102+
*/
103+
public Jackson2JsonRedisSerializer(ObjectMapper mapper, JavaType javaType) {
104+
this(mapper, javaType, JacksonObjectReader.create(), JacksonObjectWriter.create());
105+
}
106+
107+
/**
108+
* Creates a new {@link Jackson2JsonRedisSerializer} for the given target {@link JavaType}.
109+
*
110+
* @param mapper must not be {@literal null}.
111+
* @param javaType must not be {@literal null}.
112+
* @param reader the {@link JacksonObjectReader} function to read objects using {@link ObjectMapper}.
113+
* @param writer the {@link JacksonObjectWriter} function to write objects using {@link ObjectMapper}.
114+
* @since 3.0
115+
*/
116+
public Jackson2JsonRedisSerializer(ObjectMapper mapper, JavaType javaType, JacksonObjectReader reader,
117+
JacksonObjectWriter writer) {
118+
119+
Assert.notNull(mapper, "ObjectMapper must not be null!");
120+
Assert.notNull(reader, "Reader must not be null!");
121+
Assert.notNull(writer, "Writer must not be null!");
122+
123+
this.mapper = mapper;
124+
this.reader = reader;
125+
this.writer = writer;
71126
this.javaType = javaType;
72127
}
73128

@@ -78,7 +133,7 @@ public T deserialize(@Nullable byte[] bytes) throws SerializationException {
78133
return null;
79134
}
80135
try {
81-
return (T) this.reader.read(this.objectMapper, bytes, javaType);
136+
return (T) this.reader.read(this.mapper, bytes, javaType);
82137
} catch (Exception ex) {
83138
throw new SerializationException("Could not read JSON: " + ex.getMessage(), ex);
84139
}
@@ -91,7 +146,7 @@ public byte[] serialize(@Nullable Object t) throws SerializationException {
91146
return SerializationUtils.EMPTY_ARRAY;
92147
}
93148
try {
94-
return this.writer.write(this.objectMapper, t);
149+
return this.writer.write(this.mapper, t);
95150
} catch (Exception ex) {
96151
throw new SerializationException("Could not write JSON: " + ex.getMessage(), ex);
97152
}
@@ -105,31 +160,15 @@ public byte[] serialize(@Nullable Object t) throws SerializationException {
105160
* process. For example, an extended {@link SerializerFactory} can be configured that provides custom serializers for
106161
* specific types. The other option for refining the serialization process is to use Jackson's provided annotations on
107162
* the types to be serialized, in which case a custom-configured ObjectMapper is unnecessary.
108-
*/
109-
public void setObjectMapper(ObjectMapper objectMapper) {
110-
111-
Assert.notNull(objectMapper, "'objectMapper' must not be null");
112-
this.objectMapper = objectMapper;
113-
}
114-
115-
/**
116-
* Sets the {@link JacksonObjectReader} for this serializer. Setting the reader allows customization of the JSON
117-
* deserialization.
118163
*
119-
* @since 3.0
164+
* @deprecated since 3.0, use {@link #Jackson2JsonRedisSerializer(ObjectMapper, Class) constructor creation} to
165+
* configure the object mapper.
120166
*/
121-
public void setReader(JacksonObjectReader reader) {
122-
this.reader = reader;
123-
}
167+
@Deprecated(since = "3.0", forRemoval = true)
168+
public void setObjectMapper(ObjectMapper mapper) {
124169

125-
/**
126-
* Sets the {@link JacksonObjectWriter} for this serializer. Setting the reader allows customization of the JSON
127-
* serialization.
128-
*
129-
* @since 3.0
130-
*/
131-
public void setWriter(JacksonObjectWriter writer) {
132-
this.writer = writer;
170+
Assert.notNull(mapper, "'objectMapper' must not be null");
171+
this.mapper = mapper;
133172
}
134173

135174
/**

src/test/java/org/springframework/data/redis/serializer/Jackson2JsonRedisSerializerTests.java

+6-1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@
2424
import org.springframework.data.redis.Person;
2525
import org.springframework.data.redis.PersonObjectFactory;
2626

27+
import com.fasterxml.jackson.databind.ObjectMapper;
28+
import com.fasterxml.jackson.databind.type.TypeFactory;
29+
2730
/**
2831
* Unit tests for {@link Jackson2JsonRedisSerializer}.
2932
*
@@ -70,8 +73,10 @@ void testJackson2JsonSerilizerThrowsExceptionWhenSettingNullObjectMapper() {
7073
@Test // GH-2322
7174
void shouldConsiderWriter() {
7275

76+
serializer = new Jackson2JsonRedisSerializer<>(new ObjectMapper(),
77+
TypeFactory.defaultInstance().constructType(Person.class), JacksonObjectReader.create(),
78+
(mapper, source) -> "foo".getBytes());
7379
Person person = new PersonObjectFactory().instance();
74-
serializer.setWriter((mapper, source) -> "foo".getBytes());
7580
assertThat(serializer.serialize(person)).isEqualTo("foo".getBytes());
7681
}
7782

0 commit comments

Comments
 (0)