Помимо того, что библиотека Джексона является еще одной популярной библиотекой JSON на Java, она является еще одной хорошо известной библиотекой, в первую очередь признанной за ее способность предлагать глубокие индивидуальные настройки.
В этой статье мы собираемся показать альтернативный способ использования библиотеки Джексона для того же решения, которое было продемонстрировано в этом посте о том, как обрабатывать переменные ответы в Gson .
Вам также может понравиться:
Обработка JSON с Джексоном
Добавление зависимостей
Прежде чем мы начнем, нам нужно добавить зависимости Maven в наш проект pom.xml
:
XML
1
<dependency>
2
<groupId>com.fasterxml.jackson.core</groupId>
3
<artifactId>jackson-core</artifactId>
4
<version>2.9.9</version>
5
</dependency>
6
<dependency>
8
<groupId>com.fasterxml.jackson.core</groupId>
9
<artifactId>jackson-databind</artifactId>
10
<version>2.9.9</version>
11
</dependency>
Следует отметить, что если мы уже используем модули Spring Boot и Spring Web Starter, включенные в нашем проекте, то нам просто не нужно добавлять никаких дополнительных зависимостей для Джексона.
Образец ответа
Давайте использовать этот пример ответа API:
JSON
xxxxxxxxxx
1
{
2
"id": 1,
3
"name": "sample article",
4
"comments": [
5
{"id":1, "text":"some comment text"},
6
{"id":2, "text":"some another comment text"}
7
]
8
}
Кроме того, ответ изменяется с обозначения Array на Object, когда есть только один comment
:
JSON
xxxxxxxxxx
1
{
2
"id": 1,
3
"name": "sample article",
4
"comments": {"id":1, "text":"some comment text"}
5
}
Модели ответа
Мы также будем использовать те же модели из предыдущей статьи :
Джава
xxxxxxxxxx
1
public class ArticleModel {
2
private Long id;
4
private String name;
6
private List<CommentModel> comments;
8
// standard getters and setters
10
}
12
public class CommentModel {
14
private Long id;
16
private String text;
18
// standard getters and setters
20
}
Использование ответа
Мы определили тип коллекции для comment
поля как List<CommentModel>
. Таким образом, мы можем автоматически сопоставить несколько комментариев в одном поле, потому что Джексон уже делает тяжелую работу за нас:
Джава
xxxxxxxxxx
1
String jsonString = "{
2
"id": 1,
3
"name": "sample article",
4
"comments": [
5
{"id":1, "text":"some comment text"},
6
{"id":2, "text":"some another comment text"}
7
]
8
}";
9
ObjectMapper mapper = new ObjectMapper();
11
ArticleModel model = mapper.readValue(this.mockResponseMultiValue, ArticleModel.class);
12
Assert.assertEquals(ArticleModel.class, model.getClass());
14
Assert.assertEquals(model.getComments().getClass(), CommentList.class);
15
Assert.assertEquals(model.getComments().get(0).getClass(), CommentModel.class);
16
Assert.assertEquals(1, model.getId().longValue());
18
Assert.assertEquals("sample article", model.getName());
19
Assert.assertEquals(2, model.getComments().size());
21
Assert.assertEquals(1, model.getComments().get(0).getId().longValue());
22
Assert.assertEquals("some comment text", model.getComments().get(0).getText());
23
Assert.assertEquals(2, model.getComments().get(1).getId().longValue());
24
Assert.assertEquals("some another comment text", model.getComments().get(1).getText());
Конечно, эта конфигурация разрешает множественные значения комментариев, если ответ не изменяется на единую структуру значений. В противном случае это просто не работает.
Следовательно, нам нужно определить собственный десериализатор, который будет обрабатывать как одно, так и несколько значений одновременно.
Пользовательские десериализаторы для Джексона
StdDeserializer
это абстрактный тип, а также базовый класс для распространенных десериализаторов в Джексоне.
Поэтому для реализации пользовательского поведения десериализации мы создадим реализацию StdDeserializer
. Это поведение будет отвечать за добавление любого отдельного значения в список так же, как Джексон автоматически добавляет несколько значений.
Итак, давайте создадим CommentListDeserializer
, который наследует базовый класс StdDeserializer
:
Джава
xxxxxxxxxx
1
public class CommentListDeserializer extends StdDeserializer<List> {
2
public CommentListDeserializer() {
4
this(null);
5
}
6
private CommentListDeserializer(JavaType valueType) {
8
super(valueType);
9
}
10
12
public List deserialize(JsonParser p, DeserializationContext ctxt)
13
throws IOException, JsonProcessingException {
14
return null;
16
}
17
}
Нам нужно, TypeReference
чтобы Джексон указал Collection
тип и тип элементов, в которые он также вовлечен.
Итак, давайте определим константу для типа List<CommentModel>
:
Джава
xxxxxxxxxx
1
private static final TypeReference<List<CommentModel>> LIST_OF_COMMENTS_TYPE =
2
new TypeReference<List<CommentModel>>() {};
Далее мы реализуем deserialize
метод, чтобы придумать ожидаемое поведение:
Джава
xxxxxxxxxx
1
2
public List deserialize(JsonParser p, DeserializationContext ctxt)
3
throws IOException, JsonProcessingException {
4
List<CommentModel> commentList = new ArrayList<>();
6
JsonToken token = p.currentToken();
8
if (JsonToken.START_ARRAY.equals(token)) {
10
List<CommentModel> listOfArticles = p.readValueAs(LIST_OF_COMMENTS_TYPE);
11
commentList.addAll(listOfArticles);
12
}
13
if (JsonToken.START_OBJECT.equals(token)) {
15
CommentModel article = p.readValueAs(CommentModel.class);
16
commentList.add(article);
17
}
18
return commentList;
20
}
Особенности десериализации в Джексоне
Хотя написание пользовательского десериализатора дает нам большую гибкость, в библиотеке Джексона есть много функций для конвертации. Предопределенные функции Джексона предоставляют практические способы настройки как сериализации, так и десериализации.
Надеемся, что эта ACCEPT_SINGLE_VALUE_AS_ARRAY
функция делает точную работу для нашего варианта использования. Конечно, мы могли бы использовать это для простоты:
Джава
xxxxxxxxxx
1
ObjectMapper mapper = new ObjectMapper();
2
mapper.enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY);
Также альтернативным способом является настройка @JsonFormat
аннотации. В нашем примере мы пойдем с @JsonFormat
аннотацией, чтобы установить ACCEPT_SINGLE_VALUE_AS_ARRAY
функцию.
Давайте удалим наш собственный десериализатор и добавим простую @JsonFormat
аннотацию к нашему comments
полю в ArticleModel
:
Джава
xxxxxxxxxx
1
with = Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY) (
2
private List<CommentModel> comments;
Таким образом, это автоматически добавит любое отдельное значение CommentModel
в наш список, как если бы оно уже было внутри массива. Если у нас нет дополнительных требований, использование функций Джексона может стать наиболее элегантным решением для нашего варианта использования.
У нас всегда может быть возможность проверить полный список функций из исходного кода .
Идти дальше с универсальными десериализаторами
Мы можем написать универсальные десериализаторы для различных требований, которые мы не могли бы выполнить с помощью предопределенных функций. Кроме того, в целях многократного использования мы можем пойти дальше, чтобы реорганизовать наш код, чтобы не создавать новые десериализаторы для каждого типа элемента.
Итак, мы реализуем новый класс SingleAwareListDeserializer
для обработки десериализации более настраиваемым способом:
Джава
xxxxxxxxxx
1
public class SingleAwareListDeserializer extends StdDeserializer<List>
2
implements ContextualDeserializer {
3
private Class<?> contentClassType;
5
public SingleAwareListDeserializer() {
7
this(null);
8
}
9
private SingleAwareListDeserializer(JavaType valueType) {
11
super(valueType);
12
}
13
15
public JsonDeserializer<?> createContextual(
16
DeserializationContext ctxt, BeanProperty property) throws JsonMappingException {
17
// we use ContextualDeserializer to obtain content class type
18
contentClassType = property.getType().getContentType().getRawClass();
19
return this;
20
}
21
23
public List deserialize(JsonParser p, DeserializationContext ctxt)
24
throws IOException, JsonProcessingException {
25
List list = new ArrayList<>();
27
JsonToken token = p.currentToken();
29
// if token is array type then perform object deserialization to each element element
30
if (JsonToken.START_ARRAY.equals(token)) {
31
while (p.nextToken() != null) {
32
if (JsonToken.START_OBJECT.equals(p.currentToken())) {
33
list.add(deserializeObject(p));
34
}
35
}
36
}
37
// if token is object type
39
if (JsonToken.START_OBJECT.equals(token)) {
40
list.add(deserializeObject(p));
41
}
42
return list;
44
}
45
private Object deserializeObject(JsonParser p) throws IOException {
47
// just use jackson default object deserializer by using element type
48
return p.readValueAs(contentClassType);
49
}
50
}
Теперь мы можем использовать один и тот же десериализатор для разных типов.
Конечно, мы должны заметить проблему, которая заключается в том, как получить тип класса элемента внутри нашей коллекции. Без информации о типе элемента Джексон не может инициализировать конкретный тип, но только LinkedHashMap
.
Тем не менее, есть обходной путь для получения связанной с типом информации внутри нашего десериализатора.
Мы использовали ContextualDeserializer
интерфейс, чтобы сообщить Джексону, что нам нужна информация о бине, с которым мы имеем дело. Таким образом, экземпляр BeanProperty
автоматически вводится, и мы получили тип класса элемента коллекции.
Наконец, чтобы использовать наш универсальный десериализатор, давайте изменим наше comments
поле:
Джава
xxxxxxxxxx
1
using = SingleAwareListDeserializer.class) (
2
private List<CommentModel> comments;
В результате мы можем обеспечить одинаковое поведение в SingleAwareListDeserializer
целом для всех типов.
в заключение
В этом руководстве мы узнали, как обрабатывать переменные ответы в Джексоне, используя встроенные функции и пользовательские десериализаторы.
Весь исходный код для примеров, показанных в этом руководстве, доступен на GitHub .
Дальнейшее чтение
Решение проблемы XML с Джексоном
Джексон Аннотации для JSON (часть 1): сериализация и десериализация