2012-09-06 27 views
5

Tengo un REST WS para actualizar un objeto bean que recibe una cadena JSON como entrada.Jackson Filtrado dinámico de propiedades durante la deserialización

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

ABean es un tipo complejo que contiene también otros objetos ej .:

class ABean { 
    public BBean b; 
    public CBean c; 

    public String d; 
} 

svc.save (...) ahorrará el grano y los objetos incrustados.

Por razones de seguridad, quiero filtrar algunas de las propiedades que se pueden actualizar con la cadena JSON, pero quiero hacerlo de forma dinámica, para que por cada WS (o función del usuario) pueda decidir qué propiedades evitar para ser actualizado (así que no puedo usar simplemente las Vistas Jackson)

En resumen, ¿hay alguna manera de poder filtrar dinámicamente las propiedades durante la Deserialización JSON?

Respuesta

5

Otra forma es utilizar 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; 
     } 
    } 

puede registrar el modfier a la ObjectMapper con:

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

A continuación, las propiedades definidas son ignorados. Puede ignorar el método updateBuilder si usa la anotación @JsonAnySetter.

Saludos, Martin

+0

¡Gracias! Me encontré con el mismo problema que el póster original, y creo que esta debería ser la respuesta aceptada, ya que admite la eliminación selectiva de propiedades según el tipo de clase. – mseddon

1

Supongo por su descripción que no puede simplemente usar la anotación @JsonIgnore para evitar que los campos se serialicen para cada clase en particular.

Mire utilizando Jakson mix-ins: las mezclas le permiten sustituir una definición de clase, con las anotaciones necesarias, por el enlace de datos. Durante la serialización, puede elegir una definición de clase de mezcla particular en stand en para la clase real que se está serializando. Defina un conjunto de mezclas para manejar cada caso, luego elija cuál es apropiado al serializar un bean en particular.

+0

Creo que esto tiene el mismo poder que puedo conseguir mediante el uso de puntos de vista. ¿Qué sucede si tengo un conjunto de funciones dinámicas que extraigo de una base de datos junto con sus permisos y deseo filtrar en función de esas reglas? – Mario

1

Puede usar @JsonIgnoreType para ignorar una clase java/interface/enum de la serialización. Además, puede usar @JsonIgnoreProperties para ignorar una lista de propiedades y @JsonIgnore para una propiedad específica

+0

esto es válido solo para filtrado estático (en tiempo de compilación) – Mario

0

La solución más poderosa, rápida y fácil que se me ocurrió es simplemente filtrar el árbol JsonNode obtenido de la deserialización, luego pasar el filtro resultado para el lectorForUpdating. Algo así:

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); 
     } 
    } 
} 
Cuestiones relacionadas