JsonDeserializer 不适用于 Class 但仅适用于 class 的单个元素
JsonDeserializer does not work on the Class but only on the single element of the class
我创建了一个新的反序列化器,可以将空字符串写成 null
public class CustomDeserializer extends JsonDeserializer<String> {
@Override
public String deserialize(JsonParser jsonParser, DeserializationContext context) throws IOException {
JsonNode node = jsonParser.readValueAsTree();
if (node.asText().isEmpty()) {
return null;
}
return node.toString();
}
}
试图在每个用户字段上制作单个注释,自定义工作但通过在整个 class 上插入注释,我无法再打印 Json 消息
@JsonDeserialize(using = CustomDeserializer.class)
public class User {
private String firstName;
private String lastName;
private String age;
private String address; }
CustomExceptionHandler 向我抛出此错误:Class MethodArgumentNotValidException
这是我的 Kafka 消费者,这是我唯一输入验证注释的消费者,但即使删除它也会给我同样的错误
public class KafkaConsumer {
@Autowired
private UserRepository userRepository;
@KafkaListener(topics = "${spring.kafka.topic.name}")
public void listen(@Validated User user) {
User user = new User(user);
UserRepository.save(user.getName(), user);
}
}
对象映射器
public ObjectMapper getObjectMapper() {
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JavaTimeModule());
mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
mapper.setSerializationInclusion(Include.NON_NULL);
return mapper;
}
是否可以让它在整个 class 中发挥作用?
如果您希望空 String
表示 整个 对象被视为 null
,您可以启用 ACCEPT_EMPTY_STRING_AS_NULL_OBJECT
Jackson deserialization feature,默认禁用。
您可以在配置 ObjectMapper
时包含它:
public ObjectMapper getObjectMapper() {
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JavaTimeModule());
mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
mapper.setSerializationInclusion(Include.NON_NULL);
// Enable ACCEPT_EMPTY_STRING_AS_NULL_OBJECT deserialization feature
mapper.enable(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT);
return mapper;
}
如上所述,当您想将表示 整个 对象的空 String
视为 null
时,它很有用;但是,它不适用于 String
类型的单个属性:在后一种情况下,您可以安全地使用自定义反序列化器,因此,解决方案实际上是两种方法的混合,使用 ACCEPT_EMPTY_STRING_AS_NULL_OBJECT
反序列化处理整个对象的功能,以及处理单个 String
属性的自定义反序列化器。
请参阅 this and 相关的 SO 问题。
您也可以改进自定义 User
反序列化器。请考虑例如(为了清楚起见,我将名称重构为 UserDeserializer
):
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import com.fasterxml.jackson.annotation.JsonAlias;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonNode;
public class UserDeserializer extends JsonDeserializer<User> {
@Override
public User deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
JsonNode node = jsonParser.readValueAsTree();
Iterator<String> fieldNames = node.fieldNames();
// Process Jackson annotations looking for aliases
Map<String, String> fieldAliases = this.getAliases();
User user = new User();
boolean anyNonNull = false;
// Iterate over every field. The deserialization process assume simple properties
while(fieldNames.hasNext()) {
String fieldName = fieldNames.next();
JsonNode fieldValue = node.get(fieldName);
String fieldValueTextRepresentation = fieldValue.asText();
if (fieldValueTextRepresentation != null && !fieldValueTextRepresentation.trim().isEmpty()) {
// Check if the field is aliased
String actualFieldName = fieldAliases.get(fieldName);
if (actualFieldName == null) {
actualFieldName = fieldName;
}
this.setFieldValue(user, actualFieldName, fieldValueTextRepresentation);
anyNonNull = true;
}
}
return anyNonNull ? user : null;
}
// Set field value via Reflection
private void setFieldValue(User user, String fieldName, String fieldValueTextRepresentation) {
try {
Field field = User.class.getDeclaredField(fieldName);
Object fieldValue = null;
Class clazz = field.getType();
// Handle each class type: probably this code can be improved, but it is extensible and adaptable,
// you can include as many cases as you need.
if (clazz.isAssignableFrom(String.class)) {
fieldValue = fieldValueTextRepresentation;
} else if (clazz.isAssignableFrom(LocalDate.class)) {
// Adjust the date pattern as required
// For example, if you are receiving the information
// like this: year-month-day, as in the provided example,
// you can use the following pattern
fieldValue = LocalDate.parse(fieldValueTextRepresentation, DateTimeFormatter.ofPattern("yyyy-MM-dd"));
} else if (clazz.isAssignableFrom(Integer.class)) {
fieldValue = Integer.parseInt(fieldValueTextRepresentation);
}
field.setAccessible(true);
field.set(user, fieldValue);
} catch (Exception e) {
// Handle the problem as appropriate
e.printStackTrace();
}
}
/* Look for Jackson aliases */
private Map<String, String> getAliases() {
Map<String, String> fieldAliases = new HashMap<>();
Field[] fields = User.class.getDeclaredFields();
for (Field field: fields) {
Annotation annotation = field.getAnnotation(JsonAlias.class);
if (annotation != null) {
String fieldName = field.getName();
JsonAlias jsonAliasAnnotation = (JsonAlias) annotation;
String[] aliases = jsonAliasAnnotation.value();
for (String alias: aliases) {
fieldAliases.put(alias, fieldName);
}
}
}
return fieldAliases;
}
}
有了这个序列化器,给定一个 User
class 类似于:
import java.time.LocalDate;
import com.fasterxml.jackson.annotation.JsonAlias;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
@JsonDeserialize(using = UserDeserializer.class)
public class User {
private String firstName;
private String lastName;
private Integer age;
private String address;
@JsonAlias("dateofbirth")
private LocalDate dateOfBirth;
// Setters and getters omitted for brevity
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
User user = (User) o;
if (firstName != null ? !firstName.equals(user.firstName) : user.firstName != null) return false;
if (lastName != null ? !lastName.equals(user.lastName) : user.lastName != null) return false;
if (age != null ? !age.equals(user.age) : user.age != null) return false;
if (address != null ? !address.equals(user.address) : user.address != null) return false;
return dateOfBirth != null ? dateOfBirth.equals(user.dateOfBirth) : user.dateOfBirth == null;
}
@Override
public int hashCode() {
int result = firstName != null ? firstName.hashCode() : 0;
result = 31 * result + (lastName != null ? lastName.hashCode() : 0);
result = 31 * result + (age != null ? age.hashCode() : 0);
result = 31 * result + (address != null ? address.hashCode() : 0);
result = 31 * result + (dateOfBirth != null ? dateOfBirth.hashCode() : 0);
return result;
}
以及以下 JSON(我更改为 dateofbirth
字段的名称只是为了测试别名):
{"firstName":"John","age":40,"dateofbirth":"1978-03-16"}
您应该获得适当的结果,请考虑以下测试:
public static void main(String... args) throws JsonProcessingException {
User user = new User();
user.setFirstName("John");
user.setAge(40);
user.setDateOfBirth(LocalDate.of(1978, Month.MARCH, 16));
ObjectMapper mapper = new ObjectMapper();
mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
String json = "{\"firstName\":\"John\",\"age\":40,\"dateofbirth\":\"1978-03-16\"}";
User reconstructed = mapper.readValue(json, User.class);
System.out.println(user.equals(reconstructed));
}
最后,请注意,为了让您的 @KafkaListener
处理 null
值,您必须使用带有 required = false
的 @Payload
注释,例如:
public class KafkaConsumer {
@Autowired
private UserRepository userRepository;
@KafkaListener(topics = "${spring.kafka.topic.name}")
public void listen(@Payload(required = false) User user) {
// Handle null value
if (user == null) {
// Consider logging the event
// logger.debug("Null message received");
System.out.println("Null message received");
return;
}
// Continue as usual
User user = new User(user);
UserRepository.save(user.getName(), user);
}
}
参见相关的 Spring Kafka documentation and this Github issue and the related commit. 也可能相关。
CustomDeserializer
是为类型 String
定义的,它用于反序列化 User
对象。这就是为什么反序列化器在应用时在单个 User
字段上工作,而不是在整个 User
对象上工作的原因。为了在整个 User
对象上应用反序列化器,CustomDeserializer
应该是 User
类型。像这样:
public class CustomDeserializer extends JsonDeserializer<User> {
@Override
public User deserialize(JsonParser jsonParser, DeserializationContext context) throws
IOException {
JsonNode node = jsonParser.readValueAsTree();
String firstName = null;
String lastName = null;
String age = null;
String address = null;
if(node.has("firstName") && !node.get("firstName").asText().isEmpty()) {
firstName = node.get("firstName").asText();
}
if(node.has("lastName") && !node.get("lastName").asText().isEmpty()) {
lastName = node.get("lastName").asText();
}
if(node.has("age") && !node.get("age").asText().isEmpty()) {
age = node.get("age").asText();
}
if(node.has("address") && !node.get("address").asText().isEmpty()) {
address = node.get("address").asText();
}
if(firstName == null && lastName == null && age == null && address == null) {
return null;
}
return new User(firstName, lastName, age, address);
}
}
现在,这可用于反序列化整个 User
对象:
示例输入:
{
"firstName" : "",
"lastName" : "Paul",
"age" : "31"
}
将被反序列化为:
User{firstName='null', lastName='Paul', age='31', address='null'}
我创建了一个新的反序列化器,可以将空字符串写成 null
public class CustomDeserializer extends JsonDeserializer<String> {
@Override
public String deserialize(JsonParser jsonParser, DeserializationContext context) throws IOException {
JsonNode node = jsonParser.readValueAsTree();
if (node.asText().isEmpty()) {
return null;
}
return node.toString();
}
}
试图在每个用户字段上制作单个注释,自定义工作但通过在整个 class 上插入注释,我无法再打印 Json 消息
@JsonDeserialize(using = CustomDeserializer.class)
public class User {
private String firstName;
private String lastName;
private String age;
private String address; }
CustomExceptionHandler 向我抛出此错误:Class MethodArgumentNotValidException 这是我的 Kafka 消费者,这是我唯一输入验证注释的消费者,但即使删除它也会给我同样的错误
public class KafkaConsumer {
@Autowired
private UserRepository userRepository;
@KafkaListener(topics = "${spring.kafka.topic.name}")
public void listen(@Validated User user) {
User user = new User(user);
UserRepository.save(user.getName(), user);
}
}
对象映射器
public ObjectMapper getObjectMapper() {
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JavaTimeModule());
mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
mapper.setSerializationInclusion(Include.NON_NULL);
return mapper;
}
是否可以让它在整个 class 中发挥作用?
如果您希望空 String
表示 整个 对象被视为 null
,您可以启用 ACCEPT_EMPTY_STRING_AS_NULL_OBJECT
Jackson deserialization feature,默认禁用。
您可以在配置 ObjectMapper
时包含它:
public ObjectMapper getObjectMapper() {
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JavaTimeModule());
mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
mapper.setSerializationInclusion(Include.NON_NULL);
// Enable ACCEPT_EMPTY_STRING_AS_NULL_OBJECT deserialization feature
mapper.enable(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT);
return mapper;
}
如上所述,当您想将表示 整个 对象的空 String
视为 null
时,它很有用;但是,它不适用于 String
类型的单个属性:在后一种情况下,您可以安全地使用自定义反序列化器,因此,解决方案实际上是两种方法的混合,使用 ACCEPT_EMPTY_STRING_AS_NULL_OBJECT
反序列化处理整个对象的功能,以及处理单个 String
属性的自定义反序列化器。
请参阅 this and
您也可以改进自定义 User
反序列化器。请考虑例如(为了清楚起见,我将名称重构为 UserDeserializer
):
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import com.fasterxml.jackson.annotation.JsonAlias;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonNode;
public class UserDeserializer extends JsonDeserializer<User> {
@Override
public User deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
JsonNode node = jsonParser.readValueAsTree();
Iterator<String> fieldNames = node.fieldNames();
// Process Jackson annotations looking for aliases
Map<String, String> fieldAliases = this.getAliases();
User user = new User();
boolean anyNonNull = false;
// Iterate over every field. The deserialization process assume simple properties
while(fieldNames.hasNext()) {
String fieldName = fieldNames.next();
JsonNode fieldValue = node.get(fieldName);
String fieldValueTextRepresentation = fieldValue.asText();
if (fieldValueTextRepresentation != null && !fieldValueTextRepresentation.trim().isEmpty()) {
// Check if the field is aliased
String actualFieldName = fieldAliases.get(fieldName);
if (actualFieldName == null) {
actualFieldName = fieldName;
}
this.setFieldValue(user, actualFieldName, fieldValueTextRepresentation);
anyNonNull = true;
}
}
return anyNonNull ? user : null;
}
// Set field value via Reflection
private void setFieldValue(User user, String fieldName, String fieldValueTextRepresentation) {
try {
Field field = User.class.getDeclaredField(fieldName);
Object fieldValue = null;
Class clazz = field.getType();
// Handle each class type: probably this code can be improved, but it is extensible and adaptable,
// you can include as many cases as you need.
if (clazz.isAssignableFrom(String.class)) {
fieldValue = fieldValueTextRepresentation;
} else if (clazz.isAssignableFrom(LocalDate.class)) {
// Adjust the date pattern as required
// For example, if you are receiving the information
// like this: year-month-day, as in the provided example,
// you can use the following pattern
fieldValue = LocalDate.parse(fieldValueTextRepresentation, DateTimeFormatter.ofPattern("yyyy-MM-dd"));
} else if (clazz.isAssignableFrom(Integer.class)) {
fieldValue = Integer.parseInt(fieldValueTextRepresentation);
}
field.setAccessible(true);
field.set(user, fieldValue);
} catch (Exception e) {
// Handle the problem as appropriate
e.printStackTrace();
}
}
/* Look for Jackson aliases */
private Map<String, String> getAliases() {
Map<String, String> fieldAliases = new HashMap<>();
Field[] fields = User.class.getDeclaredFields();
for (Field field: fields) {
Annotation annotation = field.getAnnotation(JsonAlias.class);
if (annotation != null) {
String fieldName = field.getName();
JsonAlias jsonAliasAnnotation = (JsonAlias) annotation;
String[] aliases = jsonAliasAnnotation.value();
for (String alias: aliases) {
fieldAliases.put(alias, fieldName);
}
}
}
return fieldAliases;
}
}
有了这个序列化器,给定一个 User
class 类似于:
import java.time.LocalDate;
import com.fasterxml.jackson.annotation.JsonAlias;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
@JsonDeserialize(using = UserDeserializer.class)
public class User {
private String firstName;
private String lastName;
private Integer age;
private String address;
@JsonAlias("dateofbirth")
private LocalDate dateOfBirth;
// Setters and getters omitted for brevity
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
User user = (User) o;
if (firstName != null ? !firstName.equals(user.firstName) : user.firstName != null) return false;
if (lastName != null ? !lastName.equals(user.lastName) : user.lastName != null) return false;
if (age != null ? !age.equals(user.age) : user.age != null) return false;
if (address != null ? !address.equals(user.address) : user.address != null) return false;
return dateOfBirth != null ? dateOfBirth.equals(user.dateOfBirth) : user.dateOfBirth == null;
}
@Override
public int hashCode() {
int result = firstName != null ? firstName.hashCode() : 0;
result = 31 * result + (lastName != null ? lastName.hashCode() : 0);
result = 31 * result + (age != null ? age.hashCode() : 0);
result = 31 * result + (address != null ? address.hashCode() : 0);
result = 31 * result + (dateOfBirth != null ? dateOfBirth.hashCode() : 0);
return result;
}
以及以下 JSON(我更改为 dateofbirth
字段的名称只是为了测试别名):
{"firstName":"John","age":40,"dateofbirth":"1978-03-16"}
您应该获得适当的结果,请考虑以下测试:
public static void main(String... args) throws JsonProcessingException {
User user = new User();
user.setFirstName("John");
user.setAge(40);
user.setDateOfBirth(LocalDate.of(1978, Month.MARCH, 16));
ObjectMapper mapper = new ObjectMapper();
mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
String json = "{\"firstName\":\"John\",\"age\":40,\"dateofbirth\":\"1978-03-16\"}";
User reconstructed = mapper.readValue(json, User.class);
System.out.println(user.equals(reconstructed));
}
最后,请注意,为了让您的 @KafkaListener
处理 null
值,您必须使用带有 required = false
的 @Payload
注释,例如:
public class KafkaConsumer {
@Autowired
private UserRepository userRepository;
@KafkaListener(topics = "${spring.kafka.topic.name}")
public void listen(@Payload(required = false) User user) {
// Handle null value
if (user == null) {
// Consider logging the event
// logger.debug("Null message received");
System.out.println("Null message received");
return;
}
// Continue as usual
User user = new User(user);
UserRepository.save(user.getName(), user);
}
}
参见相关的 Spring Kafka documentation and this Github issue and the related commit.
CustomDeserializer
是为类型 String
定义的,它用于反序列化 User
对象。这就是为什么反序列化器在应用时在单个 User
字段上工作,而不是在整个 User
对象上工作的原因。为了在整个 User
对象上应用反序列化器,CustomDeserializer
应该是 User
类型。像这样:
public class CustomDeserializer extends JsonDeserializer<User> {
@Override
public User deserialize(JsonParser jsonParser, DeserializationContext context) throws
IOException {
JsonNode node = jsonParser.readValueAsTree();
String firstName = null;
String lastName = null;
String age = null;
String address = null;
if(node.has("firstName") && !node.get("firstName").asText().isEmpty()) {
firstName = node.get("firstName").asText();
}
if(node.has("lastName") && !node.get("lastName").asText().isEmpty()) {
lastName = node.get("lastName").asText();
}
if(node.has("age") && !node.get("age").asText().isEmpty()) {
age = node.get("age").asText();
}
if(node.has("address") && !node.get("address").asText().isEmpty()) {
address = node.get("address").asText();
}
if(firstName == null && lastName == null && age == null && address == null) {
return null;
}
return new User(firstName, lastName, age, address);
}
}
现在,这可用于反序列化整个 User
对象:
示例输入:
{
"firstName" : "",
"lastName" : "Paul",
"age" : "31"
}
将被反序列化为:
User{firstName='null', lastName='Paul', age='31', address='null'}