ジャクソンの逆シリアル化:逆シリアル化可能なオブジェクトのフィールドに注釈付きの値を挿入できますか?

Nov 24 2020

デシリアライズする次のようなオブジェクトがあります。

public class RelationsInput {

   Relation relation1;

   Relation relation2;

}

一方、クラスRelationは次のようになります。

public class Relation {

   RelationType relationtype;
   ... (more fields)

}

RelationType は列挙型であり、逆シリアル化される値ではありませんが、他のすべては逆シリアル化されます。

relationTypeクラス内のフィールドに注釈を付けて、フィールドの列挙値を「注入」することは可能RelationInputですか?次のように


public class RelationsInput {

   @RelationType(RelationType.OWNER)
   Relation owner;

   @RelationType(RelationType.TENANT)
   Relation tenant;

}

ジャクソンはこのようなものを提供しますか?

回答

1 MichałZiober Nov 25 2020 at 00:04

com.fasterxml.jackson.databind.deser.ContextualDeserializerインターフェイスを使用してカスタムデシリアライザーの実装を試みることができます。これにより、コンテキストを使用してデシリアライザーインスタンスを作成できます。

以下の例を参照してください。

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

ペイロードの上記のコード:

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

プリント:

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

参照:

  • Jacksonを使用して文字列またはオブジェクトに逆シリアル化する
  • Jacksonカスタムデシリアライザーに依存性を注入する方法
  • ジャクソン-オブジェクトの内部リストを1つの上位レベルのリストに逆シリアル化します
pirho Nov 24 2020 at 21:48

そんなことはないのではないかと思います。カスタムデシリアライザーを使用できるような注釈を使用したい場合。最初に次のようなものを作成します。

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

次に、実際のものを実装します。

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

そして

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

次に、次のようなものを使用します。

@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

要求したことを実行すると、RelationTypeフィールドの値は常に同じになります。とにかく、1つの可能な解決策は、次のようなパーソナライズされたシリアライザー-デシリアライザーを使用することです。

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

列挙型を前後に変換するには、map <String、RelationType>を使用することをお勧めします。これは非常にシンプルで、完璧に機能します。次のようになります。

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

そして、get(string)を使用してシリアル化し、get(value)を使用して逆シリアル化します(ある値のキーを見つけます)これは、デフォルトのシリアライザー-デシリアライザーがないものをシリアル化-逆シリアル化する場合の一般的な例ですこれを見てください詳細については、Jacksonを使用してColor javaクラスをJSONに解析する方法を教えてください。