Jackson:如何在序列化期间 post-process JsonNode?

Jackson: How do I post-process JsonNode during serialization?

我正在尝试实现 HL7 FHIR spec's assertion that JSON representing a FHIR model will not have empty objects nor empty arrays. For the sake of not making the lives of my consumers any harder, I'm not strictly enforcing this during deserialization, but I want to ensure the serialized JSON produced by my library conforms as specified. I am using Java and Jackson ObjectMapper 以将对象序列化为 JSON。我对编写自定义序列化程序的理解是,对象在某一时刻表示为 JsonNode,无论您要转换成什么。

我想做的是在 JsonNode 退出序列化程序时拦截它,对其进行一些调整(查找并删除空数组和对象),然后让它继续运行。我需要在无法调整 ObjectMapper 的环境中执行此操作,因为我无权访问它。此外,这个库中复杂的模型层次结构大量使用 Jackson 的默认序列化和注释等,我无法消除这一点。

如果我采用为基类型定义自定义序列化程序的方式,比方说 "Resource",那么我就会遇到问题,因为我仍然需要原始序列化程序的输出才能生成修改后的输出。此外,这需要适应模型中各种类型可能已经存在的任何自定义序列化程序。

我使用 https://www.baeldung.com/jackson-call-default-serializer-from-custom-serializer 和最后一个选项实现了 BeanSerializerModifier,但我 运行 遇到了无法控制我的库使用者使用的 ObjectMapper 的问题.

示例 POJO(使用 Lombok 作为访问器):

@Data
@JsonInclude(JsonInclude.Include.NON_EMPTY)
@JsonIgnoreProperties(ignoreUnknown = true)
abstract class Resource {
  private FhirString id;
  private List<Extension> extension;

  @JsonProperty(access = JsonProperty.Access.READ_ONLY)
  public abstract ResourceType getResourceType();
}
@Data
@Builder
class SomethingElse extends Resource {
  FhirUri someProperty;
  CodeableConcept someCode;
  List<Reference> someReferences;

  @Override
  public ResourceType getResourceType() {
    return ResourceType.SOMETHING_ELSE;
  }
}

以及 SomethingElse 的示例实例 class:

SomethingElse somethingElse = SomethingElse.builder()
    .someProperty(FhirUri.from("some-simple-uri"))
    .someCode(new CodeableConcept())
    .someReference(List.of(new Reference()))
    .build();
somethingElse.setId(FhirString.randomUuid());
somethingElse.setExtension(new ArrayList<>());

当我告诉任何映射器(或者,例如,使用 Spring 服务)将 SomethingElse class 映射到 JsonNode 时,我可以,例如,以空对象和数组结束,像这样:

ObjectMapper mapper = getUntouchableMapper();
JsonNode somethingElseNode = mapper.valueToTree(somethingElse);
System.out.println(somethingElseNode.toString());

变为:

{
  "resourceType": "SomethingElse",
  "id": "00000000-0002-0004-0000-000000000000",
  "someProperty": "some-simple-uri",
  "someCode": {},
  "someReferences": [{}],
  "extension": []
}

根据 FHIR,这实际上应该是这样的:

{
  "resourceType": "SomethingElse",
  "id": "00000000-0002-0004-0000-000000000000",
  "someProperty": "some-simple-uri"
}

总结

如何保留现有的序列化机制,而不管使用的是什么 ObjectMapper,并以某种方式从 Jackson 序列化过程生成的传出 JSON 中删除空列表和对象?

编辑: 我还尝试了 @JsonInclude(JsonInclude.Include.NON_EMPTY),它确实省略了空列表实现。但是,该库中的绝大多数数据由序列化为映射和基元的 POJO 表示,并且此注释仅在它们直接由模型中的映射和基元表示时才有效。

解决方案是使用自定义 @JsonInclude,即 new in Jackson 2.9。感谢@dai 将我重新指向此功能。

在基础资源 class 上,看起来像:

@JsonInclude(value = JsonInclude.Include.CUSTOM, valueFilter = FhirJsonValueFilter.class)
class Resource implements FhirTypeInterface {
  ...

  @Override
  public boolean isEmpty() {
    //Details omitted for simplicity
  }
}

为了可见性,上面使用的接口:

interface FhirTypeInterface {
  boolean isEmpty();
}

我对 FhirJsonValueFilter 的自定义定义实现了 JsonInclude.Include.NON_EMPTY 的所有功能,还添加了检查 FHIR 类型实现的方法的功能(此实现与答案无关)。

public class FhirJsonValueFilter {
    @Override
    public boolean equals(Object value) {
        return !getWillInclude(value);
    }

    /**
     * Returns true for an object that matched filter criteria (will be 
     * included) and false for those to omit from the response.
     */
    public boolean getWillInclude(Object value) {
        //Omit explicit null values
        if (null == value) {
            return false;
        }

        //Omit empty collections
        if (Collection.class.isAssignableFrom(value.getClass())) {
            return !((Collection) value).isEmpty();
        }

        //Omit empty maps
        if (Map.class.isAssignableFrom(value.getClass())) {
            return !((Map) value).isEmpty();
        }

        //Omit empty char sequences (Strings, etc.)
        if (CharSequence.class.isAssignableFrom(value.getClass())) {
            return ((CharSequence) value).length() > 0;
        }

        //Omit empty FHIR data represented by an object
        if (FhirTypeInterface.class.isAssignableFrom(value.getClass())) {
            return !((FhirTypeInterface) value).isEmpty();
        }

        //If we missed something, default to include it
        return true;
    }
}

请注意,自定义省略过滤器使用 Java 的 Object.equals 功能,其中 true 表示 省略 属性,并且我我使用了第二种方法来减少这个答案中的混淆。