Найти в Дзене

Начало работы с пользовательской десериализацией в Jackson

Оглавление

1. Обзор

В этом кратком руководстве будет показано, как использовать Jackson 2 для десериализации JSON с помощью пользовательского десериализатора.

2. Стандартная десериализация

Давайте начнем с определения двух сущностей и посмотрим, как Джексон будет десериализовывать представление JSON для этих сущностей без каких-либо настроек:

public class User {
public int id;
public String name;
}
public class Item {
public int id;
public String itemName;
public User owner;
}

Теперь давайте определим представление в формате JSON, которое мы хотим десериализовать:

{
"id": 1,
"itemName": "theItem",
"owner": {
"id": 2,
"name": "theUser"
}
}

И, наконец, давайте распакуем этот JSON-файл в Java-объекты:

Item itemWithOwner = new ObjectMapper().readValue(json, Item.class);

3. Пользовательский десериализатор в ObjectMapper

В предыдущем примере представление в формате JSON идеально соответствовало объектам Java.

Далее мы упростим JSON:

{
"id": 1,
"itemName": "theItem",
"createdBy": 2
}

При отмене обмена данными с точно такими же объектами, по умолчанию, это, конечно, не сработает:

com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException:
Unrecognized field "createdBy" (class org.baeldung.jackson.dtos.Item),
not marked as ignorable (3 known properties: "id", "owner", "itemName"])
at [Source: java.io.StringReader@53c7a917; line: 1, column: 43]
(through reference chain: org.baeldung.jackson.dtos.Item["createdBy"])

Мы решим эту проблему, выполнив нашу собственную десериализацию с помощью пользовательского десериализатора:

public class ItemDeserializer extends StdDeserializer<Item> {

public ItemDeserializer() {
this(null);
}

public ItemDeserializer(Class<?> vc) {
super(vc);
}

@Override
public Item deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
JsonNode node = jp.getCodec().readTree(jp);
int id = (Integer) ((IntNode) node.get("id")).numberValue();
String itemName = node.get("itemName").asText();
int userId = (Integer) ((IntNode) node.get("createdBy")).numberValue();

return new Item(id, itemName, new User(userId, null));
}
}

Как мы видим, десериализатор работает со стандартным джексоновским представлением JSON — JsonNode. Как только входной JSON представлен в виде JsonNode, мы можем извлечь из него соответствующую информацию и создать нашу собственную сущность Item.

Проще говоря, нам нужно
зарегистрировать этот пользовательский десериализатор и десериализовать JSON в обычном режиме:

ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.addDeserializer(Item.class, new ItemDeserializer());
mapper.registerModule(module);

Item readValue = mapper.readValue(json, Item.class);

4. Пользовательский десериализатор для класса

В качестве альтернативы, мы также можем зарегистрировать десериализатор непосредственно в классе:

@JsonDeserialize(using = ItemDeserializer.class)
public class Item {
...
}

С десериализатором, определенным на уровне класса, нет необходимости регистрировать его в ObjectMapper — средство отображения по умолчанию будет работать нормально:

Item itemWithOwner = new ObjectMapper().readValue(json, Item.class);

Этот тип настройки для каждого класса очень полезен в ситуациях, когда у нас может не быть прямого доступа к необработанному ObjectMapper для настройки.

5. Пользовательский десериализатор для универсального типа

Давайте теперь создадим класс-оболочку, который содержит только уникальный аргумент универсального типа T:

public class Wrapper<T> {

T value;

public T getValue() {
return value;
}

public void setValue(T value) {
this.value = value;
}
}

Вместо этого пользовательский атрибут нашего элемента теперь будет иметь тип Wrapper<User>:

public class Item {
public int id;
public String itemName;
public Wrapper<User> owner;
}

Давайте реализуем пользовательский десериализатор для этого случая.

Во-первых, нам нужно реализовать интерфейс ContextualDeserializer, чтобы мы могли получить тип объекта внутри оболочки. Мы сделаем это, переопределив метод createContextual(). Когда вызывается этот метод, контекст разрешается, и фактическое содержимое оболочки может быть получено с помощью аргумента BeanProperty.

Нам также нужно расширить JsonDeserializer. Таким образом, мы можем задать конкретный тип значения оболочки внутри deserialize():

public class WrapperDeserializer extends JsonDeserializer<Wrapper<?>> implements ContextualDeserializer {

private JavaType type;

@Override
public JsonDeserializer<?> createContextual(DeserializationContext ctxt, BeanProperty property) {
this.type = property.getType().containedType(0);
return this;
}

@Override
public Wrapper<?> deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
Wrapper<?> wrapper = new Wrapper<>();
wrapper.setValue(deserializationContext.readValue(jsonParser, type));
return wrapper;
}
}

Еще раз, нам нужно зарегистрировать наш пользовательский десериализатор, чтобы иметь возможность десериализовывать JSON:

ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.addDeserializer(Wrapper.class, new WrapperDeserializer());
mapper.registerModule(module);

Item readValue = mapper.readValue(json, Item.class);

6. Заключение

В этой статье показано, как использовать Jackson 2 для чтения нестандартных входных данных в формате JSON, а также как сопоставить эти входные данные с любым Java entity graph с полным контролем над отображением.

Оригинал статьи: https://www.baeldung.com/jackson-deserialization