如何在序列化过程中跳过某些类型的字段?

How can I skip fields of certain types during serialization?

提问人:Eugene_Z 提问时间:11/4/2023 最后编辑:kaqqaoEugene_Z 更新时间:11/7/2023 访问量:70

问:

我有一个Spring AOP服务,它可以拦截许多不同的第三方控制器端点。我的服务运行一个端点,获取结果 DTO,使用 ObjectMapper 将 DTO 序列化为 json,将 json 发送到 Kafka 并在外部返回 DTO。问题是:服务不应该序列化特定类型的字段(例如 MultipartFile)。我不能使用@JsonIgnore注释,因为 DTO 应该在不做更改的情况下返回外部。 我可以让 ObjectMapper 跳过某些类型的字段吗?或者,也许,复制 DTO 并将此字段设置为带有反射的 null?

java json 序列化 反射 jackson

评论

0赞 kriegaex 11/4/2023
您的完整、最小的复制器在哪里?
0赞 Eugene_Z 11/4/2023
Kriegaex,对不起,什么的复制品?
0赞 CaptainAye 11/4/2023
这有帮助吗?stackoverflow.com/questions/65568597/......
0赞 kriegaex 11/5/2023
当然,@Eugene_Z你的情况。没有人可以调试和纠正不可见的代码。请了解如何就 SO 提出问题,并提供一个最小、完整且可验证的示例。谢谢。
0赞 kaqqao 11/6/2023
@kriegaex 问题是如何配置杰克逊。无需调试。

答:

1赞 kaqqao 11/6/2023 #1

永远记住,杰克逊是字面上的魔法,是无穷无尽的可定制:)

有很多方法可以实现你想要的。

假设您有以下 DTO 类,并且想要排除:File

public class SimpleDTO {

    private final File file;
    private final String name;

    @JsonCreator
    public SimpleDTO(@JsonProperty("file") File file, @JsonProperty("name") String name) {
        this.file = file;
        this.name = name;
    }

    public File getFile() {
        return file;
    }

    public String getName() {
        return name;
    }
}
混合

您可以创建混音,例如:

@JsonIgnoreType
public interface IgnoreTypeMixin {}

然后在序列化时使用它:

//Pretend the File class was annotated by @JsonIgnoreType
ObjectMapper mapper = new ObjectMapper().addMixIn(File.class, IgnoreTypeMixin.class);

String serialized = mapper.writeValueAsString(new SimpleDTO(new File("/tmp/simple.txt"), "A kewl name"));
System.out.println(serialized); //Prints {"name":"A kewl name"}

除了 ,您还可以将 或 或任何其他 Jackson 注释中的字段混合在一起。以下是用于忽略字段的注释的概述@JsonIgnoreType@JsonIgnorefileSimpleDTO@JsonIgnoreProperties(value = { "file" })

注意:不要每次都创建一个新的。配置一个实例进行序列化并永久共享。ObjectMapper

编程配置

混合允许您从外部添加注释,但注释仍然是静态配置。如果您必须动态选择要忽略的字段(不仅按类型),那么注释(无论是否混合)都是不够的。杰克逊,作为魔术和一切,让你以编程方式实现任何可以通过注释完成的事情:

public class FieldFilteringIntrospector extends NopAnnotationIntrospector {

    private final Set<Class<?>> ignoredTypes;

    public FieldFilteringIntrospector(Set<Class<?>> ignoredTypes) {
        this.ignoredTypes = Collections.unmodifiableSet(ignoredTypes);
    }

    public FieldFilteringIntrospector(Class<?>... ignoredTypes) {
        this.ignoredTypes = Arrays.stream(ignoredTypes).collect(Collectors.toUnmodifiableSet());
    }

    @Override
    public Boolean isIgnorableType(AnnotatedClass ac) {
        return ignoredTypes.contains(ac.getRawType());
    }
}

然后在序列化时使用它:

SimpleModule module = new SimpleModule("type-filter-module") {
    @Override
    public void setupModule(SetupContext context) {
        super.setupModule(context);
        context.insertAnnotationIntrospector(new FieldFilteringIntrospector(File.class));
    }
};
ObjectMapper mapper = new ObjectMapper().registerModule(module);

String serialized = mapper.writeValueAsString(new SimpleDTO(new File("/tmp/simple.txt"), "A kewl name"));
System.out.println(serialized); //Prints {"name":"A kewl name"}

通过覆盖 中的其他方法,可以模拟其他注释和其他方法。NopAnnotationIntrospector@JsonIgnore

注意:同样,仅创建一次。ObjectMapper

评论

0赞 Eugene_Z 11/7/2023
谢谢!这真的是很好的方法。不幸的是,我在 YAML 属性文件中有可更改的禁止类列表。所以,我无法创建混合列表。在下面的回答中,我使用了另一种方法。
-1赞 Eugene_Z 11/7/2023 #2

我在 YAML 属性文件中有可更改的禁止类列表,所以我不能使用 mixin。任务是这样解决的:

@Value("${classes-to-remove}")
private String classesToRemove;

public String getClearedJson(MyDTO myDTO) {
    List<String> fieldNamesToRemove = new ArrayList<>();

    for (Field field: myDTO.getClass().getDeclaredFields()) {
        if (classesToRemove.contains(field.getType().getSimpleName())) {
            fieldNamesToRemove.add(field.getName());
            continue;
        }

        if (field.getGenericType() instanceof ParameterizedType) {
            ParameterizedType listType = (ParameterizedType) field.getGenericType();
            Class<?> listClass = (Class<?>) listType.getActualTypeArguments()[0];
            if (classesToRemove.contains(listClass.getSimpleName())) {
                fieldNamesToRemove.add(field.getName());
            }
        }
    }

    String json = objectMapper.writeValueAsString(myDTO);
    JsonNode jsonNode = objectMapper.readTree(json);
    ObjectNode object = (ObjectNode) jsonNode;
    fieldNamesToRemove.forEach(object::remove);
    String updatedJson = objectMapper.writeValueAsString(object);
    
    return updatedJson;
}

评论

0赞 kaqqao 11/7/2023
我向你展示了 2 种非常优雅的方法来排除动态可配置的类。不知道为什么你更喜欢这个彻头彻尾的黑客解决方案。例如,如果不同包中的类的简单名称匹配,您将错误地排除它们,您只处理一个级别的泛型,并且只处理 etc。至少,不要只 - 直接使用。ParameterizedTypewriteValueAsStringreadTreevalueToTree
0赞 Sachin De Silva 11/7/2023 #3

您可以使用对象映射器来克服此问题,也可以使用 getDeclaredFields 方法来构建逻辑以删除不需要的属性并返回