Jakson 多态枚举案例

Posted

技术标签:

【中文标题】Jakson 多态枚举案例【英文标题】:Jakson polymorphic Enum case 【发布时间】:2017-08-08 21:36:42 【问题描述】:

我被一些杰克逊多态问题所困扰。

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

没有 Jackson 配置,我对 Competence 的序列化没有任何问题。但是当我尝试取回对网络编辑器的任何修改时,因此当 Jackson 对 Competence 进行反序列化时,“依赖”属性会出现问题。

这是我的课程:

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

public class Competence 实现 Composante, ComposanteTemplate 

    公共枚举类别
        学徒,
        比较
    

    私有字符串 nom;
    私有字符串描述;
    私人类别类别;
    私人礼拜堂;
    私有属性模板依赖;
    私有列表 sousCompetences = new ArrayList();

    公共字符串 getNom() 
        返回名称;
    

    公共无效setNom(字符串标称)
        this.nom = 标称;
    

    公共字符串 getDescription() 
        返回说明;
    

    公共无效 setDescription(字符串描述)
        this.description = 描述;
    

    公共能力 getTemplate() 
        返回这个;
    

    公共类别 getCategorie() 
        返回类别;
    

    公共无效setCategorie(类别类别)
        this.categorie = 类别;
    

    公共Chapitre getChapitre()
        返回章节;
    

    公共无效setChapitre(Chapitre chapitre)
        this.chapitre = chapitre;
    

    公共属性模板 getDependance() 
        返回依赖;
    

    公共无效setDependance(属性模板依赖)
        this.dependance = 依赖;
    

    公共列表 getSousCompetences() 
        返回sousCompetences;
    

    公共无效 setSousCompetences(列出 sousCompetences)
        this.sousCompetences = sousCompetences;
    

    公共布尔 isOuverte() 
        返回 !sousCompetences.isEmpty();
    

我有问题的属性的超类:

公共接口 AttributTemplate 扩展 ComposanteTemplate 

可用于 Competence#dependance 属性的两个子类:

公共枚举 Carac 实现 AttributTemplate, Attribut 

    堡垒(Type.PHYSIQUE),
    敏捷(Type.PHYSIQUE),
    抗性(类型.物理),
    OBSERVATEUR(Type.PHYSIQUE),
    SAVANT(Type.MENTALE),
    诡计(类型.MENTALE),
    TALENTUEUX(Type.MENTALE),
    CHARMEUR(Type.MENTALE);

    公共枚举类型
        体质,
        心理
    

    公共最终类型类型;
    public final String nom = name().toLowerCase();

    私有字符串描述;

    Carac(类型类型)
        this.type = 类型;
    

    @Override
    public String getNom()  return nom; 

    @Override
    public String getDescription()  返回描述; 

    @Override
    public Carac getTemplate()  return this; 

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

公共枚举 ArtTemplate 实现 AttributTemplate 

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

    公共静态最终字符串 ART_PREFIX = "ART";

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

    私有字符串描述;

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

    @Override
    公共字符串 getNom() 
        返回名称;
    

    @Override
    公共字符串 getDescription() 
        返回说明;
    

    公共无效 setDescription(字符串描述)
        this.description = 描述;
    

    公共 int getNbCaracs() 
        返回 nbCaracsPhysiques + nbCaracsMentales;
    

结果 json(然后是我发送的 json)是:

"nom":"Comp_1489746646510","description":"ezbuixnwrclfvmgwdviubcauenzytpzzvumnohwyhpuynxaqhkjdbqygtrmbtlschthovuyoioolkaucwokkfjnaujnufshrjboykuqce","categorie":"APPRENTI","chapitre":"GUERRE","ouvertance":"falseART":" "sousCompetences":[]

问题: 我知道我的问题是由抽象关系 AttributTemplate 引起的,然后当 Jackson 尝试反序列化时,他不知道要使用 Carac 或 ArtTemplate 类中的哪个。 我尽量保持 Competence 不变(Competence 来自外部 jar),因此无法对此类进行注释。

我已经尝试了许多我找到的解决方案 (Jackson 1.5: Polymorphic Type Handling, first steps),唯一有效的是定义一个 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.persoged. Carac","AGILE"],"ouverte":true,"sousCompetences":[...]

通过像这样配置映射器

抽象类 CompetenceMixIn 私有属性模板依赖; @JsonTypeInfo(使用=JsonTypeInfo.Id.CLASS,包括=JsonTypeInfo.As.EXISTING_PROPERTY,属性=“依赖”) @JsonSubTypes( @JsonSubTypes.Type(value = Carac.class, name = "carac"), @JsonSubTypes.Type(value = ArtTemplate.class, name = "artTemplate") ) 公共无效setDependance(属性模板依赖) this.dependance = 依赖;
ObjectMapper 映射器 = jsonConverter.getObjectMapper();
mapper.addMixIn(Competence.class, CompetenceMixIn.class);

如您所见,我仍然被包裹dependance 值的数组所寄生。我会(...)"dependance": "AGILE", (...) 而不是(...)"dependance":["mova.ged.perso.inne.Carac", "AGILE"], (...) 而且我不知道要改变什么才能拥有它。

【问题讨论】:

我之前回答过类似的问题。杰克逊允许这是可配置的。我不想为你的具体情况重新发明这个,所以请看这里:***.com/questions/38501574/… 那里的例子很简单。 Params 类有 2 个实现,我告诉杰克逊使用什么属性来查找如何反序列化它 我尝试了您的示例,但仍然找不到真正的解决方案。感谢您,我在找到解决方案的开头修改了我的帖子。 仍然有问题....我无法想象在我看来如此简单的事情会有这样的问题?? 【参考方案1】:

我一直在调查你想要做什么。不幸的是,我认为 Enums + 继承存在问题。

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

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;
    

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

我基本上说我将我的枚举序列化为它的值(你显然可以选择你想要的任何属性)。

然后我的输出 json 看起来像:

"template_type":"TEST_Att1","nome":"nome","desc":"desc"

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

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

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

最后,我忽略了 bean 中实际的 template,因为杰克逊无法构造它。

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

不使用继承的原因:

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

    反序列化需要模板。我假设您不一定要序列化枚举的所有元素。这是因为枚举名称(在我的情况下为 TEST_Att1)使枚举独一无二。无需序列化和发送这些枚举所具有的所有不同属性。但是,Deserialization with @JsonSubTypes for no value - missing property error 表明 jackson 要求您的模板字段至少存在。这是一个小问题,因为您想为此使用外部属性(那么为什么要在您的 json 中包含一个空字段只是为了让杰克逊高兴)

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

阿图尔

【讨论】:

如果你不能更改能力类,你可以扩展它并在该类中添加 jsoncreator。如果这不是一个选项,我认为您最初提出的解决方案非常好 我会试试你的选择,但我有点被我的工作垄断了:) 我终于(经过这么长时间!)尝试您的解决方案。正如你所说,我无法修改 Competence 类。然后我使用 MixIn 可能性来添加注释和创建者。但是好像创作者不是这样使用的。 我找到了一篇解释 JsonCreator 暂时无法通过 Mixin 关联的帖子。感谢您的帮助!

以上是关于Jakson 多态枚举案例的主要内容,如果未能解决你的问题,请参考以下文章

json jakson框架

@RequestBody jackson解析复杂的传入值的一个坑;jackson解析迭代数组;jackson多重数组;jakson数组

SpringMVC-处理JSON

装箱 拆箱 枚举 注解 多态

在这种情况下使用多态而不是枚举有啥好处吗?

JAVA之旅——多态的体现,前提,好处,应用,转型,instanceof,多态中成员变量的特点,多态的案例