如何通过 Jackson 动态过滤属性

问题描述 投票:0回答:1

假设有一个如下所示的类:

public class Employee {

    private String name;
    private boolean nameSpecified;

    private LocalDateTime birthday;
    private boolean birthdaySpecified;

    private String jobTitle;
    private boolean jobTitleSpecified;

    public String getName() {
        if (!nameSpecified) {
            throw new IllegalStateException("The property `name` is not specified");
        }
        return name;
    }

    public void setName(String name) {
        this.name = name;
        this.nameSpecified = true;
    }

    public LocalDateTime getBirthday() {
        if (! birthdaySpecified) {
            throw new IllegalStateException("The property `birthday` is not specified");
        }
        return birthday;
    }

    public void setBirthday(LocalDateTime name) {
        this.birthday = birthday;
        this.birthdaySpecified = true;
    }

    public String getJobTitle() {
        if (!jobTitleSpecified) {
            throw new IllegalStateException("The property `jobTitle` is not specified");
        }
        return jobTitle;
    }

    public void setJobTitle(String jobTitle) {
        this.jobTitle = jobTitle;
        this.jobTitleSpecified = true;
    }

    @Override
    public String toString() {
        StringBuilder builder = new StringBuilder();
        builder.append("Employee");
        String separator = " {";
        if (nameSpecified) {
            builder.append(separator).append("name=").append(name);
            separator = ", ";
        }
        if (birthdaySpecified) {
            builder.append(separator).append("birthday=").append(birthday);
            separator = ", ";
        }
        if (jobTitleSpecified) {
            builder.append(separator).append("jobTitle=").append(jobTitle);
        }
        return builder.append("}").toString();
    }
}

很明显,Employee对象不是POJO,一些属性没有指定或设置为null是完全不同的

这是ORM中流行的设计

  • 未指定:属性值未知
  • Null:值属性已知,没有数据。

例如,从 hibernate3 开始,它被惰性 scalar 属性使用。

这实际上是一种使对象动态化,同时保留强类型语言优点的方法。

显然,默认的Jackson无法序列化/反序列化这个对象,并且Jackson的行为需要被覆盖,未指定的属性必须被跳过(就像

toString
的逻辑)

未指定的属性需要被Jackson忽略,这与@JsonFilter的要求非常相似,唯一的区别是过滤规则不是固定的,而是每个对象都是独立的。

我尝试通过自定义 Jackson 模块注册自定义序列化器和反序列化器来解决此问题。然而,我的自定义读/写策略并不像标准 Jackson 行为那么全面,并且不可避免地破坏了一些高级 Jackson 功能,例如 @JsonAlias、@JsonView、继承和多态性。

因此,我需要实现动态对象过滤而不破坏任何高级 Jackson 功能,就好像每个对象都有自己的@JsonFilter

有没有一种优雅的方式来实现我的要求?


其实上面提到的类是从我自己的框架中提取出来的逻辑等价物,非常简单,适合作为一个独立的问题来讨论。在我的框架中,为了处理此类动态对象,我自定义了 ImmutableSerializerImmutableDeserializer 来处理动态对象的 Jackson 序列化/反序列化。然而,我的方法无法适应所有高级 Jackson 功能,并且用户报告了过去两年与某些高级 Jackson 功能被破坏相关的错误,这一直困扰着我。

java json jackson
1个回答
0
投票

如果我答对了你的问题,你可以这样做:

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.BeanSerializerModifier;
import java.io.IOException;

public class SerializerTest {
    public static void main(String[] args) throws Exception {
        // Create an instance of MyEntity with name and age
        MyEntity entity = new MyEntity("John Doe", 30);

        // Create a customized ObjectMapper for serialization
        ObjectMapper mapper = createObjectMapper();

        // Serialize the entity to JSON and print the output
        String json = mapper.writeValueAsString(entity);
        System.out.println(json);
    }

    public static ObjectMapper createObjectMapper() {
        // Instantiate a new ObjectMapper
        ObjectMapper mapper = new ObjectMapper();

        // Configure the mapper to ignore empty properties for serialization
        mapper.setSerializationInclusion(JsonInclude.Include.NON_EMPTY);

        // Register a module to customize serialization for Property class
        mapper.registerModule(new SimpleModule() {
            @Override
            public void setupModule(SetupContext context) {
                super.setupModule(context);
                
                // Register a serializer modifier to handle instances of Property class
                context.addBeanSerializerModifier(new BeanSerializerModifier() {
                    @Override
                    public JsonSerializer<?> modifySerializer(
                            SerializationConfig config, BeanDescription desc, JsonSerializer<?> serializer) {
                        // If the property being serialized is of type Property, use the custom PropertySerializer
                        if (Property.class.isAssignableFrom(desc.getBeanClass())) {
                            return new PropertySerializer((JsonSerializer<Object>) serializer);
                        }
                        // Otherwise, use the default serializer
                        return serializer;
                    }
                });
            }
        });
        return mapper;
    }
}

// Represents an entity with name and age properties, using the Property wrapper
class MyEntity {
    private final Property<String> name;
    private final Property<Integer> age;

    // Constructor initializing name and age with the Property wrapper
    public MyEntity(String name, Integer age) {
        this.name = new Property<>(name);
        this.age = new Property<>(age);
    }

    // Getters for name and age properties
    public Property<String> getName() {
        return name;
    }

    public Property<Integer> getAge() {
        return age;
    }
}

// A generic wrapper class for entity properties, tracking if a value has been explicitly set
class Property<T> {
    private T value; // The actual value of the property
    private boolean isSet = false; // Flag to track if the value has been explicitly set

    // Constructor that initializes the property value and marks it as set
    public Property(T value) {
        setValue(value); // Using setValue ensures isSet is correctly updated
    }

    // Getter for the property value
    public T getValue() {
        return value;
    }

    // Setter for the property value, also marks the property as set
    public void setValue(T value) {
        this.value = value;
        this.isSet = true;
    }

    // Checks if the property has been explicitly set
    public boolean isSet() {
        return isSet;
    }
}

// Custom JsonSerializer for the Property class to handle serialization based on the isSet flag
class PropertySerializer extends JsonSerializer<Property<?>> {
    private final JsonSerializer<Object> defaultSerializer; // The default serializer for the actual value of the property

    // Constructor receiving the default serializer to be used for the property's value
    public PropertySerializer(JsonSerializer<Object> serializer) {
        defaultSerializer = serializer;
    }

    // Custom serialization logic that only serializes the property if it has been explicitly set
    @Override
    public void serialize(Property<?> value, JsonGenerator gen, SerializerProvider provider) throws IOException {
        if(!value.isSet()) {
            return; // Skip serialization if the property has not been set
        }

        // Use the default serializer to serialize the value of the property
        defaultSerializer.serialize(value.getValue(), gen, provider);
    }

    // Determines if a Property is considered empty (not set), used to decide if a property should be omitted
    @Override
    public boolean isEmpty(SerializerProvider provider, Property<?> value) {
        return !value.isSet();
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.