Jackson-Deserialisierung: Kann ich einen Wert mit einer Anmerkung in das Feld des zu deserialisierbaren Objekts einfügen?

Nov 24 2020

Ich habe ein Objekt wie dieses zum Deserialisieren:

public class RelationsInput {

   Relation relation1;

   Relation relation2;

}

während die Klasse so Relationaussieht:

public class Relation {

   RelationType relationtype;
   ... (more fields)

}

RelationType ist en enum und ist kein Wert, der deserialisiert wird, während alle anderen es sind.

Ist es möglich, dass ich den Aufzählungswert für das Feld relationTypemit einer Anmerkung zum Feld in der Klasse "einfügen" kann RelationInput? Wie das folgende


public class RelationsInput {

   @RelationType(RelationType.OWNER)
   Relation owner;

   @RelationType(RelationType.TENANT)
   Relation tenant;

}

Bietet Jackson so etwas an?

Antworten

1 MichałZiober Nov 25 2020 at 00:04

Sie können versuchen, einen benutzerdefinierten Deserialisierer mit com.fasterxml.jackson.databind.deser.ContextualDeserializerSchnittstelle zu implementieren . Es ermöglicht die Erstellung einer Deserialisiererinstanz mit einem Kontext.

Siehe Beispiel unten:

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());
    }
}

Oben Code für eine Nutzlast:

{
  "owner": {
    "id": 1
  },
  "tenant": {
    "id": 2
  }
}

Drucke:

RelationsInput(owner=Relation(id=1, relationtype=OWNER), tenant=Relation(id=2, relationtype=TENANT))

Siehe auch:

  • Deserialisieren Sie mit Jackson zu String oder Object
  • So injizieren Sie Abhängigkeiten in Jackson Custom Deserializer
  • Jackson - Deserialisieren Sie die innere Liste der Objekte in eine Liste einer höheren Ebene
pirho Nov 24 2020 at 21:48

Ich fürchte, so etwas gibt es nicht. Wenn Sie eine Annotation wie diese verwenden möchten, können Sie benutzerdefinierte De-Serializer verwenden. Erstelle so etwas wie:

@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;
    }
}

Dann implementieren Sie die tatsächlichen:

public class OwnerDeserializer extends RelationTypeDeserializer {
    public OwnerDeserializer() {
        super(RelationType.OWNER);
    }   
}

und

public class TenantDeserializer extends RelationTypeDeserializer {
    public TenantDeserializer() {
        super(RelationType.TENANT);
    }
}

dann benutze solche wie:

@Getter @Setter
public class RelationsInput {
    @JsonDeserialize(using = OwnerDeserializer.class)
    private Relation owner;
    @JsonDeserialize(using = TenantDeserializer.class)
    private Relation tenant;
}
JesusHernandezBarrios Nov 25 2020 at 00:24

Wenn Sie das tun, wonach Sie fragen, haben Sie immer den gleichen Wert für das Feld RelationType. Eine mögliche Lösung ist die Verwendung eines personalisierten Serializer-Deserializers wie folgt:

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);

Um die Aufzählung vor und zurück zu konvertieren, empfehle ich die Verwendung von map <String, RelationType>, super einfach und perfekt, ungefähr so:

Map<String, RelationType> map = new HashMap<String, RelationType>();
map.put("Some Type", RelationType.SOME_TYPE);
map.put("Some Other Type", RelationType.SOME_OTHER_TYPE);

Verwenden Sie get (string) zum Serialisieren und get (value) zum Deserialisieren (Suchen Sie den Schlüssel eines Werts). Dies ist ein allgemeines Beispiel, wenn Sie etwas serialisieren-deserialisieren möchten, das keinen Standard-Serializer-Deserializer hat Weitere Informationen Wie analysiere ich die Color Java-Klasse mit Jackson in JSON?