spring-projects / spring-data-redis

Provides support to increase developer productivity in Java when using Redis, a key-value store. Uses familiar Spring concepts such as a template classes for core API usage and lightweight repository style data access.
https://spring.io/projects/spring-data-redis/
Apache License 2.0
1.77k stars 1.17k forks source link

When json is deserialized, the JsonDeserializer returned in the custom ContextualDeserializer is not executed. #2978

Open LkZtCode opened 3 months ago

LkZtCode commented 3 months ago

this is my custom ContextualDeserializer image

christophstrobl commented 3 months ago

If you'd like us to spend some time investigating, please take the time to provide a complete minimal sample (something that we can unzip or git clone, build, and deploy) that reproduces the problem.

LkZtCode commented 3 months ago

this my custom @interface

import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.trustasia.cloudpki.common.base.core.localizedtime.jsonformat.deserialize.LocalizedJsonFormatDeserialize;
import com.trustasia.cloudpki.common.base.core.localizedtime.jsonformat.serialize.LocalizedJsonFormatSerialize;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@JacksonAnnotationsInside
@JsonSerialize(using = LocalizedJsonFormatSerialize.class)
@JsonDeserialize(using = LocalizedJsonFormatDeserialize.class)
public @interface LocalizedJsonFormat {

    String DEFAULT_PATTERN = "yyyy-MM-dd HH:mm:ss";

    String pattern() default DEFAULT_PATTERN;
}

this is custom JsonDeserializer

@Slf4j
public class LocalizedDateDeserialize extends JsonDeserializer<Date> {
    private String pattern;

    private ZoneId defaultZoneid = ZoneId.systemDefault();
    private SimpleDateFormat defaultSimpleDateFormat;

    private CommonSecurity commonSecurity;

    public LocalizedDateDeserialize(String pattern) {
        this.pattern = pattern;
        this.defaultSimpleDateFormat = new SimpleDateFormat(pattern);
        this.commonSecurity = SpringUtils.getBean(CommonSecurity.class);
    }

    @Override
    public Date deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JacksonException {
        String dateStr = jsonParser.getText().trim();
        if (StringUtils.isBlank(dateStr)) {
            return null;
        }
        // 尝试获取时区ID,若无则使用默认处理逻辑
        ZoneId targetZoneId = commonSecurity.getCurrentTimeZoneId()
                .or(() -> Optional.of(defaultZoneid.getId()))
                .map(ZoneId::of)
                .orElseGet(() -> defaultZoneid);

        if (defaultZoneid.equals(targetZoneId)) {
            return generateDefaultDate(dateStr);
        }

        EnhanceSimpleDateFormat enhanceSimpleDateFormat = new EnhanceSimpleDateFormat(this.pattern, targetZoneId);
        try {
            return enhanceSimpleDateFormat.parse(dateStr);
        } catch (ParseException e) {
            log.error("LocalizedDateDeserialize parse Date error", e);
            throw new LocalizedJsonFormatException(String.format("Failed to parse Date value '%s'", dateStr));
        }
    }

    private Date generateDefaultDate(String dateStr) {
        try {
            return defaultSimpleDateFormat.parse(dateStr);
        } catch (ParseException e) {
            log.error("LocalizedDateDeserialize parse Date error", e);
            throw new LocalizedJsonFormatException(String.format("Failed to parse Date value '%s'", dateStr));
        }
    }
}
@NoArgsConstructor
@Slf4j
public class LocalizedJsonFormatDeserialize extends JsonDeserializer<Object> implements ContextualDeserializer {

    @Override
    public Object deserialize(JsonParser p, DeserializationContext deserializationContext) throws IOException, JacksonException {
        throw new LocalizedJsonFormatException("this is proxy deserializer,not support deserialize");
    }

    @Override
    public JsonDeserializer<?> createContextual(DeserializationContext deserializationContext, BeanProperty beanProperty) throws JsonMappingException {
        if (beanProperty != null) {
            if (Objects.equals(beanProperty.getType().getRawClass(), Date.class)) {
                LocalizedJsonFormat localizedJsonFormat = beanProperty.getAnnotation(LocalizedJsonFormat.class);
                if (localizedJsonFormat == null) {
                    localizedJsonFormat = beanProperty.getContextAnnotation(LocalizedJsonFormat.class);
                }
                if (localizedJsonFormat != null) {
                    return new LocalizedDateDeserialize(localizedJsonFormat.pattern());
                }
            }
            return deserializationContext.findContextualValueDeserializer(beanProperty.getType(), beanProperty);
        }
        return deserializationContext.findNonContextualValueDeserializer(null);
    }
}

in entity used @LocalizedJsonFormat

@Getter
@Setter
public class OpenApiBaseDomain implements Serializable {
    public Long id;
    /**
     * 创建时间
     */
    @LocalizedJsonFormat
    private Date createAt;
    /**
     * 更新时间
     */
    @LocalizedJsonFormat
    private Date updateAt;
    private Boolean deleted;
    /**
     * 版本号
     */
    private Long version;
}

In the mvc framework, @LocalizedJsonFormat will follow custom logic, but in redis deserialization, @LocalizedJsonFor will not follow custom logic.

I added the relevant logic of custom annotations to the createContextual of DateBasedDeserializer. Redis will use the custom logic when serializing.

image

So I think that the logic of custom JsonDeserializer is implemented during the deserialization calling process of redis.