Sérialiser uniquement les champs qui répondent à un critère personnalisé avec Jackson

Sérialiser uniquement les champs qui répondent à un critère personnalisé avec Jackson

1. Vue d'ensemble

Ce tutoriel va illustrer comment nous pouvonsuse Jackson to only serialize a field if it meets a specific, custom criteria.

Par exemple, disons que nous ne voulons sérialiser une valeur entière que si elle est positive - et que nous voulons l’ignorer complètement si ce n’est pas le cas.

Si vous voulez creuser plus profondément et apprendreother cool things you can do with the Jackson 2 - allez àthe main Jackson tutorial.

2. Utilisez Jackson Filter pour contrôler le processus de sérialisation

Tout d'abord, nous devons définir le filtre sur notre entité, en utilisant l'annotation@JsonFilter:

@JsonFilter("myFilter")
public class MyDto {
    private int intValue;

    public MyDto() {
        super();
    }

    public int getIntValue() {
        return intValue;
    }

    public void setIntValue(int intValue) {
        this.intValue = intValue;
    }
}

Ensuite, nous devons définir nosPropertyFilter personnalisés:

PropertyFilter theFilter = new SimpleBeanPropertyFilter() {
   @Override
   public void serializeAsField
    (Object pojo, JsonGenerator jgen, SerializerProvider provider, PropertyWriter writer)
     throws Exception {
      if (include(writer)) {
         if (!writer.getName().equals("intValue")) {
            writer.serializeAsField(pojo, jgen, provider);
            return;
         }
         int intValue = ((MyDtoWithFilter) pojo).getIntValue();
         if (intValue >= 0) {
            writer.serializeAsField(pojo, jgen, provider);
         }
      } else if (!jgen.canOmitFields()) { // since 2.3
         writer.serializeAsOmittedField(pojo, jgen, provider);
      }
   }
   @Override
   protected boolean include(BeanPropertyWriter writer) {
      return true;
   }
   @Override
   protected boolean include(PropertyWriter writer) {
      return true;
   }
};

Ce filtre contient la logique réelle déterminant si le champintValue va êtreserialized or not, en fonction de sa valeur.

Ensuite, nous raccordons ce filtre auxObjectMapper et nous sérialisons une entité:

FilterProvider filters = new SimpleFilterProvider().addFilter("myFilter", theFilter);
MyDto dtoObject = new MyDto();
dtoObject.setIntValue(-1);

ObjectMapper mapper = new ObjectMapper();
String dtoAsString = mapper.writer(filters).writeValueAsString(dtoObject);

Et enfin, on peut vérifier que le champintValue est biennot part of the marshalled JSON output:

assertThat(dtoAsString, not(containsString("intValue")));

3. Ignorer les objets de manière conditionnelle

Voyons maintenant comment ignorer des objets lors de la sérialisation en fonction de la propriétévalue. Nous ignorerons tous les objets où la propriétéhidden esttrue:

3.1. Classes masquables

Tout d'abord, jetons un œil à notre interfaceHidable:

@JsonIgnoreProperties("hidden")
public interface Hidable {
    boolean isHidden();
}

Et nous avons deux classes simples implémentant cette interfacePerson,Address:

ClassePerson:

public class Person implements Hidable {
    private String name;
    private Address address;
    private boolean hidden;
}

Et la classeAddress:

public class Address implements Hidable {
    private String city;
    private String country;
    private boolean hidden;
}

Remarque: nous avons utilisé@JsonIgnoreProperties(“hidden”) pour nous assurer que la propriétéhidden elle-même n'est pas incluse dans JSON

3.2. Sérialiseur personnalisé

Suivant - voici notre sérialiseur personnalisé:

public class HidableSerializer extends JsonSerializer {

    private JsonSerializer defaultSerializer;

    public HidableSerializer(JsonSerializer serializer) {
        defaultSerializer = serializer;
    }

    @Override
    public void serialize(Hidable value, JsonGenerator jgen, SerializerProvider provider)
      throws IOException, JsonProcessingException {
        if (value.isHidden())
            return;
        defaultSerializer.serialize(value, jgen, provider);
    }

    @Override
    public boolean isEmpty(SerializerProvider provider, Hidable value) {
        return (value == null || value.isHidden());
    }
}


Notez que:

  • Lorsque l'objet ne sera pas ignoré, nous déléguerons la sérialisation au sérialiseur injecté par défaut.

  • Nous avons remplacé la méthodeisEmpty() - pour nous assurer que si l'objet Hidable est une propriété, le nom de la propriété est également exclu de JSON.

3.3. Utilisation deBeanSerializerModifier

Enfin, nous devrons utiliserBeanSerializerModifier pour injecter le sérialiseur par défaut dans notreHidableSerializer personnalisé - comme suit:

ObjectMapper mapper = new ObjectMapper();
mapper.setSerializationInclusion(Include.NON_EMPTY);
mapper.registerModule(new SimpleModule() {
    @Override
    public void setupModule(SetupContext context) {
        super.setupModule(context);
        context.addBeanSerializerModifier(new BeanSerializerModifier() {
            @Override
            public JsonSerializer modifySerializer(
              SerializationConfig config, BeanDescription desc, JsonSerializer serializer) {
                if (Hidable.class.isAssignableFrom(desc.getBeanClass())) {
                    return new HidableSerializer((JsonSerializer) serializer);
                }
                return serializer;
            }
        });
    }
});



3.4. Exemple de sortie

Voici un exemple simple de sérialisation:

Address ad1 = new Address("tokyo", "jp", true);
Address ad2 = new Address("london", "uk", false);
Address ad3 = new Address("ny", "usa", false);
Person p1 = new Person("john", ad1, false);
Person p2 = new Person("tom", ad2, true);
Person p3 = new Person("adam", ad3, false);

System.out.println(mapper.writeValueAsString(Arrays.asList(p1, p2, p3)));

Et le résultat est:

[
    {
        "name":"john"
    },
    {
        "name":"adam",
        "address":{
            "city":"ny",
            "country":"usa"
        }
    }
]

3.5. Test

Enfin - voici quelques cas de test:

Premier cas,nothing is hidden:

@Test
public void whenNotHidden_thenCorrect() throws JsonProcessingException {
    Address ad = new Address("ny", "usa", false);
    Person person = new Person("john", ad, false);
    String result = mapper.writeValueAsString(person);

    assertTrue(result.contains("name"));
    assertTrue(result.contains("john"));
    assertTrue(result.contains("address"));
    assertTrue(result.contains("usa"));
}

Ensuite,only address is hidden:

@Test
public void whenAddressHidden_thenCorrect() throws JsonProcessingException {
    Address ad = new Address("ny", "usa", true);
    Person person = new Person("john", ad, false);
    String result = mapper.writeValueAsString(person);

    assertTrue(result.contains("name"));
    assertTrue(result.contains("john"));
    assertFalse(result.contains("address"));
    assertFalse(result.contains("usa"));
}

Maintenant,entire person is hidden:

@Test
public void whenAllHidden_thenCorrect() throws JsonProcessingException {
    Address ad = new Address("ny", "usa", false);
    Person person = new Person("john", ad, true);
    String result = mapper.writeValueAsString(person);

    assertTrue(result.length() == 0);
}

4. Conclusion

Ce type de filtrage avancé est incroyablement puissant et permet une personnalisation très flexible du json lors de la sérialisation d'objets complexes avec Jackson.

Une alternative plus flexible mais aussi plus complexe consisterait à utiliser un sérialiseur entièrement personnalisé pour contrôler la sortie JSON - donc si cette solution n'est pas assez flexible, cela peut valoir la peine de s'y pencher.

L'implémentation de tous ces exemples et extraits de codecan be found in my github project - il s'agit d'un projet basé sur Eclipse, il devrait donc être facile à importer et à exécuter tel quel.