When overriding JacksonJsonProvider.writeTo, can I

2019-08-02 21:34发布

问题:

I have a setup with Resteasy-3.0.6, Jackson-2.2.3 and CDI (running on Wildfly-8.0.0.CR1). In my case, every entity has a mix-in class that extends it and specifies the attributes that are to be serialized. There are two "views", let's call them Basic and Extended. Since what kind of view of an object I need depends on the "root" object of the serialization, these views need to be per object. So I re-use the mix-in classes for that. Example:

public class Job { 
    @Id private Long id;
    @OneToMany(mappedBy="job") private Set<Bonus> bonuses;
}

public class Bonus {
    @Id private Long id;
    @ManyToOne(optional=false) private Job job;
    private BigDecimal value;
}

public abstract class JsonJob extends Job { 
    abstract Long getId();

    @JsonView({ JsonJob.class })
    abstract Set<Bonus> getBonuses();
}

public abstract class JsonBonus extends Bonus {
    abstract BigDecimal getValue();
}

Now I'm looking for a way to hook into jackson's serialization process to specify JsonJob as a view iff the root entity is Job. I'm currently using JacksonJsonProvider#writeTo:

@Override
public void writeTo(Object value, Class<?> type, Type genericType, 
                    Annotation[] annotations, MediaType mediaType, 
                    MultivaluedMap<String, Object> httpHeaders, 
                    OutputStream entityStream) throws IOException {
    Class view = getViewForType(type); // if no mix-in class is set, return Object
    ObjectMapper mapper = locateMapper(type, mediaType);

    // require all properties to be explicitly specified for serialization
    mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.NONE);
    mapper.registerModule(new SerializationMixinConf()); // registers all mix-ins

    super.writeTo(value, type, genericType, annotations, mediaType, 
        httpHeaders, entityStream);
}

The point is: this works, iff the calling method is annotated @JsonView. But since the main user of this is a generic super-class, I can't add the annotation to the method directly. Can someone suggest a way to either set the view in the existing mapper (setSerializationConfig() seems to be gone in jackson-2) or dynamically add the @JsonView to the annotations array?

回答1:

Here's what I have in writeTo now:

ObjectMapper mapper = locateMapper(type, mediaType);
// require all properties to be explicitly specified for serialization
mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.NONE);
mapper.registerModule(new SerializationMixinConf());

// TODO: check if we already have @JsonView
Annotation[] myAnnotations = Arrays.copyOf(annotations, annotations.length + 1);
myAnnotations[annotations.length] = new JsonViewQualifier(view);

super.writeTo(value, type, genericType, myAnnotations, mediaType, 
    httpHeaders, entityStream);

And the qualifier simply extends from AnnotationLiteral:

public class JsonViewQualifier extends AnnotationLiteral<JsonView> 
        implements JsonView {
    private final Class[] views;

    public JsonViewQualifier(Class[] views) { this.views = views; }
    public JsonViewQualifier(Class view) { this(new Class[] { view }); }

    @Override
    public Class<?>[] value() {
        return views;
    }
}

Note that this will generate a new writer for each view (as it should).



回答2:

With Jackson 2.3, you can use @JsonView on JAX-RS end points, if that would help.

But if not, another possibility is to use ObjectWriter directly (writers are created from ObjectMapper), constructing writer with specific view. This is fully dynamic and can be done on per-call basis. This requires sort of by-passing JAX-RS logic and usually you will need to return StreamingOutput (instead of actual POJO), but it is the ultimate in configurability.