【问题标题】:JsonDeserializer<T> deserialize-method not invoked on absent propertiesJsonDeserializer<T> 反序列化方法未在缺少的属性上调用
【发布时间】:2017-10-29 23:44:31
【问题描述】:

我有一个简单而复杂的问题:是否可以在缺少 JSON 属性时调用 JsonDeserializer?

为什么:我有一个 RegisterableProfile,它对应于用户填充并发送到服务器的表单。在最简单的形式中,它看起来像这样:

JSON-Request,将映射到 RegisterableProfile:

{
    "username": "someUser",
    "password": "somePass",
    "email": "some@mail.com",
    "language": "en"
}

它映射到的实体(RegisterableProfile):

@Getter
@Setter
@EqualsAndHashCode(callSuper = true)
@MappedSuperclass
public class RegisterableProfile extends CredentialsProfile {
    @Column(nullable = false, unique = true)
    private String email;

    @OneToOne
    @Convert(converter = LanguageConverter.class)
    private Language language;

    @Override
    public String toString() {
        return new StringBuilder(RegisterableProfile.class.getSimpleName())
                                                                           .append(" {")
                                                                           .append("name : '").append(getUsername()).append("',")
                                                                           .append("password : '").append(getPassword()).append("',")
                                                                           .append("email : '").append(email)
                                                                           .append("'}")
                                                                           .toString();
    }
}

请注意,CredentialsProfile 仅提供用户名和密码并与此类分开,因为在初始登录时可能会单独发生。

语言实体:

@JsonDeserialize(using = LanguageDeserializer.class)
@Data
@NoArgsConstructor
@Entity(name = "Languages")
public class Language {
    @Id
    @Column(nullable = false, unique = true)
    private String  languageCode;
    @Column(nullable = false, unique = true)
    private String  name;
    @Column(nullable = false, unique = true)
    private String  i18nName;
    private boolean localizeUi      = false;
    private boolean canChoose       = false;
    private boolean defaultLanguage = false;
}

使用 LanguageDeserializer 将 JSON 字符串中的“语言”属性正确转换为语言对象 - 如果该属性存在。 但是:如果 属性缺失或为空,则不会调用 LanguageDeserializer#deserialize 方法并且 RegisterableProfile 中的“语言”属性保持为空。

我可能可以通过检查我的 ProfileService 方法中的语言属性是否为“null”并将其设置在那里来解决这个问题,但它似乎不像在缺少的属性上调用自定义反序列化器那样干净.

另一个想法(仍然是一种解决方法)是为 RegisterableProfile 提供一个自定义反序列化器,并让它检测语言属性是否丢失或其他什么,但我不知道如何去做。

我的LanguageDeserializer

public class LanguageDeserializer extends JsonDeserializer<Language> {

    @Autowired
    private LanguageService languageService;

    @Override
    public Language deserialize(final JsonParser p, final DeserializationContext ctxt) throws IOException, JsonProcessingException {
        final JsonNode node = p.readValueAsTree();
        final String languageCode = node.asText();
        return languageService.getLanguage(languageCode);
    }
}

注意:languageService.getLanguage(...) 始终返回与 languageCode 匹配的 Language-object 或默认 Language-object。

此外:解决方案应该使用 spring-boot(及其库堆栈)。我目前使用的是 spring-boot 版本 1.5.7.RELEASE 和 2.8.10(与 spring-boot 捆绑)。

【问题讨论】:

    标签: java spring-boot jackson


    【解决方案1】:

    缺少语言属性更新

    如果您希望语言属性丢失,您可以编写一个BeanDeserializerModifier,它将RegisterableProfile 的创建委托给默认反序列化程序。它根据提供的值创建完整的Language 对象。如果完全缺少语言属性,请使用默认值。这里]1 是一个工作示例。

    RegisterableProfile 反序列化器委托给默认反序列化器

    package com.divstar.particle.authservice.rest.accountservice.mapper;
    
    import com.divstar.particle.authservice.rest.languageservice.LanguageService;
    import com.divstar.particle.authservice.rest.tos.RegisterableAccount;
    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.JsonMappingException;
    import com.fasterxml.jackson.databind.deser.ResolvableDeserializer;
    import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
    import java.io.IOException;
    
    public class RegisterableAccountDeserializer
            extends StdDeserializer<RegisterableAccount>
            implements ResolvableDeserializer {
    
        private final JsonDeserializer<?> defaultDeserializer;
    
        private LanguageService languageService;
    
        public RegisterableAccountDeserializer(
                JsonDeserializer<?> defaultDeserializer,
                LanguageService languageService) {
            super(RegisterableAccount.class);
            this.defaultDeserializer = defaultDeserializer;
            this.languageService = languageService;
        }
    
        @Override
        public RegisterableAccount deserialize(JsonParser parser,
                DeserializationContext context) throws IOException, JsonProcessingException {
            RegisterableAccount account =
                    (RegisterableAccount) defaultDeserializer.deserialize(parser,
                            context);
    
            if (account.getLanguage() != null && account.getLanguage().getLanguageCode() != null) {
                account.setLanguage(
                        languageService.getLanguage(
                                account.getLanguage().getLanguageCode()));
            } else {
                account.setLanguage(languageService.getLanguage("en"));
            }
    
            return account;
        }
    
        @Override
        public void resolve(
                DeserializationContext context) throws JsonMappingException {
            ((ResolvableDeserializer) defaultDeserializer).resolve(context);
        }
    }
    

    弹簧配置

    package com.divstar.particle.authservice.rest.config;
    
    import com.divstar.particle.authservice.rest.accountservice.mapper.RegisterableAccountDeserializer;
    import com.divstar.particle.authservice.rest.languageservice.LanguageService;
    import com.divstar.particle.authservice.rest.tos.RegisterableAccount;
    import com.fasterxml.jackson.databind.BeanDescription;
    import com.fasterxml.jackson.databind.DeserializationConfig;
    import com.fasterxml.jackson.databind.JsonDeserializer;
    import com.fasterxml.jackson.databind.deser.BeanDeserializerModifier;
    import com.fasterxml.jackson.databind.module.SimpleModule;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
    
    @Configuration
    public class SpringConfiguration {
    
        @Bean
        public Jackson2ObjectMapperBuilder objectMapperBuilder(
                final LanguageService service) {
            Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
    
            SimpleModule module = new SimpleModule();
            module.setDeserializerModifier(new BeanDeserializerModifier() {
                @Override
                public JsonDeserializer<?> modifyDeserializer(
                        DeserializationConfig config, BeanDescription beanDesc,
                        JsonDeserializer<?> deserializer) {
                    if (beanDesc.getBeanClass() == RegisterableAccount.class) {
                        return new RegisterableAccountDeserializer(deserializer,
                                service);
                    }
    
                    return deserializer;
                }
            });
    
            builder.modules(module);
    
            return builder;
        }
    }
    

    RegisterableAccount 的变化

    注释掉this.language = Language.getDefaultLanguage()这一行。

    public class RegisterableAccount extends Credentials {
        @Column(nullable = false, unique = true)
        private String email;
    
        @OneToOne
        @Convert(converter = LanguageConverter.class)
        private Language language;
    
        /**
         * Default constructor.
         */
        public RegisterableAccount() {
            // set language to default; will be overridden if valid languageCode is provided
            //this.language = Language.getDefaultLanguage();
        }
        ...
    
    }
    

    对 LanguageDeserializer 的更改

    public class LanguageDeserializer extends JsonDeserializer<Language> {
    
        @Override
        public Language deserialize(final JsonParser p, final DeserializationContext ctxt) throws IOException, JsonProcessingException {
            final JsonNode node = p.readValueAsTree();
            final String languageCode = node.asText();
    
            if (languageCode != null) {
                Language language = new Language();
                language.setLanguageCode(languageCode);
                return language;
            }
    
            return null;
        }
    }
    

    【讨论】:

    • 感谢您的回复!但是:我遇到了麻烦。我遵循了您的代码示例,甚至为 LanguageDeserializer 添加了一个无参数构造函数(因为否则我得到一个 JsonMappingException 说明没有无参数构造函数 oon 请求)。添加无参数构造函数后,根据请求,我收到:无法读取 HTTP 消息:org.springframework.http.converter.HttpMessageNotReadableException:缺少所需的请求正文:public ...PersistableProfile ...profileservice.ProfileController.registerAccount(. ..RegisterableProfile)。 getObject() 似乎也不完整。有什么想法吗?
    • 更正:在 spring-boot 启动时使用了 CustomObjectMapperFactory,它似乎成功地创建了一个包含 languageService 和 LanguageDeserializer 的自定义 ObjectMapper。但是:将空构造函数添加到 LanguageDeserializer 会使其中的 LanguageService 为空,从而导致 NullPointerException。删除它会导致 JsonMappingException 指出反序列化器没有无参数构造函数。我想知道为什么它试图构造另一个 LanguageDeserializer-object - 我以为我们使用 addDeserializer 来添加一个初始化的对象......?
    • 尝试使用默认构造函数在反序列化器中自动连接语言服务。看起来 Spring 正在尝试通过反射创建反序列化器(由 Spring 管理)。
    • 我试过了,我什至通过将 BeanFactory 注入 CustomObjectMapperFactory 并调用 beanFactory.getBean(LanguageDeserializer.class) 来获得 LanguageDeserializer 的实例。问题与开始时相同:如果 JSON-request(body) 中不存在属性“语言”,则不会调用 LanguageDeserializer#deserialize-method。如果是的话,一切照旧。
    • 更新了我的帖子。我测试了它。它现在应该可以工作了。
    猜你喜欢
    • 1970-01-01
    • 2021-06-11
    • 1970-01-01
    • 1970-01-01
    • 2021-12-13
    • 1970-01-01
    • 1970-01-01
    • 2021-12-12
    • 2016-08-15
    相关资源
    最近更新 更多