【问题标题】:Specifying different JSON property names according to API version with Jackson使用 Jackson 根据 API 版本指定不同的 JSON 属性名称
【发布时间】:2016-12-03 13:55:54
【问题描述】:

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

  • @JsonProperty
  • 属性命名策略
  • 混合注释

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

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

随着时间的推移,这可能会变得很长,但肯定会很容易理解。但是,这似乎是不可能的。

PropertyNamingStrategy 和 mixins 也是一个好主意。事实上,我尝试了 mixin 注释(例如,Inherit model with different JSON property names)并且它们起作用了,但是这两种解决方案都存在一个问题。您必须在某处指定和使用 ObjectMapper(也可能是 ObjectReader/Writer)。

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

实体

|--用户

|--组

|--凭证

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

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,EntityMixinV2 类似于:

public abstract class EntityMixinV2 {

    @JsonProperty("employmentState")
    String state;
}

覆盖 User 的父类 Entity 中的变量之一(在本例中为 state)。这有几个问题:

  • 每次版本号上升,你必须添加另一个 if 块
  • 这只处理基类。如果子类需要更改某些属性怎么办?如果只有少数几个类,这不是问题,但它可能很快就会失控。
  • 更重要的是,将映射器/读取器/写入器设置为正确的类来映射将是一个巨大的痛苦和错误的来源,因为有人不可避免地会忘记这样做。此外,将映射器、读取器或写入器放入类本身只会导致堆栈溢出或无限递归。
  • 最后,除非我遗漏了什么,否则必须手动根据 API 版本和被映射的类设置正确的类。反射会解决这个问题,但是
    • a) 它速度很慢,而且这种东西被称为过于频繁而无法考虑使用它,并且
    • b) 如果它在父类中使用,它将无法看到子类,从而迫使您在子类中使用反射来设置映射器。

理想情况下,我希望能够使用类似上面的 @JsonProperty 示例,因为名称更改是问题,而不是我对变量所做的事情。我也考虑过使用

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

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

【问题讨论】:

    标签: java json jackson


    【解决方案1】:

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

    自定义注解如下所示:

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

    以及将ObjectMapper 与内省器一起使用的示例:

    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

    【讨论】:

      猜你喜欢
      • 2019-06-14
      • 2021-07-23
      • 2020-12-26
      • 1970-01-01
      • 2019-05-09
      • 1970-01-01
      • 2015-06-05
      • 2012-08-21
      相关资源
      最近更新 更多