定制的jackson bean序列化程序,可以基于同级属性的值动态忽略某些带注释的属性

2020年8月23日 17点热度 0条评论

因此,我有一个名为MyClass的java类型,它具有一些要序列化的属性。它还具有另一个属性,该属性决定其他属性是否应该序列化。是否可以编写一个自定义的序列化器来实现此规则,即仅在条件匹配时才对Java类型的属性进行序列化?例如我还有另一个拥有ID列表的同级属性。如果要序列化的当前属性具有(通过注释)分配的ID作为该列表的一部分,则我不想序列化它。

我的java课...

public class MyClass implements WithFieldsToHide {

  // serializable fields

  @FieldId(id = "one")
  private String name;

  @FieldId(id = "two")
  private String company;

  public String getName() { return name; }

  public String getCompany() { return company; }


  // internal fields, non-serializable

  @JsonIgnore
  private Set<String> fieldsToHide;

  public Set<String> getFieldsToHide() { return fieldsToHide; }

}

一个简单的界面,支持隐藏字段...

public interface WithFieldsToHide {
  Set<String> getFieldsToHide();
}

简单的注解...

@Target(value = { ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER })
public @interface FieldId {
  String id();
}

用法...

public static void main(String args...) {
  ObjectMapper objectMapper = new ObjectMapper();

  MyClass testObject = new MyClass();

  // assume the setters are available
  testObject.setName("Jackson");
  testObject.setCompany("NoBitMedia");
  testObject.setFieldsToHide(new HashSet<String>() {{ add("two"); }});

  String serialized = objectMapper.writeValueAsString(testObject);
  // serialized should equal `{"name":"Jackson"}`
}

我尝试过的几件事:

我可以扩展
BeanSerializerModifier并覆盖
modifySerializer(),但是我没有可用的实际对象来读取该
fieldsToHide属性(在此基础上,我将决定是否进行序列化)。

我可以编写一个自定义转换器(扩展
StdConverter<MyClass, MyClass>,但在
convert()方法中没有注释的属性列表。

我可以编写一个自定义的序列化器(
MyClassSerializer<T extends WithFieldsToHide> extends JsonSerializer<T>),但是在这里,我也不确定在
serialize()方法中是否可以使用属性列表及其批注。

在尝试深入研究杰克逊代码之前,想问一下这种动态序列化是否完全可能..或有什么建议可以让我得到类似的结果?

解决方案如下:

尽管我早些时候确实尝试覆盖了BeanPropertyWriter,但是无法真正弄清楚如何完成我想做的事情。

这就是我最后所做的。

接口,用于实现Bean类是否需要动态隐藏其字段。

public interface WithFieldsToHide {
  boolean hide(String fieldId);
}

放置在需要隐藏的字段上的注释。

@Target(value = { ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER })
public @interface HideFieldId {
  String id();
}
public class CustomFieldHideModule extends Module {
  @Override
  public void setupModule(SetupContext context) {
    context.addBeanSerializerModifier(new CustomFieldHideSerializerModifier());
  }

  private static class CustomFieldHideSerializerModifier extends BeanSerializerModifier {

    @Override
    public List<BeanPropertyWriter> changeProperties(SerializationConfig config, BeanDescription beanDesc, List<BeanPropertyWriter> beanProperties) {
      // only try to hide fields for class of a particular interface type
      if (WithFieldsToHide.class.isAssignableFrom(beanDesc.getBeanClass())) {
        for (int i = 0; i < beanProperties.size(); i++) {
          BeanPropertyWriter writer = beanProperties.get(i);
          // to be able to run the logic to hide, we need to know if the property has a field id assigned
          HideFieldId annotation = writer.getAnnotation(HideFieldId.class);
          if (null != annotation) {
            beanProperties.set(i, new ConditionalPropertyWriter<>(writer, annotation.id()));
          }
        }
      }
      return beanProperties;
    }

    private static class ConditionalPropertyWriter<T extends WithFieldsToHide> extends BeanPropertyWriter{

      private final fieldId;
      protected ConditionalPropertyWriter(BeanPropertyWriter writer, String fieldId) {
        super(writer);
        this.fieldId = fieldId;
      }

      @Override
      public void serializeAsField(Object bean, JsonGenerator jgen, SerializerProvider prov) throws Exception {
        T withFieldsToHide = (T) bean; // don't need type check as we're checking when registering this writer for the property
        if (!withFieldsToHide.isInternal(fieldId)) {
          super.serializeAsField(bean, jgen, prov);
        }
      }
    }
  }
}

然后,您只需在对象映射器中注册该模块,瞧!

对于地图属性类型,我还希望有一个更高的隐藏级别,在这里我只需要隐藏map属性中的某些元素,而不是整个属性本身,并可以编写一些内容来实现它。让我知道是否有人想要,为简洁起见不包括在内。

谢谢!!