JsonDeserializer <t>反序列化方法未在缺少属性

时间:2017-10-29 23:44:31

标签: java spring-boot jackson

我有一个简单但复杂的问题:是否可以在缺少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-String中的&#34;语言&#34; -property正确转换为语言对象 - 如果属性存在,则 。 但是:如果属性缺失或为空,则不会调用LanguageDeserializer #deserialize方法,并且RegrisrableProfile中的&#34; language&#34; -attribute保持为空。

我可能通过检查language-attribute是否为&#34; null&#34;来解决这个问题。在我的ProfileService方法中并将其设置在那里,如果是这样,但它似乎不像在缺少的属性上调用自定义反序列化器一样干净。

另一个想法(仍然是一种解决方法)是为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或默认语言对象。

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

1 个答案:

答案 0 :(得分:0)

缺少语言属性更新

如果您希望缺少语言属性,可以编写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);
    }
}

Spring配置

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