2012-09-06 2 views
5

J'ai un REST WS pour mettre à jour un objet bean qui reçoit une chaîne JSON en entrée.Jackson Filtrage dynamique des propriétés lors de la désérialisation

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

ABean est un type complexe contenant également d'autres objets par exemple .:

class ABean { 
    public BBean b; 
    public CBean c; 

    public String d; 
} 

svc.save (...) sauvegarde le haricot et les objets incorporés. Pour des raisons de sécurité, je veux filtrer certaines des propriétés qui peuvent être mises à jour par la chaîne JSON, mais je veux le faire dynamiquement, de sorte que pour chaque WS (ou rôle de l'utilisateur) je peux décider quelles propriétés empêcher à mettre à jour (donc je ne peux pas simplement utiliser les vues de Jackson)

Pour résumer, est-il possible de filtrer dynamiquement les propriétés lors de la désérialisation JSON?

Répondre

5

Une autre façon utilise 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; 
     } 
    } 

Vous pouvez enregistrer le modfier au ObjectMapper avec:

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

Ensuite, les propriétés définies sont ignorées. Vous pouvez ignorer la méthode updateBuilder si vous utilisez l'annotation @JsonAnySetter.

Salutations, Martin

+0

Merci! J'ai rencontré le même problème que l'affiche originale, et je crois que cela devrait être la réponse acceptée, car il prend en charge la suppression sélective des propriétés en fonction du type de classe. – mseddon

1

Je suppose de votre description que vous ne pouvez pas simplement utiliser l'annotation @JsonIgnore pour empêcher les champs d'être sérialisés pour chaque classe particulière. Regardez avec Jakson mix-ins: les mix-ins vous permettent de substituer une définition de classe - avec les annotations nécessaires - à la liaison de données. Au cours de la sérialisation, vous pouvez choisir une définition de classe de mixage particulière à dans pour la classe en cours de sérialisation. Définissez un ensemble de mix-ins pour gérer chaque cas, puis choisissez celui qui est approprié lorsque vous sérialisez un bean particulier.

+0

Je pense que cela a la même puissance que je peux obtenir en utilisant des vues. Que faire si j'ai un ensemble de rôles dynamiques que j'extrais d'une base de données avec leurs permissions et que je veux filtrer en fonction de ces règles? – Mario

1

Vous pouvez utiliser @JsonIgnoreType pour ignorer une classe/interface/enum Java à partir de la sérialisation. De plus, vous pouvez utiliser @JsonIgnoreProperties pour ignorer une liste de propriétés et @JsonIgnore pour une propriété spécifique

+0

Ceci est valide uniquement pour le filtrage statique (à la compilation) – Mario

0

La solution la plus puissante, la plus rapide et la plus simple que j'ai trouvée est de filtrer l'arbre JsonNode obtenu à partir de la désérialisation, puis passer le filtre résultat au lecteurForUpdating. Quelque chose comme ça:

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); 
     } 
    } 
} 
Questions connexes