Статьи

Десериализация Json с помощью токенов типа Джексона и Супер

Datatables — это плагин jquery для представления табличной информации — он может улучшать простую таблицу или использовать данные на основе AJAX и представлять информацию в табличной форме.

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
{
   'aaData':[
      {
         'id':1,
         'first':'one',
         'last':'one',
         'addresses':[
 
         ],
         'version':0
      },
      {
         'id':2,
         'first':'two',
         'last':'two',
         'addresses':[
 
         ],
         'version':0
      }
   ],
   'iTotalRecords':100,
   'iTotalDisplayRecords':10,
   'success':true
}

Может быть определен универсальный тип Java, который Джексон может использовать для генерации json типа, показанного выше, рассмотрим следующий универсальный тип Java:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package mvcsample.types;
import java.util.List;
 
public class ListWrapper<T> {
    private List<T> aaData;
    private int iTotalRecords;
    private int iTotalDisplayRecords;
    private  Boolean success;
 
    public List<T> getAaData() {
  return aaData;
 }
 public void setAaData(List<T> aaData) {
  this.aaData = aaData;
 }
 public int getiTotalRecords() {
  return iTotalRecords;
 }
 public void setiTotalRecords(int iTotalRecords) {
  this.iTotalRecords = iTotalRecords;
 }
 public int getiTotalDisplayRecords() {
  return iTotalDisplayRecords;
 }
 public void setiTotalDisplayRecords(int iTotalDisplayRecords) {
  this.iTotalDisplayRecords = iTotalDisplayRecords;
 }
 public Boolean getSuccess() {
  return success;
 }
 public void setSuccess(Boolean success) {
  this.success = success;
 }  
}

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

01
02
03
04
05
06
07
08
09
10
11
12
13
List<Member> members = new ArrayList<>();
members.add(new Member('one', 'one'));
members.add(new Member('two', 'two'));
ListWrapper<Member> membersWrapper = new ListWrapper<>();
membersWrapper.setAaData(members);
membersWrapper.setiTotalDisplayRecords(10);
membersWrapper.setiTotalRecords(100);
ObjectMapper objectMapper = new ObjectMapper();
 
StringWriter w = new StringWriter();
objectMapper.writeValue(w, membersWrapper);
String json = w.toString();
System.out.println(json);

Аналогичным образом может быть сгенерирован json для любого другого типа.

Тем не менее, как же наоборот, генерируя тип Java с учетом json.

Опять же, рассмотрим случай, когда указанный в начале json должен быть преобразован в ListWrapper <Member>, я могу попробовать десериализацию следующим образом:

1
2
ObjectMapper objectMapper = new ObjectMapper(); 
ListWrapper<Member> membersUpdated = objectMapper.readValue(json, ListWrapper.class);

Обратите внимание, что выше я не могу ссылаться на тип класса как ListWrapper <Member> .class, я могу только ссылаться на него как ListWrapper.class.

Это, однако, не будет работать, и результирующий тип не будет оберткой вокруг класса Member, так как во время выполнения Джексон не подозревает, что ему нужно сгенерировать ListWrapper <Member>.

Исправление заключается в том, чтобы каким-то образом передать информацию о типе ListWrapper Джексону, и именно здесь в него помещаются токены типа Super . В статье объясняется, как это работает очень подробно, суть в том, что в то время как стирание типа действительно удаляет информацию о типе из параметризованных экземпляров универсальный тип, однако тип сохраняется в подклассах универсальных классов.

Например, Рассмотрим следующий класс StringList, производный от ArrayList <String>, можно обнаружить, что параметром типа базового класса является String, как показано в тесте ниже:

01
02
03
04
05
06
07
08
09
10
11
12
13
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
 
public class StringList extends ArrayList<String>{
 
 public static void main(String[] args) {
  StringList list = new StringList();
  Type superClassType = list.getClass().getGenericSuperclass();
  ParameterizedType parameterizedType = (ParameterizedType)superClassType;
  System.out.println(parameterizedType.getActualTypeArguments()[0]);
 }
}

Это применимо для случая, когда подкласс определяется как анонимный класс также следующим образом:

1
2
3
4
ArrayList<String> list = new ArrayList<String>(){};
Type superClassType = list.getClass().getGenericSuperclass();
ParameterizedType parameterizedType = (ParameterizedType)superClassType;
System.out.println(parameterizedType.getActualTypeArguments()[0]);

Это то, что используется внутри шаблона токенов Super Type для поиска типа параметризованного типа. Абстрактный класс Джексона com.fasterxml.jackson.core.type.TypeReference реализует это, и с помощью этого десериализация Джексона будет работать следующим образом:

1
2
3
4
5
6
import com.fasterxml.jackson.core.type.TypeReference;
 
....
ListWrapper<Member> membersWrapper = objectMapper.readValue(json, new TypeReference<ListWrapper<Member>>() {});
 
ListWrapper<Address> addressWrapper = objectMapper.readValue(json, new TypeReference<ListWrapper<Address>>() {});

Таким образом, два различных параметризованных типа могут быть десериализованы с использованием универсального типа и представления json.

Ресурсы:

Ссылка: десериализация Json с помощью токенов типа Джексона и Super от нашего партнера JCG Биджу Кунджуммена в блоге all and sundry.