Jackson:如何在序列化过程中对 JsonNode 进行后处理?

Posted

技术标签:

【中文标题】Jackson:如何在序列化过程中对 JsonNode 进行后处理?【英文标题】:Jackson: How do I post-process JsonNode during serialization? 【发布时间】:2020-02-07 05:52:21 【问题描述】:

我正在尝试实现HL7 FHIR spec's assertion that JSON representing a FHIR model will not have empty objects nor empty arrays。为了不让我的消费者的生活更加困难,我没有在反序列化期间严格执行这一点,但我想确保我的库生成的序列化 JSON 符合指定的要求。我正在使用 Java 和 Jackson ObjectMapper 将对象序列化为 JSON。我对编写自定义序列化程序的理解是,无论您要转换为什么,Object 在某一时刻都表示为 JsonNode。

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

如果我为基本类型定义自定义序列化程序,比如说“资源”,那么我就有问题了,因为我仍然需要原始序列化程序的输出来生成我修改后的输出。此外,这需要适应模型中各种类型可能已经存在的任何自定义序列化程序。

我在使用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 类的示例实例:

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 类映射到 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 表示的,并且只有当它们直接由模型中的映射和图元表示时,此注解才有效。

【问题讨论】:

@JsonSerialize(include = Inclusion.NON_EMPTY) @dai JsonSerialize.Inclusion 已弃用,但将其现代等效项 @JsonInclude(JsonInclude.Include.NON_EMPTY) 放在我的模型上仅适用于地图、列表和 srting,不适用于序列化为地图、列表或字符串的对象.它在序列化中过早地执行检查。我将在原始问题中对此进行说明。 这确实揭示了NON_EMPTY 文档中提到的一些内容,这使我得到了这个答案,这可能会使注释起作用:***.com/a/31034525/1708977 不幸的是,这也需要我修改映射器,我无法访问。 【参考方案1】:

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

在基础 Resource 类中,如下所示:

@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 表示 省略 属性,我使用了第二种方法来减少此答案中的混淆。

【讨论】:

以上是关于Jackson:如何在序列化过程中对 JsonNode 进行后处理?的主要内容,如果未能解决你的问题,请参考以下文章

FastJSONGson和Jackson性能对比

如何优雅的实现数据脱敏

如何在 Jackson 中使用自定义序列化程序?

如何仅序列化 Jackson 的孩子的 ID

如何在 Jackson 中记录 JSON 反序列化

让springboot序列化空值null为"null"而不是空报文体(jackson)