JMax
JMax

Reputation: 1202

Jackson filtering out fields without annotations

I was trying to filter out certain fields from serialization via SimpleBeanPropertyFilter using the following (simplified) code:

public static void main(String[] args) {
    ObjectMapper mapper = new ObjectMapper();

    SimpleFilterProvider filterProvider = new SimpleFilterProvider().addFilter("test",
            SimpleBeanPropertyFilter.filterOutAllExcept("data1"));
    try {
        String json = mapper.writer(filterProvider).writeValueAsString(new Data());

        System.out.println(json); // output: {"data1":"value1","data2":"value2"}

    } catch (JsonProcessingException e) {
        e.printStackTrace();
    }
}

private static class Data {
    public String data1 = "value1";
    public String data2 = "value2";
}

Us I use SimpleBeanPropertyFilter.filterOutAllExcept("data1")); I was expecting that the created serialized Json string contains only {"data1":"value1"}, however I get {"data1":"value1","data2":"value2"}.

How to create a temporary writer that respects the specified filter (the ObjectMapper can not be re-configured in my case).

Note: Because of the usage scenario in my application I can only accept answers that do not use Jackson annotations.

Upvotes: 24

Views: 28081

Answers (4)

doubleW
doubleW

Reputation: 688

If for some reason MixIns does not suit you. You can try this approach:

ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setAnnotationIntrospector(new JacksonAnnotationIntrospector(){
    @Override
    public boolean hasIgnoreMarker(final AnnotatedMember m) {

    List<String> exclusions = Arrays.asList("field1", "field2");
    return exclusions.contains(m.getName())|| super.hasIgnoreMarker(m);
    }
});

Upvotes: 31

Beno
Beno

Reputation: 987

The example of excluding properties by name:

public Class User {
    private String name = "abc";
    private Integer age = 1;
    //getters
}

@JsonFilter("dynamicFilter")
public class DynamicMixIn {
}

User user = new User();
String[] propertiesToExclude = {"name"};
ObjectMapper mapper = new ObjectMapper()
      .addMixIn(Object.class, DynamicMixIn.class);
FilterProvider filterProvider = new SimpleFilterProvider()
                .addFilter("dynamicFilter", SimpleBeanPropertyFilter.filterOutAllExcept(propertiesToExclude));
        mapper.setFilterProvider(filterProvider);

mapper.writeValueAsString(user); // {"name":"abc"}

You can instead of DynamicMixIn create MixInByPropName

@JsonIgnoreProperties(value = {"age"})
public class MixInByPropName {
}

ObjectMapper mapper = new ObjectMapper()
      .addMixIn(Object.class, MixInByPropName.class);

mapper.writeValueAsString(user); // {"name":"abc"}

Note: If you want exclude property only for User you can change parameter Object.class of method addMixIn to User.class

Excluding properties by type you can create MixInByType

@JsonIgnoreType
public class MixInByType {
}

ObjectMapper mapper = new ObjectMapper()
      .addMixIn(Integer.class, MixInByType.class);

mapper.writeValueAsString(user); // {"name":"abc"}

Upvotes: 21

Manos Nikolaidis
Manos Nikolaidis

Reputation: 22214

You would normally annotate your Data class to have the filter applied:

@JsonFilter("test")
class Data {

You have specified that you can't use annotations on the class. You could use mix-ins to avoid annotating Data class.

@JsonFilter("test")
class DataMixIn {}

Mixins have to be specified on an ObjectMapper and you specify you don't want to reconfigure that. In such a case, you can always copy the ObjectMapper with its configuration and then modify the configuration of the copy. That will not affect the original ObjectMapper used elsewhere in your code. E.g.

ObjectMapper myMapper = mapper.copy();
myMapper.addMixIn(Data.class, DataMixIn.class);

And then write with the new ObjectMapper

String json = myMapper.writer(filterProvider).writeValueAsString(new Data());
System.out.println(json); // output: {"data1":"value1"}

Upvotes: 23

armnotstrong
armnotstrong

Reputation: 9065

It seems you have to add an annotation which indicts which filter to use when doing the serialization to the bean class if you want the filter to work:

@JsonFilter("test")
public class Data {
    public String data1 = "value1";
    public String data2 = "value2";
}

EDIT

The OP has just added a note that just take the answer that not using a bean animation, then if the field you want to export is very less amount, you can just retrieve that data and build a Map of List yourself, there seems no other way to do that.

Map<String, Object> map = new HashMap<String, Object>();
map.put("data1", obj.getData1());
...
// do the serilization on the map object just created.

If you want to exclude specific field and kept the most field, maybe you could do that with reflect. Following is a method I have written to transfer a bean to a map you could change the code to meet your own needs:

protected Map<String, Object> transBean2Map(Object beanObj){
        if(beanObj == null){
            return null;
        }
        Map<String, Object> map = new HashMap<String, Object>();

        try {
            BeanInfo beanInfo = Introspector.getBeanInfo(beanObj.getClass());
            PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
            for (PropertyDescriptor property : propertyDescriptors) {
                String key = property.getName();


                if (!key.equals("class")
                        && !key.endsWith("Entity")
                        && !key.endsWith("Entities")
                        && !key.endsWith("LazyInitializer")
                        && !key.equals("handler")) {


                    Method getter = property.getReadMethod();

                    if(key.endsWith("List")){
                        Annotation[] annotations = getter.getAnnotations();
                        for(Annotation annotation : annotations){
                            if(annotation instanceof javax.persistence.OneToMany){
                                if(((javax.persistence.OneToMany)annotation).fetch().equals(FetchType.EAGER)){
                                    List entityList = (List) getter.invoke(beanObj);
                                    List<Map<String, Object>> dataList = new ArrayList<>();
                                    for(Object childEntity: entityList){
                                        dataList.add(transBean2Map(childEntity));
                                    }
                                    map.put(key,dataList);
                                }
                            }
                        }
                        continue;
                    }

                    Object value = getter.invoke(beanObj);

                    map.put(key, value);
                }
            }
        } catch (Exception e) {
            Logger.getAnonymousLogger().log(Level.SEVERE,"transBean2Map Error " + e);
        }
        return map;
    }

But I recommend you to use Google Gson as the JSON deserializer/serializer And the main reason is I hate dealing with exception stuff, it just messed up with the coding style.

And it's pretty easy to satisfy your need with taking advantage of the version control annotation on the bean class like this:

@Since(GifMiaoMacro.GSON_SENSITIVE) //mark the field as sensitive data and will not export to JSON
private boolean firstFrameStored; // won't export this field to JSON.

You can define the Macro whether to export or hide the field like this:

 public static final double GSON_SENSITIVE = 2.0f;
 public static final double GSON_INSENSITIVE = 1.0f;

By default, Gson will export all field that not annotated by @Since So you don't have to do anything if you do not care about the field and it just exports the field.

And if some field you are not want to export to json, ie sensitive info just add an annotation to the field. And generate json string with this:

 private static Gson gsonInsensitive = new GsonBuilder()
            .registerTypeAdapter(ObjectId.class,new ObjectIdSerializer()) // you can omit this line and the following line if you are not using mongodb
            .registerTypeAdapter(ObjectId.class, new ObjectIdDeserializer()) //you can omit this
            .setVersion(GifMiaoMacro.GSON_INSENSITIVE)
            .disableHtmlEscaping()
            .create();

public static String toInsensitiveJson(Object o){
    return gsonInsensitive.toJson(o);
}

Then just use this:

 String jsonStr = StringUtils.toInsensitiveJson(yourObj);

Since Gson is stateless, it's fine to use a static method to do your job, I have tried a lot of JSON serialize/deserialize framework with Java, but found Gson to be the sharp one both performance and handily.

Upvotes: 0

Related Questions