簡體   English   中英

使用 Jackson,如何使用返回具有通用類型的包裝器的 static 工廠方法反序列化值?

[英]Using Jackson, how can I deserialize values using static factory methods that return wrappers with a generic type?

使用 Jackson,我想將一些值反序列化為通用包裝對象,我為每種類型都有一個特定的 static 工廠方法。
然而,Jackson 似乎並沒有接受這一間接層,即使我用@JsonCreator注釋工廠方法。

com.fasterxml.jackson.databind.exc.InvalidDefinitionException:無法構造Wrapped實例(不存在 Creator,如默認構造函數):沒有字符串參數構造函數/工廠方法可從字符串值('Carl')反序列化

我怎樣才能使 Jackson 使用返回具有通用類型的包裝器的工廠方法?

這個獨立的代碼說明了我的問題:

class Request {
    // I want to deserialize into these fields
    @JsonProperty Wrapped<Person> person;
    @JsonProperty Wrapped<Score> score;
}

class Wrapped<T> {
    // This is my generic wrapper type.
    // Its construction is non-trivial: it is impossible to first construct the value before wrapping it.
    // Therefor, construction is performed by the factory methods of the concrete value classes (Person, Score, ...).

    // Let's say for simplicity that it did have a simple constructor:
    T value;
    public Wrapped(T value) {
        this.value = value;
    }
}

class Person {
    @JsonCreator
    public static Wrapped<Person> createWrapped(String name) {
        // complex construction of the wrapped person
        return new Wrapped<>(new Person(name));
    }

    @JsonValue
    String name;
    public Person(String name) {
        this.name = name;
    }
}

class Score {
    @JsonCreator
    public static Wrapped<Score> createWrapped(int score) {
        // complex construction of the wrapped score
        return new Wrapped<>(new Score(score));
    }

    @JsonValue
    int score;
    public Score(int score) {
        this.score = score;
    }
}

class Example {
    private static final String JSON_REQUEST =
            """
            {
              "person":"Carl",
              "score":20
            }
            """;

    public static void main(String[] args) throws Exception {
        Request request = new ObjectMapper()
                .readValue(JSON_REQUEST, Request.class);
        System.out.println(request.person.value.name);
        System.out.println(request.score.value.score);
    }
}

需要注意的是類型信息只在java類中,不應該在json中。

一種解決方案,添加一個DTO:

public class RequestDTO {
    @JsonValue
    String  person;
    @JsonValue
    Integer score;

    /**
     * @return the person
     */
    public String getPerson() {
        return person;
    }

    /**
     * @return the score
     */
    public Integer getScore() {
        return score;
    }

    /**
     * @param person the person to set
     */
    public void setPerson(String person) {
        this.person = person;
    }

    /**
     * @param score the score to set
     */
    public void setScore(Integer score) {
        this.score = score;
    }

    public RequestDTO() {
        
    }
    
    public RequestDTO(String person, Integer score) {
        this.person = person;
        this.score = score;
    }
}

並更改請求定義以使用 Mode.DELEGATING

public class Request {
    // I want to deserialize into these fields
    @JsonProperty Wrapped<Person> person;
    @JsonProperty Wrapped<Score> score;

    @JsonCreator(mode=Mode.DELEGATING)
    public static Request createWrapped(RequestDTO requestDTO) {
        // complex construction of the wrapped person
        Request req = new Request();
        req.person = new Wrapped<>(new Person(requestDTO.getPerson()));
        req.score = new Wrapped<>(new Score(requestDTO.getScore()));
        
        return req ;
    }
}

@p3consulting 的回答讓我朝着正確的方向前進,但它導致了完全不同的事情。

Jackson 有一個叫做Converter的東西,它完全符合我的要求。

我為每個包裝值類型創建了轉換器,
然后在請求中注釋屬性以使用這些轉換器:

class Request {
    @JsonDeserialize(converter = WrappedPersonDeserializer.class)
    Wrapped<Person> person;

    @JsonDeserialize(converter = WrappedScoreDeserializer.class)
    Wrapped<Score> score;
}
class PersonConverter
        extends StdConverter<String, Wrapped<Person>> {

    @Override
    public Wrapped<Person> convert(String value) {
        return Person.createWrapped(value);
    }
}

class ScoreConverter
        extends StdConverter<Integer, Wrapped<Score>> {

    @Override
    public Wrapped<Score> convert(Integer score) {
        return Score.createWrapped(score);
    }
}

對於具有更復雜簽名的工廠方法,您可以使用 DTO 使其工作,例如:

class WrappedPersonConverter2
        extends StdConverter<WrappedPersonConverter2.DTO, Wrapped<Person>> {

    @Override
    public Wrapped<Person> convert(WrappedPersonConverter2.DTO dto) {
        return Person.createWrapped(dto.first, dto.last);
    }

    public static class DTO {
        public int first;
        public int last;
    }
}

我不敢相信這是如此簡單,但我花了這么長時間才找到。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM