【问题标题】:Custom serializer with fallback to default serialization回退到默认序列化的自定义序列化程序
【发布时间】:2019-11-20 21:18:04
【问题描述】:

我有一个对象可能需要以不同方式序列化的情况。具体来说,一个 REST 端点正在获取一个标头,如果标头为真,我们以一种方式序列化,如果为假,我们以另一种方式序列化。我认为如果我们将 header 属性放在对象中,自定义序列化器可能是完美的,但使用它时遇到了一些麻烦。

(请注意,由于使用了一些泛型以及代码的结构方式,仅创建一个不同的对象就需要大量重构,因此我正在尝试找到一个更快的解决方案。)

例如,我的序列化器如下所示:

public class FooSerializer extends StdSerializer<Foo>
{
    @Override
    public void serialize(
        final Foo foo,
        final JsonGenerator jsonGenerator,
        final SerializerProvider serializerProvider)
        throws IOException
    {
        if (foo.isFlattened())
        {
            jsonGenerator.writeObject(flatten(foo));
        }
        else
        {
            jsonGenerator.writeObject(foo);
        }
    }

    private Map<String,Object> flatten(Foo foo)
    {
        // ... some logic that builds the Map...
    }
}

并且被序列化的类将被相应地注释:

@JsonSerialize(using = FooSerializer.class)
public class Foo
{
    // ... things...
}

问题几乎立即变得明显:jsonGenerator.writeObject 调用 ObjectMapper 来序列化对象...然后它会再次调用我的序列化程序,最终给我们一个可爱的无限循环和堆栈溢出。

我能想到的唯一解决方法是使用反射,遍历对象的属性,然后使用jsonGenerator 将它们写入新对象。这感觉有点矫枉过正,尤其是当杰克逊应该能够为我做这件事的时候。问题是我找不到方法告诉ObjectMapper 忽略类上的序列化程序。

有没有更好的方法来做到这一点?我想我可以创建一个自定义的ObjectMapper,它以某种方式忽略类上注释的序列化程序,并将其用作jsonGenerator 的编解码器......但不完全确定要拉动ObjectMapper 的杠杆。

【问题讨论】:

    标签: java serialization jackson


    【解决方案1】:

    正如怀疑的那样,有一种方法可以禁用特定的注释。在这种情况下,我想禁用 Foo 类上的 JsonSerialize 注释。所以,我这样做是为了打破无限循环:

    public class FooSerializer extends StdSerializer<Foo>
    {
        @Override
        public void serialize(
            final Foo foo,
            final JsonGenerator jsonGenerator,
            final SerializerProvider serializerProvider)
            throws IOException
        {
            ObjectMapper mapper = new ObjectMapper();
            JacksonAnnotationIntrospector annotationIntrospector =
                new JacksonAnnotationIntrospector()
            {
                @Override
                protected <A extends Annotation> A _findAnnotation(
                    final Annotated annotated,
                    final Class<A> annotationClass)
                {
                    if (annotated.hasAnnotation(JsonSerialize.class) &&
                        annotated.getRawType() == Foo.class)
                    {
                        return null;
                    }
    
                    return super._findAnnotation(annotated, annotationClass);
                }
            };
            mapper.setAnnotationIntrospector(annotationIntrospector);
            jsonGenerator.setCodec(mapper);
    
            if (foo.isFlattened())
            {
                jsonGenerator.writeObject(flatten(foo));
            }
            else
            {
                jsonGenerator.writeObject(foo);
            }
        }
    
        private Map<String,Object> flatten(Foo foo)
        {
            // ... some logic that builds the Map...
        }
    }
    

    【讨论】:

    • 您的解决方案看起来有点像 hack。对于每个序列化过程,它将使用JacksonAnnotationIntrospector 的自定义实现创建新的ObjectMapper,从而禁用Foo 的自定义序列化程序。此外,用jsonGenerator.setCodec(mapper) 覆盖编解码器可能会有缺点,因为进程中的所有其他对象都将被ObjectMapper 的这个实例序列化。如果您需要使用一些自定义或通用模块,对于Java 8Java 8 Time 等类,您每次都必须在自定义序列化程序中重新注册它们。考虑使用我的解决方案。
    【解决方案2】:

    Jackson 允许以多种不同方式注册自定义序列化程序。其中之一是使用com.fasterxml.jackson.databind.ser.BeanSerializerModifier 类,它允许创建自定义序列化器,但如果需要,我们也可以使用那里可用的基本序列化器。

    上面的问题看起来像是OOP 问题的一个很好的例子,我们可以解耦POJO,它是逻辑和序列化过程。序列化器不应该知道如何将对象转换为Map。它应该只有序列化逻辑。下面介绍POJO和serialiser使用的接口:

    interface Flattenable {
    
        boolean isFlattened();
    
        Map<String, Object> flatten();
    }
    
    class Foo implements Flattenable {
    
        private boolean flattened;
        private int id;
    
        public Foo(boolean flattened, int id) {
            this.flattened = flattened;
            this.id = id;
        }
    
        @Override
        public Map<String, Object> flatten() {
            Map<String, Object> map = new LinkedHashMap<>();
            map.put("id", getId());
            map.put("random", ThreadLocalRandom.current().nextDouble());
    
            return map;
        }
    
        @Override
        public boolean isFlattened() {
            return flattened;
        }
    
        public void setFlattened(boolean flattened) {
            this.flattened = flattened;
        }
    
        public int getId() {
            return id;
        }
    
        public void setId(int id) {
            this.id = id;
        }
    }
    

    现在,我们可以通过实现Flattenable 接口轻松处理其他类型。序列化逻辑将是相同的。自定义序列化器可能如下所示:

    class FlattenableJsonSerializer extends JsonSerializer<Flattenable> {
    
        private final JsonSerializer<Object> base;
    
        public FlattenableJsonSerializer(JsonSerializer base) {
            this.base = Objects.requireNonNull(base);
        }
    
        @Override
        public void serialize(Flattenable value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
            if (value.isFlattened()) {
                gen.writeObject(value.flatten());
            } else {
                base.serialize(value, gen, serializers);
            }
        }
    }
    

    当我们有POJO 模型和序列化器时,我们只需要配置ObjectMapper 并尝试使用我们的解决方案:

    import com.fasterxml.jackson.core.JsonGenerator;
    import com.fasterxml.jackson.databind.BeanDescription;
    import com.fasterxml.jackson.databind.JsonSerializer;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import com.fasterxml.jackson.databind.SerializationConfig;
    import com.fasterxml.jackson.databind.SerializerProvider;
    import com.fasterxml.jackson.databind.json.JsonMapper;
    import com.fasterxml.jackson.databind.module.SimpleModule;
    import com.fasterxml.jackson.databind.ser.BeanSerializerModifier;
    
    import java.io.IOException;
    import java.util.LinkedHashMap;
    import java.util.Map;
    import java.util.Objects;
    import java.util.concurrent.ThreadLocalRandom;
    
    public class JsonFlattenApp {
    
        public static void main(String[] args) throws Exception {
            SimpleModule flattenModule = new SimpleModule("FlattenModule");
            flattenModule.setSerializerModifier(new FlattenableBeanSerializerModifier());
    
            ObjectMapper mapper = JsonMapper.builder()
                    .addModule(flattenModule)
                    .build();
    
            System.out.println(mapper.writeValueAsString(new Foo(true, 1)));
            System.out.println(mapper.writeValueAsString(new Foo(false, 2)));
        }
    }
    
    class FlattenableBeanSerializerModifier extends BeanSerializerModifier {
        @Override
        public JsonSerializer<?> modifySerializer(SerializationConfig config, BeanDescription beanDesc, JsonSerializer<?> serializer) {
            if (Flattenable.class.isAssignableFrom(beanDesc.getBeanClass())) {
                return new FlattenableJsonSerializer(serializer);
            }
    
            return serializer;
        }
    }
    

    上面的代码打印了两行:

    //1
    {"id":1,"random":0.7818309762014325}
    
    //2
    {"flattened":false,"id":2}
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2023-04-08
      • 1970-01-01
      • 2018-12-19
      • 1970-01-01
      • 2016-09-17
      • 2018-07-15
      • 1970-01-01
      • 2010-11-01
      相关资源
      最近更新 更多