Utilisation de Jackson ObjectMapper avec Java 8 valeurs optionnelles
J'essayais d'utiliser Jackson pour écrire une valeur de classe dans JSON qui a des champs optionnels:
public class Test {
Optional<String> field = Optional.of("hello, world!");
public Optional<String> getField() {
return field;
}
public static void main(String[] args) throws JsonProcessingException {
ObjectMapper mapper = new ObjectMapper();
System.out.println(mapper.writeValueAsString(new Test()));
}
}
Lorsqu'elle est exécutée, cette classe génère la sortie suivante:
{"field":{"present":true}}
Je comprends que le champ présent/non présent soit inclus et pourrait le contourner lors de la lecture des données JSON, mais je ne peux pas contourner le fait que le contenu réel de l'option n'est jamais écrit dans la sortie. :(
Des solutions de contournement ici sauf ne pas utiliser ObjectMapper du tout?
7 réponses
Vous pouvez utiliser jackson-type de données-jdk8, qui est décrit comme:
Prise en charge des nouveaux types spécifiques à JDK8, tels que optionnel
La classe Optional
a un champ value
, mais pas de getter/setter standard pour cela. Par défaut, Jackson recherche les getters / setters pour trouver les propriétés de classe.
Vous pouvez ajouter un Mixin personnalisé pour identifier le champ en tant que propriété
final class OptionalMixin {
private Mixin(){}
@JsonProperty
private Object value;
}
Et enregistrez-le avec votre ObjectMapper
.
ObjectMapper mapper = new ObjectMapper();
mapper.addMixInAnnotations(Optional.class, OptionalMixin.class);
Vous pouvez maintenant sérialiser votre objet.
System.out.println(mapper.writeValueAsString(new Test()));
Imprimera
{"field":{"value":"hello, world!","present":true}}
Envisager également de regarder jackson-datatype-guava
. Il y a une implémentation Jackson Module
pour les types de goyave y compris leur Optional
. C'est peut-être plus complet que ce que j'ai montré ci-dessus.
Similaire à la réponse de @Manikandan mais ajoutez @JsonProperty
au champ privé au lieu d'un getter afin de ne pas exposer votre travail sur l'api publique.
public class Test {
@JsonProperty("field")
private String field;
@JsonIgnore
public Optional<String> getField() {
return Optional.of(field); // or Optional.ofNullable(field);
}
}
Définissez new getter qui retournera String au lieu D'Optional.
public class Test {
Optional<String> field = Optional.of("hello, world!");
@JsonIgnore
public Optional<String> getField() {
return field;
}
@JsonProperty("field")
public String getFieldName() {
return field.orElse(null);
}
}
Essayez de passer des options dans @JsonInclude
annotation.
Par exemple, si vous ne voulez pas afficher field
lorsque la valeur est null
. vous devrez peut-être utiliser Jackson-Modules > 2.8.5 .
import com.fasterxml.jackson.annotation.JsonInclude;
public class Test {
@JsonInclude(JsonInclude.Include.NON_NULL)
Optional<String> field;
}
Si vous utilisez la dernière version de Spring-boot, vous pouvez y parvenir en ajoutant la dépendance suivante dans le fichier pom
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jdk8</artifactId>
</dependency>
Et auto fil le JacksonObjectMapper.
@Autowired
private ObjectMapper jacksonObjectMapper;
Ensuite, utilisez l'instance de mapper du conteneur Spring ci-dessus pour convertir L'objet en chaîne
jacksonObjectMapper.writeValueAsString(user);
Certains modules Jackson bien connus sont automatiquement enregistrés s'ils sont détectés sur le classpath:
- jackson-type de données-jdk7: Java 7 types comme java.nio.fichier.Chemin (à partir de la version 4.2.1)
- jackson-datatype-joda: Joda-types de temps
- jackson-datatype-Jsr310: Java 8 types de données API Date et heure
- jackson-datatype-jdk8: D'autres types Java 8 comme optionnels (à partir de la version 4.2.0)
Il vous suffit d'enregistrer le module Jdk8Module
. Ensuite, il sérialisera tout Optional<T>
comme {[5] } s'il est présent ou null
sinon.
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new Jdk8Module());
Modifions un peu la classe Test
pour pouvoir tester une valeur Optional.empty()
. Ceci est similaire à la classe Test
d'origine lorsqu'elle est sérialisée car le mappeur d'objet recherche le getter (puisque le champ est private
). L'utilisation de la classe Test
d'origine fonctionnera aussi.
class Test {
private final String field;
public Test(String field) {
this.field = field;
}
public Optional<String> getField() {
return Optional.ofNullable(field);
}
}
, Puis dans notre classe principale:
Test testFieldNull = new Test(null);
Test testFieldNotNull = new Test("foo");
// output: {"field":null}
System.out.println(objectMapper.writeValueAsString(testFieldNull));
// output: {"field":"foo"}
System.out.println(objectMapper.writeValueAsString(testFieldNotNull));