简体   繁体   English

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

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

I am attempting to implement the HL7 FHIR spec's assertion that JSON representing a FHIR model will not have empty objects nor empty arrays .我正在尝试实现HL7 FHIR 规范的断言,即代表 FHIR model 的 JSON 不会有空对象,也不会有空 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.为了不让我的消费者的生活变得更加困难,我在反序列化期间没有严格执行这一点,但我想确保我的库生成的序列化 JSON 符合规定。 I am using Java and Jackson ObjectMapper to serialize Objects into JSON.我正在使用 Java 和Jackson ObjectMapper 将对象序列化为 JSON。 My understanding from writing a custom serializer is that the Object is at one point represented as JsonNode, regardless of what you are converting to.我对编写自定义序列化程序的理解是,Object 在某一时刻表示为 JsonNode,无论您要转换为什么。

What I would like to do is intercept the JsonNode as it exits the serializer, make some adjustments to it (find and remove empty arrays and objects), and then let it continue on its way.我想做的是在 JsonNode 退出序列化程序时拦截它,对其进行一些调整(查找并删除空的 arrays 和对象),然后让它继续前进。 I need to do this in an environment where I can't tweak the ObjectMapper, because I don't have access to it.我需要在无法调整 ObjectMapper 的环境中执行此操作,因为我无权访问它。 And further, the complex hierarchy of models in this library use Jackson's default serialization with annotations etc. heavily, and I cannot eliminate this.此外,该库中模型的复杂层次结构大量使用 Jackson 的默认序列化和注释等,我无法消除这一点。

If I go the route of defining a custom serializer for the base type, let's say "Resource", then I have a problem, because I still need the original serializer's output in order to generate my modified output.如果我 go 为基本类型定义自定义序列化程序的路线,比如说“资源”,那么我就有问题了,因为我仍然需要原始序列化程序的 output 才能生成我修改后的 Z78E6221F6398F1CE63 And further, that needs to accommodate any custom serializers that may already exist on various types within the model.此外,这需要适应 model 中各种类型可能已经存在的任何自定义序列化程序。

I got pretty far with the above option using https://www.baeldung.com/jackson-call-default-serializer-from-custom-serializer and the last option, implementing BeanSerializerModifier, but I ran into the issue where I can't control the ObjectMapper that my library consumers use.使用https://www.baeldung.com/jackson-call-default-serializer-from-custom-serializer和最后一个选项,实现 BeanSerializerModifier,我在上面的选项中走了很远,但我遇到了我不能的问题t 控制我的库使用者使用的 ObjectMapper。

Example POJOs (Using Lombok for accessors): 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;
  }
}

And an example instance of the SomethingElse class:还有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<>());

When I tell any mapper (or, for example, use a Spring service) to map the SomethingElse class into JsonNode, I can, for example, end up with empty objects and arrays, like this: When I tell any mapper (or, for example, use a Spring service) to map the SomethingElse class into JsonNode, I can, for example, end up with empty objects and arrays, like this:

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

Becomes:变成:

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

According to FHIR, this should actually look like:根据 FHIR,这实际上应该如下所示:

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

To summarize总结

How do I preserve the serialization mechanisms already in place, regardless of the ObjectMapper used, and somehow remove empty lists and objects from outgoing JSON produced by the Jackson serialization process?无论使用何种 ObjectMapper,我如何保留已经存在的序列化机制,并以某种方式从 Jackson 序列化过程产生的传出 JSON 中删除空列表和对象?

Edit: I also tried @JsonInclude(JsonInclude.Include.NON_EMPTY) , which did omit empty list implementations.编辑:我也尝试@JsonInclude(JsonInclude.Include.NON_EMPTY) ,它确实省略了空列表实现。 However, the vast majority of data in this library is represented by POJOs that serialize to maps and primitives, and this annotation only works if they are represented directly by maps and primitives in the model.然而,这个库中的绝大多数数据是由序列化为映射和图元的 POJO 表示的,并且只有当它们直接由 model 中的映射和图元表示时,此注释才有效。

The solution is to use a custom @JsonInclude , which is new in Jackson 2.9 .解决方案是使用自定义@JsonInclude ,这是Jackson 2.9 中的新功能 Thank you @dai for pointing me back towards this functionality.感谢@dai 将我指向此功能。

On the base Resource class, this looks like:在基础资源 class 上,如下所示:

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

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

For visibility, the interface used above:为了可见性,上面使用的界面:

interface FhirTypeInterface {
  boolean isEmpty();
}

And my custom definition for FhirJsonValueFilter implements all of the functionality of JsonInclude.Include.NON_EMPTY but also adds functionality for checking against a method implemented by FHIR types (implementation of this is not relevant to the answer).我对 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;
    }
}

Note that the custom omission filter uses Java's Object.equals functionality, where true means to omit the property, and I've used a second method to reduce confusion in this answer.请注意,自定义省略过滤器使用 Java 的 Object.equals 功能,其中 true 意味着省略该属性,我使用了第二种方法来减少此答案中的混淆。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM