java - Jakson 多态枚举案例

标签 java spring-boot jackson polymorphism json-deserialization

我对 Jackson 的多态问题很着迷。

我从事网络 JDR 角色编辑器个人项目。我使用 Springboot 并尝试坚持其哲学。此外,我尝试制作一些独立的包,因为我的实际工作(另一个 springboot 项目)的研究案例。

在没有 Jackson 配置的情况下,我的能力序列化没有问题。但是,当我尝试取回 Web 编辑器上的任何修改时,当 Jackson 对 Competence 进行反序列化时,“依赖”属性就会出现问题。

这是我的类(class):

我尝试序列化/反序列化的:

public class Competence implements Composante, ComposanteTemplate {

    public enum Categorie {
        APPRENTI,
        COMPAGNON
    }

    private String nom;
    private String description;
    private Categorie categorie;
    private Chapitre chapitre;
    private AttributTemplate dependance;
    private List sousCompetences = new ArrayList();

    public String getNom() {
        return nom;
    }

    public void setNom(String nom) {
        this.nom = nom;
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }

    public Competence getTemplate() {
        return this;
    }

    public Categorie getCategorie() {
        return categorie;
    }

    public void setCategorie(Categorie categorie) {
        this.categorie = categorie;
    }

    public Chapitre getChapitre() {
        return chapitre;
    }

    public void setChapitre(Chapitre chapitre) {
        this.chapitre = chapitre;
    }

    public AttributTemplate getDependance() {
        return dependance;
    }

    public void setDependance(AttributTemplate dependance) {
        this.dependance = dependance;
    }

    public List getSousCompetences() {
        return sousCompetences;
    }

    public void setSousCompetences(List sousCompetences) {
        this.sousCompetences = sousCompetences;
    }

    public boolean isOuverte() {
        return !sousCompetences.isEmpty();
    }
}

The superclass of the property I have a problem with:

public interface AttributTemplate extends ComposanteTemplate {}

The two subclasses which could be use for Competence#dependance property:

public enum Carac implements AttributTemplate, Attribut {

    FORT(Type.PHYSIQUE),
    AGILE(Type.PHYSIQUE),
    RESISTANT(Type.PHYSIQUE),
    OBSERVATEUR(Type.PHYSIQUE),
    SAVANT(Type.MENTALE),
    RUSE(Type.MENTALE),
    TALENTUEUX(Type.MENTALE),
    CHARMEUR(Type.MENTALE);

    public enum Type {
        PHYSIQUE,
        MENTALE
    }

    public final Type type;
    public final String nom = name().toLowerCase();

    private String description;

    Carac(Type type) {
        this.type = type;
    }

    @Override
    public String getNom() { return nom; }

    @Override
    public String getDescription() { return description;  }

    @Override
    public Carac getTemplate() { return this; }

    public void setDescription(String description) { this.description = description; }

}
public enum ArtTemplate implements AttributTemplate {

    ART_GUERRIER(2, 1),
    ART_ETRANGE(1, 2),
    ART_GUILDIEN(1, 1);

    public static final String ART_PREFIX = "ART";

    public final String nom = name().toLowerCase().replace("_", " ");
    public final int nbCaracsPhysiques;
    public final int nbCaracsMentales;

    private String description;

    ArtTemplate(int nbCaracsPhysiques, int nbCaracsMentales) {
        this.nbCaracsMentales = nbCaracsMentales;
        this.nbCaracsPhysiques = nbCaracsPhysiques;
    }

    @Override
    public String getNom() {
        return nom;
    }

    @Override
    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }

    public int getNbCaracs() {
        return nbCaracsPhysiques + nbCaracsMentales;
    }

}

The result json (and then the json I send) is:

{"nom":"Comp_1489746646510","description":"ezbuixnwrclfvmgwdviubcauenzytpzzvumnohwyhpuynxaqhkjdbqygtrmbtlschthovuyoiolkauucwokkfjnaujnufshrjboykuqce","categorie":"APPRENTI","chapitre":"GUERRE","dependance":"ART_ETRANGE","ouverte":false,"sousCompetences":[]}

QUESTION: I understand that my problem is caused by the abstract relation AttributTemplate, and then when Jackson try to deserialize, he does not know which of Carac or ArtTemplate class to use. I try to keep unchanged Competence (Competence come from an external jar), so no annotation on this class is possible.

I've tried many of the solutions I found (Jackson 1.5: Polymorphic Type Handling, first steps ) and the only one which has worked was to define a DeserializationProblemHandler

mapper.addHandler(new DeserializationProblemHandler() {
 @Override
 public Object handleMissingInstantiator(DeserializationContext ctxt, Class<?> instClass, JsonParser p, String msg) throws IOException {
     if (instClass == AttributTemplate.class) {
         String name = p.getText();
         return !name.startsWith(ArtTemplate.ART_PREFIX) ? Carac.valueOf(name) : ArtTemplate.valueOf(name);
     }
     return super.handleMissingInstantiator(ctxt, instClass, p, msg);
 }

});

但我对这个解决方案感觉不好,因为我确信还有另一个漂亮的解决方案。

那么是否可以配置映射器,以便他能够确定他必须使用 Carac 或 ArtTemplate 中的哪一个来获取 AttributTemplate?

<小时/>

编辑: 我设法做到了:

{"nom":"Comp_1489756873433","description":"kruzueemlwisibshlkotasayfkhdqkqolvhlqgsnntndkpvbmmgklqysabiakaolempmupeyiqaztdcrhwimdksgzybbdzttwnwqjxhfo","categorie":"COMPAGNON","chapitre":"GUERRE","dependance":["mova.ged.perso.inne.Carac","AGILE"],"ouverte":true,"sousCompetences":[...]}

通过这样配置映射器

    abstract class CompetenceMixIn {

        private AttributTemplate dependance;

        @JsonTypeInfo(use=JsonTypeInfo.Id.CLASS, include=JsonTypeInfo.As.EXISTING_PROPERTY, property="dependance")
        @JsonSubTypes({ @JsonSubTypes.Type(value = Carac.class, name = "carac"), @JsonSubTypes.Type(value = ArtTemplate.class, name = "artTemplate") })
        public void setDependance(AttributTemplate dependance) {
            this.dependance = dependance;
        }
    }
ObjectMapper mapper = jsonConverter.getObjectMapper();
mapper.addMixIn(Competence.class, CompetenceMixIn.class);

如您所见,我仍然寄生在包装 dependance 值的数组中。我会 (...)"dependance": "AGILE", (...) 而不是 (...)"dependance":["mova.ged.perso.inne. Carac", "AGILE"], (...)
我不知道要改变什么才能得到这个。

最佳答案

我一直在研究你想做什么。不幸的是,我认为枚举+继承存在问题。

我有一个您可以使用的替代解决方案,即使用自定义创建器并忽略未知属性。请参阅以下示例:

public class JacksonInheritance {

    public static void main(String[] args) throws IOException {
        ObjectMapper mapper = new ObjectMapper();

        Competence c = new Competence();
        c.desc = "desc";
        c.nome = "nome";
        c.template = Att1.TEST_Att1;
        String test = mapper.writeValueAsString(c);
        System.out.println(test);

        Competence readValue = mapper.readValue(test, Competence.class);
        System.out.println(readValue.template);
    }

    @JsonIgnoreProperties(ignoreUnknown = true)
    public static class Competence {

        private static final Map<String, AttributeTemplate> templates;
        static {
            templates = new HashMap<>();
            Stream.of(Att1.values()).forEach( a -> templates.put(a.name(), a));
            Stream.of(Att2.values()).forEach( a -> templates.put(a.name(), a));
        }

        @JsonProperty
        String nome;
        @JsonProperty
        String desc;
        @JsonIgnore
        AttributeTemplate template;

        @JsonProperty("template_type")
        public String getTempl() {
            // Here you can do whichever way uou would like to serialise your template. This will be the key
            return template.toString();
        }

        @JsonCreator
        public static Competence create(@JsonProperty("template_type") String templateType) {
            Competence c = new Competence();
            c.template =  templates.get(templateType);
            return c;
        }
    }

    public static interface AttributeTemplate {
    }

    public static enum Att1 implements AttributeTemplate {
        TEST_Att1;
    }

    public static enum Att2 implements AttributeTemplate {

        TEST2_Att2;
    }
}

在这里,我将枚举逻辑与 jackson 逻辑分离并实现我自己的逻辑。这不需要自定义序列化。

我基本上是说,我将枚举序列化为其值(您显然可以选择您想要的属性)。

我的输出 json 如下所示:

{"template_type":"TEST_Att1","nome":"nome","desc":"desc"}

在返回步骤中,我现在知道从 template_type 属性构造正确的枚举模板类型所需的信息。这就是我可以注入(inject)到我的工厂方法 create 中的内容。

在创建中,我可以使用静态创建的映射将正确的枚举填充到我的对象中。我们可以静态地创建这个映射,因为我们的枚举是有限的和静态的。

这样做的好处还在于生成器仅用于创建。使用@JsonIgnoreProperties(ignoreUnknown = true),我们可以告诉 Jackson 不要被 json 中的所有自定义元素吓坏。它只会反序列化它可以检测到的任何字段并保留其他字段(因为我们使用自定义 template_type 进行枚举解析)。

最后,我忽略了 bean 中实际的模板,因为 jackson 无法构造它。

我希望这对您有用/对您有帮助。抱歉耽搁了。

不使用继承的原因:

  1. jackson 中的枚举+继承似乎存在问题。特别是 Jackson 默认使用反射并调用枚举的私有(private)构造函数进行生成。不过,您也许可以让创作者以与上述类似的方式工作。

  2. 反序列化需要模板。我假设您不一定想要序列化枚举的所有元素。这是因为枚举名称(在我的例子中 TEST_Att1)使枚举变得唯一。无需序列化并发送这些枚举所具有的所有不同属性。然而,Deserialization with @JsonSubTypes for no value - missing property error显示 Jackson 要求您的模板字段至少存在。这是一个小问题,因为您想为此使用外部属性(所以为什么要按照 json 中的建议包含一个空字段,只是为了让 jackson 高兴)

这可能不是最好的解决方案,但考虑到限制,我认为它相对优雅。希望对您有帮助,

阿图尔

关于java - Jakson 多态枚举案例,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42855646/

相关文章:

java - Jsoup:select()在不应该的时候返回空

java - 从java运行.exe文件将pdf转换为Excel

spring-boot - ApiSelectorBuilder 类型中的方法 apis(java.util.function.Predicate<springfox.documentation.RequestHandler>) 不适用

java - 如何在未知 XML 标签上失败?

java - 是什么导致了 java.lang.NoSuchMethodError : com. fastxml.jackson.core.JsonFactory.requiresPropertyOrdering()Z

java - 如何解决以 ImageIO 插件为原因的 OutOfMemoryError?

java - 如何检查子对象是否来自特定类(而不是父类)(Java)

spring - 为什么使用 Spring Boot 而不是 Spring Boot-less?

spring - 如何在spring boot mvc中执行请求之前修改Http headers

java - 使用 Jackson 反序列化(未知属性失败)不会忽略鉴别器属性(DTO 使用 SwaggerCodegen 创建)