使用Jackson,我希望能够在序列化属性时更改活动视图。

因此,如果我的应用程序使用3个视图A,B和C,我可能希望类型T的属性p始终使用视图A进行序列化,而不管活动视图ClassnullA.classB.classC.class

我还没有找到任何内置于Jackson的方法,所以我计划制作自己的类来实现此功能。

在我可能重新发明轮子之前,使用普通的Jackson注释和/或类是否可以做到这一点?

如果不是这样,我计划执行以下操作(注意:为简化此问题,我省略了许多配置选项和性能优化,这些功能对于核心功能不是必需的,但是我将在核心工作后再添加) ;为了简明起见,我偶尔还会省略可见性修饰符等):


进行以下注释,指示对于带注释的字段或方法,应将视图从活动视图切换到value

@Target({FIELD, METHOD})
@Retention(RUNTIME)
public @interface JsonApplyView {

    Class<?> value();
}

如果将@JsonApplyView应用于属性,则以某种方式将_serializationViewgetSerializationView()getActiveView()SerializerProvider的值从原始活动视图更改为value中的@JsonApplyView(我们将其称为JsonApplyView.value appliedView)。

如果有更好/更正确的方法来更改活动视图,请告诉我。

如果这是正确的方法,则由于_serializationView是最终的,并且由于两个getter都返回_serializationView,因此我必须创建一个BeanSerializerModifier,其modifySerializer()ContextualSerializer将其参数序列化器包装为ViewApplyingSerializer,并且:

JsonSerializer<?> createContextual(SerializerProvider sp, BeanProperty bp) {
    JsonApplyView apply = bp.getAnnotation(JsonApplyView.class);
    return
        apply == null ||
        apply.value().equals(sp.getActiveView()))
            ? this
            : new ViewApplyingSerializer(this, apply.value())
    ;
}


ViewApplyingSerializer.serialize(Object o, JsonGenerator g, SerializerProvider p)将以下属性保存在其构造函数中:

final JsonSerializer<?> delegate;
final Class<?>          appliedView;


p将执行以下操作之一:


复制SerializerProvider copy(SerializerProvider p, Class<?> appliedView)

delegate.serialize(o, g, copy(p, appliedView));


其中p返回_serializationView = appliedView的副本,其p
包装SerializerProvider wrap(SerializerProvider p, Class<?> appliedView)

delegate.serialize(o, g, wrap(p, appliedView));


其中p返回围绕_serializationView的包装,其getSerializationView()getActiveView()appliedView均返回ObjectWriter
使用全新的ow

ow.writeValue(g, o);


以某种方式获得SerializerProvider



我想复制SerializerProvider会是最好的,但是我发现复制SerializerFactory并更改其活动视图的唯一方法是传递SerializerFactory,并且这是我发现的唯一方法。获得ObjectMapper是构造一个新的,或从_serializerFactory获得一个。我更喜欢使用当前SerializerProvider中的SerializerProvider来尽可能地模仿它,但是该字段是受保护的,而且我还没有发现任何吸气剂。复制或包装Class使其具有不同视图的最简单方法是什么?

最佳答案

这是很久以前问过的,但我也在寻找类似的方法。我认为,如果杰克逊能够自己支持这一点,那就太好了,因为在我看来,这是一个非常频繁的案例。

例如,假设您有一个视图层次结构,其中DetailView(高细节)从SummaryView(低细节)继承,以及两个具有父子关系的对象。当使用活动的DetailView序列化子对象时,您可能希望添加其父对象的摘要,而不必添加其所有详细信息……然后您需要从DetailsView切换到SummaryView。

无论如何,无需再次解释您已经知道的显而易见的事情,我最后要做的是修改BeanSerializer,因为它只是在序列化JsonView注释发生的bean的同时进行的。

@Override
public JsonSerializer<?> modifySerializer(SerializationConfig config, BeanDescription beanDesc, JsonSerializer<?> serializer) {
    return new JsonViewOverrideSerializer(serializer);
}


JsonViewOverrideSerializer保留原始bean序列化程序的引用。真正重要的事情发生在上下文中:如果未使用@ApplyJsonView注释属性,则它将直接返回原始bean序列化器。否则,它将在上下文化后包装Bean序列化程序。像这样:

@Override
public JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty property) throws JsonMappingException {
    JsonSerializer<?> serializer = this.beanSerializer;

    if (serializer instanceof ContextualSerializer) {
        serializer = ((ContextualSerializer) serializer).createContextual(prov, property);
    }

    if (property != null) {
        ApplyJsonView applyJsonView = property.getAnnotation(ApplyJsonView.class);

        if (applyJsonView != null) {
            Class<?> jsonView = applyJsonView.value();
            serializer = new JsonViewOverrideSerializer(serializer, jsonView);
        }
    }

    return serializer;
}


最后,这是棘手的部分,您想在其中重写SerializerProvider对象。我也没有找到任何一种优雅的方式来做,所以这就是我最终创建SerializerProvider' by retrieving the SerializerFactory from the JsonGenerator codec, the ObjectMapper`的新实例的原因(我也希望从提供者那里检索它而是被覆盖,但是很好...太糟糕了,我无法访问它)。

public SerializerProvider overrideProvider(Class<?> overrideView, JsonGenerator gen, SerializerProvider provider) {
       return ((DefaultSerializerProvider) provider).createInstance(provider.getConfig().withView(overrideView), ((ObjectMapper) gen.getCodec()).getSerializerFactory(););
    });
}


(注意:为避免重新创建具有相同活动视图的提供程序,我会在提供程序的属性中保留所有覆盖的提供程序的映射)。

最后,在序列化对象之前,让序列化程序查找其有效提供程序:

@Override
public void serialize(Object value, JsonGenerator gen, SerializerProvider provider) throws IOException {
   SerializerProvider effectiveProvider = provider;

    if (provider.getActiveView() != null && overrideView != provider.getActiveView()) {
        effectiveProvider = overrideProvider(overrideView, gen, provider);
    }

    beanSerializer.serialize(value, gen, effectiveProvider);
}

关于java - 更改属性的 jackson Activity View ,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/30187119/

10-11 01:11