Mario
Mario

Reputation: 3313

Jackson Dynamic filtering of properties during deserialization

I have a REST WS to update a bean object which receives a JSON string as input.

ABean entity = svc.findEntity(...);
objectMapper.readerForUpdating(entity).readValue(json);
[...]
svc.save(entity);

ABean is a complex type containing also other objects e.g.:

class ABean {
    public BBean b;
    public CBean c;

    public String d;
}

svc.save(...) will save the bean and the embedded objects.

For security reasons I want to filter out some of the properties that can be updated by the JSON string, but I want to do this dynamically, so that for every WS (or user Role) I can decide which properties to prevent to be updated (so I can't simply use the Jackson Views)

To summarize, is there any way I can dynamically filter out properties during JSON Deserialization?

Upvotes: 13

Views: 12324

Answers (4)

Martin
Martin

Reputation: 532

Another way is using BeanDeserializerModifier:

private static class BeanDeserializerModifierForIgnorables extends BeanDeserializerModifier {

        private java.lang.Class<?> type;
        private List<String> ignorables;

        public BeanDeserializerModifierForIgnorables(java.lang.Class clazz, String... properties) {
            ignorables = new ArrayList<>();
            for(String property : properties) {
                ignorables.add(property);
            }
            this.type = clazz;
        }

        @Override
        public BeanDeserializerBuilder updateBuilder(
                DeserializationConfig config, BeanDescription beanDesc,
                BeanDeserializerBuilder builder) {
            if(!type.equals(beanDesc.getBeanClass())) {
                return builder;
            }

            for(String ignorable : ignorables) {
                builder.addIgnorable(ignorable);                
            }

            return builder;
        }

        @Override
        public List<BeanPropertyDefinition> updateProperties(
                DeserializationConfig config, BeanDescription beanDesc,
                List<BeanPropertyDefinition> propDefs) {
            if(!type.equals(beanDesc.getBeanClass())) {
                return propDefs;
            }

            List<BeanPropertyDefinition> newPropDefs = new ArrayList<>();
            for(BeanPropertyDefinition propDef : propDefs) {
                if(!ignorables.contains(propDef.getName())) {
                    newPropDefs.add(propDef);
                }
            }
            return newPropDefs;
        }
    }

You can register the modfier to the ObjectMapper with:

BeanDeserializerModifier modifier = new BeanDeserializerModifierForIgnorables(YourType.class, "name");
DeserializerFactory dFactory = BeanDeserializerFactory.instance.withDeserializerModifier(modifier);
ObjectMapper mapper = new ObjectMapper(null, null, new DefaultDeserializationContext.Impl(dFactory));

Then the defined properties are ignored. You can ignore the updateBuilder method if you use the @JsonAnySetter annotation.

Greetings, Martin

Upvotes: 7

Mario
Mario

Reputation: 3313

The most powerful, quick and easy solution I came up with is just filtering the JsonNode tree obtained from the deserialization, then pass the filtered result to the readerForUpdating. Something like that:

public class JacksonHelper {
    public JsonNode filterBeanTree(JsonNode o, List<String> includedProperties,
            List<String> excludedProperties, int maxDepth) {
        JsonNode tree = o.deepCopy();
        this.filterBeanTreeRecursive(tree, includedProperties, excludedProperties, maxDepth, null);
        return tree;
    }

    private void filterBeanTreeRecursive(JsonNode tree, List<String> includedProperties,
            List<String> excludedProperties, int maxDepth, String key) {
        Iterator<Entry<String, JsonNode>> fieldsIter = tree.fields();
        while (fieldsIter.hasNext()) {
            Entry<String, JsonNode> field = fieldsIter.next();
            String fullName = key == null ? field.getKey() : key + "." + field.getKey();

            boolean depthOk = field.getValue().isContainerNode() && maxDepth >= 0;
            boolean isIncluded = includedProperties != null
                    && !includedProperties.contains(fullName);
            boolean isExcluded = excludedProperties != null
                    && excludedProperties.contains(fullName);
            if ((!depthOk && !isIncluded) || isExcluded) {
                fieldsIter.remove();
                continue;
            }

            this.filterBeanTreeRecursive(field.getValue(), includedProperties, excludedProperties,
                    maxDepth - 1, fullName);
        }
    }
} 

Upvotes: 0

Chris
Chris

Reputation: 5654

You can use @JsonIgnoreType to ignore a java class/interface/enum from serializing. Additionally you can use @JsonIgnoreProperties for ignoring a list of properties and @JsonIgnore for a specific property

Upvotes: 1

pb2q
pb2q

Reputation: 59637

I assume from your description that you can't simply use the @JsonIgnore annotation to prevent the fields from being serialized for each particular class.

Look at using Jakson mix-ins: mix-ins allow you to substitute a class definition - with the necessary annotations - for data binding. During serialization you can choose a particular mix-in class definition to stand in for the actual class being serialized. Define a set of mix-ins to handle every case, then choose which is appropriate when you serialize a particular bean.

Upvotes: 1

Related Questions