根据与Jackson的API版本指定不同的JSON属性名称 [英] Specifying different JSON property names according to API version with Jackson

查看:112
本文介绍了根据与Jackson的API版本指定不同的JSON属性名称的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我需要能够使用Jackson同时支持多个API版本来序列化/反序列化对象。我已经探索过如下解决方案:

I need to be able to support multiple API versions at the same time using Jackson to serialize/deserialize objects. I've explored solutions like:


  • @JsonProperty

  • PropertyNamingStrategy

  • Mixin注释

然而,每一个都会导致问题。 @JsonProperty将是一个完美的解决方案,如果我可以直接在注释中添加具有正确名称的多个版本:

However, each one causes its own problems. @JsonProperty would be a perfect solution if I could add, say, multiple versions with the correct names directly in the annotation:

@JsonProperty(api="1.5", "fname")
@JsonProperty(api="1.6", "firstname")
String firstName;

随着时间的推移,可能会有很长的一段时间,但它肯定很容易理解。但是,这似乎不太可能。

Over time that might get a big long, but it would certainly be easy to understand. However, it doesn't seem like that's possible.

PropertyNamingStrategy和mixins也是一个好主意。事实上,我尝试了mixin注释(例如,使用不同的JSON属性名继承模型)并且它们有效,但这两个都是解决方案遇到一个问题。你必须在某处指定和使用ObjectMapper(也可能是ObjectReader / Writer)。

PropertyNamingStrategy and mixins would also be a good idea. In fact, I tried mixin annotations (e.g., Inherit model with different JSON property names) and they worked, but both of these solutions suffer from one problem. You have to specify and use an ObjectMapper (and possibly ObjectReader/Writer, too) somewhere.

这很痛苦,因为对象的层次结构如下所示:

This is a pain, because the hierarchy of objects looks like this:

实体

| - 用户

| - -Group

|--Group

| --Credential

|--Credential

等。实体包含公共属性,例如名称,ID,描述,状态和API版本。假设您现在执行以下操作:

etc. Entity contains the common properties, things like name, id, description, state, and API version. Let's say you now do the following:

User user = new User("catherine", "stewardess", "active");
user.setApiVersion(Entity.ApiVersion.V2);
if(user.getVersion() == Entity.ApiVersion.V2) {
    MAPPER.addMixin(Entity.class, EntityMixinV2.class);
}
String userJson = MAPPER.writeValueAsString(user);
User user2 = MAPPER.readValue(userJson);
System.out.println(MAPPER.writeValueAsString(user2));

其中 MAPPER 只是其他地方定义的ObjectMapper和$ code> EntityMixinV2 类似于:

where MAPPER is just an ObjectMapper defined elsewhere and EntityMixinV2 is something like:

public abstract class EntityMixinV2 {

    @JsonProperty("employmentState")
    String state;
}

覆盖其中一个变量(在这种情况下, state )在User的父类Entity中。这有几个问题:

to override one of the variables (in this case, state) in User's parent class, Entity. There are several problems with this:


  • 每次版本号上升时,你必须添加另一个if块

  • 这只处理基类。如果子类需要更改一些属性怎么办?如果只有少数几个类,这不是问题,但很快就会失控。

  • 更重要的是,将mapper / reader / writer设置为正确的类来映射将是一个巨大的痛苦和错误的来源,因为有人会不可避免地忘记这样做。此外,将mapper,reader或writer放在类本身中只会导致堆栈溢出或无限递归。

  • 最后,根据API版本和类正在设置正确的类map必须手动完成,除非我遗漏了什么。反思可以解决这个问题但是


    • a)它很慢并且这种东西经常被称为使用它并且

    • b)如果它在父类中使用,它将无法看到子类,因此强制您在子类本身中使用反射来设置映射器。

    • every time the version number goes up, you have to add another if block
    • this only handles the base class. What if the child class need some properties changed? If there are only a handful of classes, it's not a problem, but it could quickly get out of control.
    • more importantly, setting the mapper/reader/writer to the correct class to map would be a huge pain and source of bugs because someone would inevitably forget to do so. Also, putting either the mapper, reader, or writer in the class itself just causes a stack overflow or infinite recursion.
    • finally, setting the correct class according to the API version and the class being mapped would have to be done manually unless I'm missing something. Reflection would solve this but
      • a) it's slow and this stuff would be called way too often to consider using it and
      • b) if it's used in the parent class, it would be unable to see the child class, thus forcing you to use reflection in the child class itself to set the mapper.

      理想情况下,我希望能够使用上面的@JsonProperty示例,因为名称更改是问题,而不是我用变量做什么我还考虑过使用

      Ideally, I would like to be able to use something like the @JsonProperty example above, because the name changes are the issue, not what I do with variables. I've also considered using the

      @JsonSerialize(using = EntitySerializer.class)
      @JsonDeserialize(using = EntityDeserializer.class)
      

      注释,但这只会改变变量的内容,而不是属性名称本身。

      annotations, but that would just change the content of the variables, not the property names themselves.

      推荐答案

      您可以创建自定义注释来定义字段的可能版本的属性名称列表并创建自定义 JacksonAnnotationIntrospector 根据给定版本解析属性名称。

      You can create a custom annotation to define a list of possible version of property name for a field and create a custom JacksonAnnotationIntrospector to resolve the property name according to a given version.

      自定义注释看起来像:

      @Retention(RetentionPolicy.RUNTIME)
      @Target({ElementType.FIELD})
      public @interface VersioningProperties {    
          Property[] value();
      
          @interface Property {
              String version();
              String value();
          }
      }
      

      和自定义 JacksonAnnotationIntrospector 将如下所示:

      public class VersioningPropertiesIntrospector {
          private String version;
      
          public VersioningPropertiesIntrospector(String version) {
              this.version = version;
          }
      
          @Override
          pubilc PropertyName findNameForSerialization(Annotated a) {
              PropertyName propertyName = findNameFromVersioningProperties(a);
              if (propertyName != null) {
                  return propertyName;
              }
              return super.findNameForSerialization(a);
          }
      
          @Override
          pubilc PropertyName findNameForDeserialization(Annotated a) {
              PropertyName propertyName = findNameFromVersioningProperties(a);
              if (propertyName != null) {
                  return propertyName;
              }
              return super.findNameForDeserialization(a);
          }
      
          private PropertyName findNameFromVersioningProperties(Annotated a) {
              VersioningProperties annotation = a.getAnnotation(VersioningProperties.class);
              if (annotation == null) {
                  return null;
              }
              for (Property property : annotation.value()) {
                  if (version.equals(property.version()) {
                      return new PropertyName(property.value());
                  }
              }
              return null;
          }
      }
      

      使用注释的示例:

      public class User {
          @VersioningProperties({
              @Property(version = "1.5", value = "fname"),
              @Property(version = "1.6", value = "firstName")
          })
          private String firstName;
      
          // constructors, getters and setters
      }
      

      以及使用introspector使用 ObjectMapper 的示例:

      and an example of using ObjectMapper with the introspector:

      User user = new User();
      user.setFirstName("catherine");
      user.setVersion("1.5");
      
      ObjectMapper mapper = new ObjectMapper();
      mapper.setAnnotationIntrospector(new VersioningPropertiesIntrospector(user.getVersion()));
      
      String userJson = mapper.writeValueAsString(user);
      User userRead = mapper.readValue(userJson, User.class);
      

      你也可以考虑实施一个工厂来获得 ObjectMapper 传递版本信息。

      You may also consider implement a factory to get ObjectMapper by passing the version information.

      这篇关于根据与Jackson的API版本指定不同的JSON属性名称的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆