web-dev-qa-db-ja.com

Jacksonでネストされた配列をArrayListとしてデシリアライズ

次のようなJSONがあります。

{
  "authors": {
    "author": [
      {
        "given-name": "Adrienne H.",
        "surname": "Kovacs"
      },
      {
        "given-name": "Philip",
        "surname": "Moons"
      }
    ]
   }
 }

著者情報を保存するクラスを作成しました:

public class Author {
    @JsonProperty("given-name")
    public String givenName;
    public String surname;
}

そして、2つのラッパークラス:

public class Authors {
    public List<Author> author;
}

public class Response {
    public Authors authors;
}

これは機能していますが、2つのラッパークラスを持つ必要はないようです。 Authorsクラスを削除し、Entryクラスのプロパティとしてリストを持つ方法を見つけたいです。ジャクソンではそのようなことが可能ですか?

更新

カスタムデシリアライザーでそれを解決しました:

public class AuthorArrayDeserializer extends JsonDeserializer<List<Author>> {

    private static final String AUTHOR = "author";
    private static final ObjectMapper mapper = new ObjectMapper();
    private static final CollectionType collectionType =
            TypeFactory
            .defaultInstance()
            .constructCollectionType(List.class, Author.class);

    @Override
    public List<Author> deserialize(JsonParser jsonParser, DeserializationContext deserializationContext)
            throws IOException, JsonProcessingException {

        ObjectNode objectNode = mapper.readTree(jsonParser);
        JsonNode nodeAuthors = objectNode.get(AUTHOR);

        if (null == nodeAuthors                     // if no author node could be found
                || !nodeAuthors.isArray()           // or author node is not an array
                || !nodeAuthors.elements().hasNext())   // or author node doesn't contain any authors
            return null;

        return mapper.reader(collectionType).readValue(nodeAuthors);
    }
}

そして、次のように使用します:

@JsonDeserialize(using = AuthorArrayDeserializer.class)
public void setAuthors(List<Author> authors) {
    this.authors = authors;
}

@wassgrenのアイデアに感謝します。

20
Dmitry

ラッパークラスを削除する場合は、少なくとも2つのアプローチがあります。 1つはJackson Tree Model(JsonNode)を使用することで、2つ目はUNWRAP_ROOT_VALUEと呼ばれる逆シリアル化機能を使用することです。


代替1:JsonNodeを使用

Jacksonを使用してJSONをデシリアライズする場合、作成するオブジェクトのタイプを制御する方法は複数あります。 ObjectMapperはJSONを逆シリアル化できます。 MapJsonNodereadTree- method経由)またはPOJO。

readTree- methodとPOJO変換を組み合わせると、ラッパーを完全に削除できます。例:

// The author class (a bit cleaned up)
public class Author {
    private final String givenName;
    private final String surname;

    @JsonCreator
    public Author(
            @JsonProperty("given-name") final String givenName,
            @JsonProperty("surname") final String surname) {

        this.givenName = givenName;
        this.surname = surname;
    }

    public String getGivenName() {
        return givenName;
    }

    public String getSurname() {
        return surname;
    }
}

逆シリアル化は次のようになります。

// The JSON
final String json = "{\"authors\":{\"author\":[{\"given-name\":\"AdrienneH.\",\"surname\":\"Kovacs\"},{\"given-name\":\"Philip\",\"surname\":\"Moons\"}]}}";

ObjectMapper mapper = new ObjectMapper();

// Read the response as a tree model
final JsonNode response = mapper.readTree(json).path("authors").path("author");

// Create the collection type (since it is a collection of Authors)
final CollectionType collectionType =
        TypeFactory
                .defaultInstance()
                .constructCollectionType(List.class, Author.class);

// Convert the tree model to the collection (of Author-objects)
List<Author> authors = mapper.reader(collectionType).readValue(response);

// Now the authors-list is ready to use...

このツリーモデルアプローチを使用すると、ラッパークラスを完全に削除できます。


代替案2:ラッパーの1つを削除し、ルート値をアンラップする 2番目のアプローチは、ラッパーの1つのみを削除することです。 Authorsクラスは削除するが、Response- wrapperは保持するものとします。 @JsonRootName-アノテーションを追加すると、後でトップレベルの名前を展開できます。

@JsonRootName("authors") // This is new compared to your example
public class Response {
    private final List<Author> authors;

    @JsonCreator
    public Response(@JsonProperty("author") final List<Author> authors) {
        this.authors = authors;
    }

    @JsonProperty("author")
    public List<Author> getAuthors() {
        return authors;
    }
}

次に、マッパーに対して次を使用します。

ObjectMapper mapper = new ObjectMapper();

// Unwrap the root value i.e. the "authors"
mapper.configure(DeserializationFeature.UNWRAP_ROOT_VALUE, true);
final Response responsePojo = mapper.readValue(json, Response.class);

2番目のアプローチはラッパークラスの1つを削除するだけですが、代わりに解析関数は非常にきれいです。

11
wassgren