Désérialisation Jackson: Puis-je injecter une valeur avec une annotation sur le champ de l'objet à désérialisable?
J'ai un objet comme celui-ci à désérialiser:
public class RelationsInput {
Relation relation1;
Relation relation2;
}
alors que la classe Relation
ressemble à ceci:
public class Relation {
RelationType relationtype;
... (more fields)
}
RelationType
est une énumération et n'est pas une valeur qui sera désérialisée, alors que toutes les autres le sont.
Est-il possible que je puisse "injecter" la valeur d'énumération pour le champ relationType
avec une annotation sur le champ dans la classe RelationInput
? Comme le suivant
public class RelationsInput {
@RelationType(RelationType.OWNER)
Relation owner;
@RelationType(RelationType.TENANT)
Relation tenant;
}
Jackson fournit-il quelque chose comme ça?
Réponses
Vous pouvez essayer d'implémenter un désérialiseur personnalisé avec une com.fasterxml.jackson.databind.deser.ContextualDeserializer
interface. Il permet de créer une instance de désérialiseur avec un contexte.
Voir l'exemple ci-dessous:
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.BeanProperty;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.deser.ContextualDeserializer;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
import com.fasterxml.jackson.databind.json.JsonMapper;
import lombok.Data;
import java.io.File;
import java.io.IOException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
public class JsonContextualDeserializerApp {
public static void main(String[] args) throws IOException {
File jsonFile = new File("./resource/test.json").getAbsoluteFile();
ObjectMapper mapper = JsonMapper.builder().build();
RelationsInput info = mapper.readValue(jsonFile, RelationsInput.class);
System.out.println(info.toString());
}
}
@Data
class RelationsInput {
@JsonDeserialize(using = RelationStdDeserializer.class)
@RelationTypeInfo(RelationType.OWNER)
private Relation owner;
@JsonDeserialize(using = RelationStdDeserializer.class)
@RelationTypeInfo(RelationType.TENANT)
private Relation tenant;
}
@Data
class Relation {
private int id;
private RelationType relationtype;
}
enum RelationType {OWNER, TENANT}
@Retention(RetentionPolicy.RUNTIME)
@interface RelationTypeInfo {
RelationType value();
}
class RelationStdDeserializer extends StdDeserializer<Relation> implements ContextualDeserializer {
private RelationType propertyRelationType;
public RelationStdDeserializer() {
this(null);
}
public RelationStdDeserializer(RelationType relationType) {
super(Relation.class);
this.propertyRelationType = relationType;
}
@Override
public Relation deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
JsonDeserializer<Object> deser = ctxt.findRootValueDeserializer(ctxt.getTypeFactory().constructType(Relation.class));
Relation instance = (Relation) deser.deserialize(p, ctxt);
if (this.propertyRelationType != null) {
instance.setRelationtype(this.propertyRelationType);
}
return instance;
}
@Override
public JsonDeserializer<?> createContextual(DeserializationContext ctxt, BeanProperty property) {
RelationTypeInfo typeInfo = property.getMember().getAllAnnotations().get(RelationTypeInfo.class);
return new RelationStdDeserializer(typeInfo.value());
}
}
Code ci-dessus pour une charge utile:
{
"owner": {
"id": 1
},
"tenant": {
"id": 2
}
}
imprime:
RelationsInput(owner=Relation(id=1, relationtype=OWNER), tenant=Relation(id=2, relationtype=TENANT))
Voir également:
- Désérialiser en chaîne ou objet à l'aide de Jackson
- Comment injecter une dépendance dans le désérialiseur personnalisé Jackson
- Jackson - désérialise la liste intérieure des objets en une liste d'un niveau supérieur
J'ai peur qu'il n'y en ait pas. Si vous souhaitez utiliser une annotation comme vous pouvez utiliser des désérialiseurs personnalisés. Créez d'abord quelque chose comme:
@RequiredArgsConstructor
public abstract class RelationTypeDeserializer extends JsonDeserializer<Relation> {
private final RelationType relationType;
@Override
public Relation deserialize(JsonParser p, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
Relation r = p.readValueAs(Relation.class);
r.setRelationtype(relationType);
return r;
}
}
Ensuite, implémentez les réels:
public class OwnerDeserializer extends RelationTypeDeserializer {
public OwnerDeserializer() {
super(RelationType.OWNER);
}
}
et
public class TenantDeserializer extends RelationTypeDeserializer {
public TenantDeserializer() {
super(RelationType.TENANT);
}
}
puis utilisez ceux comme:
@Getter @Setter
public class RelationsInput {
@JsonDeserialize(using = OwnerDeserializer.class)
private Relation owner;
@JsonDeserialize(using = TenantDeserializer.class)
private Relation tenant;
}
Si vous faites ce que vous demandez, vous aurez toujours la même valeur pour le champ RelationType. Quoi qu'il en soit, une solution possible consiste à utiliser un sérialiseur-désérialiseur personnalisé comme celui-ci:
public class RelationTypeJsonSerializer extends JsonSerializer<RelationType> {
@Override
public void serialize(RelationType value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
String string = value.toString();//or something like that, convert the enum to string as you like
gen.writeString(string);
}
}
public class RelationTypeJsonDeserializer extends JsonDeserializer<RelationType> {
@Override
public RelationType deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
String toString = p.getCodec().readValue(p, String.class);//read the value as string
return RelationType.build(toString);//convert back the value to object, use switch if needed)
}
}
ObjectMapper om = new ObjectMapper();
SimpleModule localDateModule = new SimpleModule("RelationType Module");
localDateModule.addSerializer(RelationType.class, new RelationTypeJsonSerializer());
localDateModule.addDeserializer(RelationType.class, new RelationTypeJsonDeserializer());
om.registerModule(localDateModule);
Pour convertir l'énumération d'avant en arrière, je recommande l'utilisation de map <String, RelationType>, super simple et fonctionne parfaitement, quelque chose comme ceci:
Map<String, RelationType> map = new HashMap<String, RelationType>();
map.put("Some Type", RelationType.SOME_TYPE);
map.put("Some Other Type", RelationType.SOME_OTHER_TYPE);
Et utilisez get (string) pour sérialiser Et get (value) pour désérialiser (trouver la clé d'une valeur) Ceci est un exemple général lorsque vous voulez sérialiser-désérialiser quelque chose qui n'a pas de sérialiseur-désérialiseur par défaut Regardez ceci pour plus d'informations Comment analyser la classe java Color en JSON avec Jackson?