jackson: ignore getter, but not with @JsonView
Asked Answered
V

2

9

I'm looking for possibility to serialize transient information only in some cases:

@JsonInclude(Include.NON_NULL)
@Entity
public class User {

    public static interface AdminView {}

    ... id, email and others ...

    @Transient
    private transient Details details;

    @JsonIgnore                  // Goal: ignore all the time, except next line
    @JsonView(AdminView.class)   // Goal: don't ignore in AdminView
    public Details getDetails() {
        if (details == null) {
            details = ... compute Details ...
        }
        return details;
    }
}

public class UserDetailsAction {
    private static final ObjectWriter writer = new ObjectMapper();
    private static final ObjectWriter writerAdmin = writer
        .writerWithView(User.AdminView.class);

    public String getUserAsJson(User user) {
        return writer.writeValueAsString(user);
    }

    public String getUserAsJsonForAdmin(User user) {
        return writerAdmin.writeValueAsString(user);
    }
}

If I call getUserAsJson I expected to see id, email and other fields, but not details. This works fine. But I see same for getUserAsJsonForAdmin, also without detail. If I remove @JsonIgnore annotation - I do see details in both calls.

What do I wrong and is there good way to go? Thanks!

Vicenta answered 19/4, 2014 at 16:2 Comment(0)
R
5

You may find the use of the dynamic Jackson filtering slightly more elegant for your use case. Here is an example of the filtering of POJO fields based on a custom annotation sharing one object mapper instance:

public class JacksonFilter {
    static private boolean shouldIncludeAllFields;

    @Retention(RetentionPolicy.RUNTIME)
    public static @interface Admin {}

    @JsonFilter("admin-filter")
    public static class User {
        public final String email;
        @Admin
        public final String details;

        public User(String email, String details) {
            this.email = email;
            this.details = details;
        }
    }

    public static class AdminPropertyFilter extends SimpleBeanPropertyFilter {

        @Override
        protected boolean include(BeanPropertyWriter writer) {
            // deprecated since 2.3
            return true;
        }

        @Override
        protected boolean include(PropertyWriter writer) {
            if (writer instanceof BeanPropertyWriter) {
                return shouldIncludeAllFields || ((BeanPropertyWriter) writer).getAnnotation(Admin.class) == null;
            }
            return true;
        }
    }

    public static void main(String[] args) throws JsonProcessingException {
        User user = new User("email", "secret");
        ObjectMapper mapper = new ObjectMapper();
        mapper.setFilters(new SimpleFilterProvider().addFilter("admin-filter", new AdminPropertyFilter()));
        System.out.println(mapper.writerWithDefaultPrettyPrinter().writeValueAsString(user));
        shouldIncludeAllFields = true;
        System.out.println(mapper.writerWithDefaultPrettyPrinter().writeValueAsString(user));
    }

}

Output:

{
  "email" : "email"
}
{
  "email" : "email",
  "details" : "secret"
}
Reseau answered 19/4, 2014 at 22:2 Comment(2)
Ok, after testing different solutions, custom filter and FilterIntrospector seems to be a way I want to go.Vicenta
For anyone using this code across threads, there is a potential concurrency issue by using shouldIncludeAllFields. But easily fixed by just creating 2 ObjectMappers, 1 with the filter and 1 without. ObjectMappers are ThreadsafeDisvalue
V
1

It's look like jackson have horrible concept on very cool feature like @JsonView. The only way I discover to solve my problem is:

@JsonInclude(Include.NON_NULL)
@Entity
public class User {

    public static interface BasicView {}
    public static interface AdminView {}

    ... id and others ...

    @JsonView({BasicView.class, AdminView.class}) // And this for EVERY field
    @Column
    private String email;

    @Transient
    private transient Details details;

    @JsonView(AdminView.class)
    public Details getDetails() {
        if (details == null) {
            details = ... compute Details ...
        }
        return details;
    }
}

public class UserDetailsAction {
    private static final ObjectWriter writer = new ObjectMapper()
        .disable(MapperFeature.DEFAULT_VIEW_INCLUSION)
        .writerWithView(User.BasicView.class);
    private static final ObjectWriter writerAdmin = new ObjectMapper()
        .disable(MapperFeature.DEFAULT_VIEW_INCLUSION)
        .writerWithView(User.AdminView.class);

    public String getUserAsJson(User user) {
        return writer.writeValueAsString(user);
    }

    public String getUserAsJsonForAdmin(User user) {
        return writerAdmin.writeValueAsString(user);
    }
}

Maybe it's help some one. But I hope to find better solution and because doesn't accept my own answer.

EDIT: because interface can extends (multiple) interfaces, I can use:

public static interface AdminView extends BasicView {}

and just

@JsonView(BasicView.class)

instead of

@JsonView({BasicView.class, AdminView.class})
Vicenta answered 19/4, 2014 at 17:52 Comment(2)
How do I do this for POJO which am sending as REST response? Note: Am not using ObjectWriterFestus
will the usage of null instead of some specific view would help? @JsonView(null) // Goal: ignore all the time, except next lineThromboplastin

© 2022 - 2024 — McMap. All rights reserved.