Skip to content

Commit 066a3c0

Browse files
authored
Json (de)serialization configuration improvements (#1573)
* Allow passing JavaType to JsonSerde * Allow setting forced type for JsonSerializer * Add copying json serializers with new type
1 parent b2f8be6 commit 066a3c0

File tree

4 files changed

+263
-19
lines changed

4 files changed

+263
-19
lines changed

spring-kafka/src/main/java/org/springframework/kafka/support/serializer/JsonDeserializer.java

Lines changed: 70 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ public JsonDeserializer(ObjectMapper objectMapper) {
142142
* {@link ObjectMapper}.
143143
* @param targetType the target type to use if no type info headers are present.
144144
*/
145-
public JsonDeserializer(Class<? super T> targetType) {
145+
public JsonDeserializer(@Nullable Class<? super T> targetType) {
146146
this(targetType, true);
147147
}
148148

@@ -151,7 +151,17 @@ public JsonDeserializer(Class<? super T> targetType) {
151151
* @param targetType the target type reference to use if no type info headers are present.
152152
* @since 2.3
153153
*/
154-
public JsonDeserializer(TypeReference<? super T> targetType) {
154+
public JsonDeserializer(@Nullable TypeReference<? super T> targetType) {
155+
this(targetType, true);
156+
}
157+
158+
159+
/**
160+
* Construct an instance with the provided target type, and a default {@link ObjectMapper}.
161+
* @param targetType the target java type to use if no type info headers are present.
162+
* @since 2.3
163+
*/
164+
public JsonDeserializer(@Nullable JavaType targetType) {
155165
this(targetType, true);
156166
}
157167

@@ -179,6 +189,18 @@ public JsonDeserializer(TypeReference<? super T> targetType, boolean useHeadersI
179189
this(targetType, JacksonUtils.enhancedObjectMapper(), useHeadersIfPresent);
180190
}
181191

192+
/**
193+
* Construct an instance with the provided target type, and
194+
* useHeadersIfPresent with a default {@link ObjectMapper}.
195+
* @param targetType the target java type.
196+
* @param useHeadersIfPresent true to use headers if present and fall back to target
197+
* type if not.
198+
* @since 2.3
199+
*/
200+
public JsonDeserializer(JavaType targetType, boolean useHeadersIfPresent) {
201+
this(targetType, JacksonUtils.enhancedObjectMapper(), useHeadersIfPresent);
202+
}
203+
182204
/**
183205
* Construct an instance with the provided target type, and {@link ObjectMapper}.
184206
* @param targetType the target type to use if no type info headers are present.
@@ -197,6 +219,15 @@ public JsonDeserializer(TypeReference<? super T> targetType, ObjectMapper object
197219
this(targetType, objectMapper, true);
198220
}
199221

222+
/**
223+
* Construct an instance with the provided target type, and {@link ObjectMapper}.
224+
* @param targetType the target java type to use if no type info headers are present.
225+
* @param objectMapper the mapper. type if not.
226+
*/
227+
public JsonDeserializer(JavaType targetType, ObjectMapper objectMapper) {
228+
this(targetType, objectMapper, true);
229+
}
230+
200231
/**
201232
* Construct an instance with the provided target type, {@link ObjectMapper} and
202233
* useHeadersIfPresent.
@@ -517,6 +548,43 @@ public void close() {
517548
// No-op
518549
}
519550

551+
/**
552+
* Copies this deserializer with same configuration, except new target type is used.
553+
* @param newTargetType type used for when type headers are missing, not null
554+
* @param <X> new deserialization result type
555+
* @return new instance of deserializer with type changes
556+
* @since 2.6
557+
*/
558+
public <X> JsonDeserializer<X> copyWithType(Class<? super X> newTargetType) {
559+
return copyWithType(this.objectMapper.constructType(newTargetType));
560+
}
561+
562+
/**
563+
* Copies this deserializer with same configuration, except new target type reference is used.
564+
* @param newTargetType type reference used for when type headers are missing, not null
565+
* @param <X> new deserialization result type
566+
* @return new instance of deserializer with type changes
567+
* @since 2.6
568+
*/
569+
public <X> JsonDeserializer<X> copyWithType(TypeReference<? super X> newTargetType) {
570+
return copyWithType(this.objectMapper.constructType(newTargetType.getType()));
571+
}
572+
573+
/**
574+
* Copies this deserializer with same configuration, except new target java type is used.
575+
* @param newTargetType java type used for when type headers are missing, not null
576+
* @param <X> new deserialization result type
577+
* @return new instance of deserializer with type changes
578+
* @since 2.6
579+
*/
580+
public <X> JsonDeserializer<X> copyWithType(JavaType newTargetType) {
581+
JsonDeserializer<X> result = new JsonDeserializer<>(newTargetType, this.objectMapper, this.useTypeHeaders);
582+
result.removeTypeHeaders = this.removeTypeHeaders;
583+
result.typeMapper = this.typeMapper;
584+
result.typeMapperExplicitlySet = this.typeMapperExplicitlySet;
585+
return result;
586+
}
587+
520588
// Fluent API
521589

522590
/**

spring-kafka/src/main/java/org/springframework/kafka/support/serializer/JsonSerde.java

Lines changed: 68 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@
2828
import org.springframework.lang.Nullable;
2929
import org.springframework.util.Assert;
3030

31+
import com.fasterxml.jackson.core.type.TypeReference;
32+
import com.fasterxml.jackson.databind.JavaType;
3133
import com.fasterxml.jackson.databind.ObjectMapper;
3234

3335
/**
@@ -53,29 +55,45 @@ public class JsonSerde<T> implements Serde<T> {
5355
private final JsonDeserializer<T> jsonDeserializer;
5456

5557
public JsonSerde() {
56-
this((ObjectMapper) null);
58+
this((JavaType) null, JacksonUtils.enhancedObjectMapper());
5759
}
5860

59-
public JsonSerde(Class<? super T> targetType) {
60-
this(targetType, null);
61+
public JsonSerde(@Nullable Class<? super T> targetType) {
62+
this(targetType, JacksonUtils.enhancedObjectMapper());
63+
}
64+
65+
public JsonSerde(@Nullable TypeReference<? super T> targetType) {
66+
this(targetType, JacksonUtils.enhancedObjectMapper());
67+
}
68+
69+
public JsonSerde(@Nullable JavaType targetType) {
70+
this(targetType, JacksonUtils.enhancedObjectMapper());
6171
}
6272

6373
public JsonSerde(ObjectMapper objectMapper) {
64-
this(null, objectMapper);
74+
this((JavaType) null, objectMapper);
75+
}
76+
77+
public JsonSerde(@Nullable TypeReference<? super T> targetType, ObjectMapper objectMapper) {
78+
this(targetType == null ? null : objectMapper.constructType(targetType.getType()), objectMapper);
79+
}
80+
81+
public JsonSerde(@Nullable Class<? super T> targetType, ObjectMapper objectMapper) {
82+
this(targetType == null ? null : objectMapper.constructType(targetType), objectMapper);
6583
}
6684

67-
@SuppressWarnings("unchecked")
68-
public JsonSerde(@Nullable Class<? super T> targetTypeArg, @Nullable ObjectMapper objectMapperArg) {
69-
ObjectMapper objectMapper = objectMapperArg;
70-
Class<T> targetType = (Class<T>) targetTypeArg;
71-
if (objectMapper == null) {
72-
objectMapper = JacksonUtils.enhancedObjectMapper();
85+
public JsonSerde(@Nullable JavaType targetTypeArg, @Nullable ObjectMapper objectMapperArg) {
86+
ObjectMapper objectMapper = objectMapperArg == null ? JacksonUtils.enhancedObjectMapper() : objectMapperArg;
87+
JavaType actualJavaType;
88+
if (targetTypeArg != null) {
89+
actualJavaType = targetTypeArg;
7390
}
74-
this.jsonSerializer = new JsonSerializer<>(objectMapper);
75-
if (targetType == null) {
76-
targetType = (Class<T>) ResolvableType.forClass(getClass()).getSuperType().resolveGeneric(0);
91+
else {
92+
Class<?> resolvedGeneric = ResolvableType.forClass(getClass()).getSuperType().resolveGeneric(0);
93+
actualJavaType = resolvedGeneric != null ? objectMapper.constructType(resolvedGeneric) : null;
7794
}
78-
this.jsonDeserializer = new JsonDeserializer<>(targetType, objectMapper);
95+
this.jsonSerializer = new JsonSerializer<>(actualJavaType, objectMapper);
96+
this.jsonDeserializer = new JsonDeserializer<>(actualJavaType, objectMapper);
7997
}
8098

8199
public JsonSerde(JsonSerializer<T> jsonSerializer, JsonDeserializer<T> jsonDeserializer) {
@@ -107,6 +125,42 @@ public Deserializer<T> deserializer() {
107125
return this.jsonDeserializer;
108126
}
109127

128+
/**
129+
* Copies this serde with same configuration, except new target type is used.
130+
* @param newTargetType type reference forced for serialization, and used as default for deserialization, not null
131+
* @param <X> new deserialization result type and serialization source type
132+
* @return new instance of serde with type changes
133+
* @since 2.6
134+
*/
135+
public <X> JsonSerde<X> copyWithType(Class<? super X> newTargetType) {
136+
return new JsonSerde<>(this.jsonSerializer.copyWithType(newTargetType),
137+
this.jsonDeserializer.copyWithType(newTargetType));
138+
}
139+
140+
/**
141+
* Copies this serde with same configuration, except new target type reference is used.
142+
* @param newTargetType type reference forced for serialization, and used as default for deserialization, not null
143+
* @param <X> new deserialization result type and serialization source type
144+
* @return new instance of serde with type changes
145+
* @since 2.6
146+
*/
147+
public <X> JsonSerde<X> copyWithType(TypeReference<? super X> newTargetType) {
148+
return new JsonSerde<>(this.jsonSerializer.copyWithType(newTargetType),
149+
this.jsonDeserializer.copyWithType(newTargetType));
150+
}
151+
152+
/**
153+
* Copies this serde with same configuration, except new target java type is used.
154+
* @param newTargetType java type forced for serialization, and used as default for deserialization, not null
155+
* @param <X> new deserialization result type and serialization source type
156+
* @return new instance of serde with type changes
157+
* @since 2.6
158+
*/
159+
public <X> JsonSerde<X> copyWithType(JavaType newTargetType) {
160+
return new JsonSerde<>(this.jsonSerializer.copyWithType(newTargetType),
161+
this.jsonDeserializer.copyWithType(newTargetType));
162+
}
163+
110164
// Fluent API
111165

112166
/**

spring-kafka/src/main/java/org/springframework/kafka/support/serializer/JsonSerializer.java

Lines changed: 58 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2016-2019 the original author or authors.
2+
* Copyright 2016-2020 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -33,7 +33,10 @@
3333
import org.springframework.util.ClassUtils;
3434
import org.springframework.util.StringUtils;
3535

36+
import com.fasterxml.jackson.core.type.TypeReference;
37+
import com.fasterxml.jackson.databind.JavaType;
3638
import com.fasterxml.jackson.databind.ObjectMapper;
39+
import com.fasterxml.jackson.databind.ObjectWriter;
3740

3841
/**
3942
* Generic {@link org.apache.kafka.common.serialization.Serializer Serializer} for sending
@@ -63,17 +66,32 @@ public class JsonSerializer<T> implements Serializer<T> {
6366

6467
protected boolean addTypeInfo = true; // NOSONAR
6568

69+
private ObjectWriter writer;
70+
6671
protected Jackson2JavaTypeMapper typeMapper = new DefaultJackson2JavaTypeMapper(); // NOSONAR
6772

6873
private boolean typeMapperExplicitlySet = false;
6974

7075
public JsonSerializer() {
71-
this(JacksonUtils.enhancedObjectMapper());
76+
this((JavaType) null, JacksonUtils.enhancedObjectMapper());
77+
}
78+
79+
public JsonSerializer(TypeReference<? super T> targetType) {
80+
this(targetType, JacksonUtils.enhancedObjectMapper());
7281
}
7382

7483
public JsonSerializer(ObjectMapper objectMapper) {
84+
this((JavaType) null, objectMapper);
85+
}
86+
87+
public JsonSerializer(TypeReference<? super T> targetType, ObjectMapper objectMapper) {
88+
this(targetType == null ? null : objectMapper.constructType(targetType.getType()), objectMapper);
89+
}
90+
91+
public JsonSerializer(JavaType targetType, ObjectMapper objectMapper) {
7592
Assert.notNull(objectMapper, "'objectMapper' must not be null.");
7693
this.objectMapper = objectMapper;
94+
this.writer = objectMapper.writerFor(targetType);
7795
}
7896

7997
public boolean isAddTypeInfo() {
@@ -174,7 +192,7 @@ public byte[] serialize(String topic, @Nullable T data) {
174192
return null;
175193
}
176194
try {
177-
return this.objectMapper.writeValueAsBytes(data);
195+
return this.writer.writeValueAsBytes(data);
178196
}
179197
catch (IOException ex) {
180198
throw new SerializationException("Can't serialize data [" + data + "] for topic [" + topic + "]", ex);
@@ -186,6 +204,43 @@ public void close() {
186204
// No-op
187205
}
188206

207+
/**
208+
* Copies this serializer with same configuration, except new target type reference is used.
209+
* @param newTargetType type reference forced for serialization, not null
210+
* @param <X> new serialization source type
211+
* @return new instance of serializer with type changes
212+
* @since 2.6
213+
*/
214+
public <X> JsonSerializer<X> copyWithType(Class<? super X> newTargetType) {
215+
return copyWithType(this.objectMapper.constructType(newTargetType));
216+
}
217+
218+
/**
219+
* Copies this serializer with same configuration, except new target type reference is used.
220+
* @param newTargetType type reference forced for serialization, not null
221+
* @param <X> new serialization source type
222+
* @return new instance of serializer with type changes
223+
* @since 2.6
224+
*/
225+
public <X> JsonSerializer<X> copyWithType(TypeReference<? super X> newTargetType) {
226+
return copyWithType(this.objectMapper.constructType(newTargetType.getType()));
227+
}
228+
229+
/**
230+
* Copies this serializer with same configuration, except new target java type is used.
231+
* @param newTargetType java type forced for serialization, not null
232+
* @param <X> new serialization source type
233+
* @return new instance of serializer with type changes
234+
* @since 2.6
235+
*/
236+
public <X> JsonSerializer<X> copyWithType(JavaType newTargetType) {
237+
JsonSerializer<X> result = new JsonSerializer<>(newTargetType, this.objectMapper);
238+
result.addTypeInfo = this.addTypeInfo;
239+
result.typeMapper = this.typeMapper;
240+
result.typeMapperExplicitlySet = this.typeMapperExplicitlySet;
241+
return result;
242+
}
243+
189244
// Fluent API
190245

191246
/**

0 commit comments

Comments
 (0)