JsonDeserializer不适用于该类,而仅适用于该类的单个元素 [英] JsonDeserializer does not work on the Class but only on the single element of the class

查看:159
本文介绍了JsonDeserializer不适用于该类,而仅适用于该类的单个元素的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我创建了一个新的反序列化器,以便能够将空字符串写为null

I created a new Deserializer to be able to make empty strings be written as 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();
    }
}

尝试在每个用户字段上创建单个注释,自定义"起作用,但是通过在整个类上插入注释,我将不再打印Json消息

Trying to make the single annotation on each User field, the Custom works but by inserting the annotation on the whole class, I can no longer print the Json message

@JsonDeserialize(using = CustomDeserializer.class)
public class User {
    private String firstName;
    private String lastName;
    private String age;
    private String address; }

CustomExceptionHandler引发此错误:类MethodArgumentNotValidException 这是我的Kafka Consumer,是我唯一输入验证注释的人,但即使删除它也会给我同样的错误

The CustomExceptionHandler throws me this error :Class MethodArgumentNotValidException This is my Kafka Consumer, the only one where I have entered a validation annotation, but even removing it gives me the same error

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);
    }
}

ObjectMapper

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);
    return mapper;
}

是否有可能使它在全班学习?

Is it possible to make it work across the whole class?

推荐答案

如果要将表示 whole 对象的空 String 视为 null ,则可以启用 ACCEPT_EMPTY_STRING_AS_NULL_OBJECT Jackson 反序列化功能,默认情况下处于禁用状态.

If you want an empty String representing the whole object to be treated as null, you can enable the ACCEPT_EMPTY_STRING_AS_NULL_OBJECT Jackson deserialization feature, disabled by default.

您可以在配置 ObjectMapper 时将其包括在内:

You can include it when configuring your 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 属性的自定义反序列化器.

As abovementioned, it is useful when you want to treat an empty String representing the whole object as null; however, it will not work for individual properties of type String: in the later case you can safely use your custom deserializer, so, the solution is in fact a mix of both approaches, use the ACCEPT_EMPTY_STRING_AS_NULL_OBJECT deserialization feature to deal with the whole object, and your custom deserializer for handling individual String properties.

请参阅其他相关的SO问题.

Please, see this and this other related SO questions.

您也可以改进自定义的 User 解串器.请考虑一下示例(为清楚起见,我将名称重构为 UserDeserializer ):

You can improve your custom User deserializer as well. Please, consider for example (I refactored the name to UserDeserializer for clarity):

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 类:

With this serializer in place, given a User class similar to:

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 字段的名称):

And the following JSON (I changed to name of the dateofbirth field just for testing aliases):

{"firstName":"John","age":40,"dateofbirth":"1978-03-16"}

您应该获得适当的结果,请考虑以下测试:

You should obtain the appropriate results, consider the following test:

  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 值,必须使用 @Payload 批注使用 required = false ,类似:

Finally, please, be aware that in order to allow your @KafkaListener to handle null values, you must use the @Payload annotation with required = false, something like:

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 文档和此 Github问题和相关的这个问题也可能是相关的.

See the relevant Spring Kafka documentation and this Github issue and the related commit. This SO question could be relevant as well.

这篇关于JsonDeserializer不适用于该类,而仅适用于该类的单个元素的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆