【问题标题】:Jackson: How do I post-process JsonNode during serialization?Jackson:如何在序列化过程中对 JsonNode 进行后处理?
【发布时间】: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 文档中提到的一些内容,这使我得到了这个答案,这可能会使注释起作用:stackoverflow.com/a/31034525/1708977 不幸的是,这也需要我修改映射器,我无法访问。

标签: java json jackson hl7-fhir


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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-02-21
    • 1970-01-01
    • 2014-07-15
    • 1970-01-01
    • 2012-02-23
    • 2020-06-26
    • 1970-01-01
    相关资源
    最近更新 更多