Deserialize вложенный массив как ArrayList с Jackson

У меня есть часть 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;
}

И два класса оболочки:

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

public class Response {
    public Authors authors;
}

Это работает, но наличие двух классов-оболочек кажется ненужным. Я хочу найти способ удалить класс Authors и иметь список как свойство класса Entry. Возможно ли подобное с Джексоном?

Update

Решено, что с пользовательским десериализатором:

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 за эту идею.

Ответы

Ответ 1

Я вижу по крайней мере два подхода, чтобы сделать это, если вы хотите избавиться от классов-оболочек. Во-первых, используйте древовидную модель Jackson (JsonNode), а вторая - использовать функцию десериализации, называемую UNWRAP_ROOT_VALUE.


Альтернатива 1: используйте JsonNode

При десериализации JSON с использованием Jackson существует несколько способов управления тем типом объектов, которые должны быть созданы. ObjectMapper может десериализовать JSON, например, a Map, JsonNode (через метод readTree) или POJO.

Если вы комбинируете метод readTree с преобразованием 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...

Если вы используете этот подход Tree Model, классы-оболочки могут быть полностью удалены.


Альтернатива 2: удалите одну из оберток и разверните корневое значение Второй подход - удалить только одну из оберток. Предположим, что вы удалите класс Authors, но сохраните Response -wrapper. Если вы добавите a @JsonRootName -nnotation, вы можете позже развернуть имя верхнего уровня.

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

Второй подход только удаляет один из классов-оболочек, но вместо этого функция синтаксического анализа довольно хороша.