Deserializacja Jacksona: czy mogę wstrzyknąć wartość z adnotacją w polu obiektu do deserializacji?
Mam taki obiekt do deserializacji:
public class RelationsInput {
Relation relation1;
Relation relation2;
}
podczas gdy klasa Relation
wygląda tak:
public class Relation {
RelationType relationtype;
... (more fields)
}
RelationType
jest wyliczeniem i nie jest wartością, która zostanie zdeserializowana, podczas gdy wszystkie inne są.
Czy to możliwe, żebym mógł „wstrzyknąć” wartość wyliczenia dla pola relationType
z adnotacją o polu w klasie RelationInput
? Jak poniżej
public class RelationsInput {
@RelationType(RelationType.OWNER)
Relation owner;
@RelationType(RelationType.TENANT)
Relation tenant;
}
Czy Jackson zapewnia coś takiego?
Odpowiedzi
Możesz spróbować zaimplementować niestandardowy deserializator z com.fasterxml.jackson.databind.deser.ContextualDeserializer
interfejsem. Pozwala na tworzenie instancji deserializatora z kontekstem.
Zobacz poniższy przykład:
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());
}
}
Powyższy kod dla ładunku:
{
"owner": {
"id": 1
},
"tenant": {
"id": 2
}
}
wydruki:
RelationsInput(owner=Relation(id=1, relationtype=OWNER), tenant=Relation(id=2, relationtype=TENANT))
Zobacz też:
- Deserializacja do łańcucha lub obiektu przy użyciu Jacksona
- Jak wstrzyknąć zależność do niestandardowego deserializatora Jackson
- Jackson - deserializacja wewnętrznej listy obiektów do listy jednego wyższego poziomu
Obawiam się, że nie ma czegoś takiego. Jeśli chcesz użyć czegoś takiego jak adnotacja, możesz użyć niestandardowych deserializatorów. Utwórz najpierw coś takiego:
@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;
}
}
Następnie zaimplementuj te rzeczywiste:
public class OwnerDeserializer extends RelationTypeDeserializer {
public OwnerDeserializer() {
super(RelationType.OWNER);
}
}
i
public class TenantDeserializer extends RelationTypeDeserializer {
public TenantDeserializer() {
super(RelationType.TENANT);
}
}
następnie użyj takich jak:
@Getter @Setter
public class RelationsInput {
@JsonDeserialize(using = OwnerDeserializer.class)
private Relation owner;
@JsonDeserialize(using = TenantDeserializer.class)
private Relation tenant;
}
Jeśli zrobisz to, o co prosisz, zawsze będziesz mieć tę samą wartość w polu RelationType. W każdym razie jednym możliwym rozwiązaniem jest użycie spersonalizowanego serializatora-deserializatora, takiego jak ten:
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);
Aby przekonwertować wyliczenie wstecz i dalej, polecam użycie map <String, RelationType>, super proste i działa idealnie, coś takiego:
Map<String, RelationType> map = new HashMap<String, RelationType>();
map.put("Some Type", RelationType.SOME_TYPE);
map.put("Some Other Type", RelationType.SOME_OTHER_TYPE);
I użyj get (string) do serializacji I pobierz (wartość) do deserializacji (znajdź klucz jakiejś wartości) To jest ogólny przykład kiedy chcesz serializować-deserializować coś co nie ma domyślnego serializatora-deserializatora Spójrz na to aby uzyskać więcej informacji Jak przeanalizować klasę Color Java do JSON z Jacksonem?