Java — Сериализация повторяющегося<Map.Entry> с помощью Gson

#java #serialization #gson #entryset

#java #сериализация #gson #набор записей

Вопрос:

Я пытаюсь сериализовать «Iterable» из type Map.Entry с помощью библиотеки Google GSON — и я получил пустой вывод.

вот пример кода:

 static private Iterable<Map.Entry<String, Integer>> getIterable(){
    HashMap<String, Integer> map = new HashMap<>();
    map.put("1", 1);
    map.put("2", 2);

    Iterable<Map.Entry<String, Integer>> iterable = map.entrySet();
    return  iterable;

}

public static void main(String[] args) {

    Iterable<Map.Entry<String, Integer>> myIterable = getIterable();
    GsonBuilder builder = new GsonBuilder();
    Gson gson = builder.enableComplexMapKeySerialization().create();
    String  a= gson.toJson(myIterable);
    System.out.println(a);

}
  

и это результат :

[{},{}]

есть идеи, что я делаю не так?

спасибо 🙂

версия java: 1.8
версия gson: 2.6.2

Комментарии:

1. почему бы не проанализировать саму карту? gson.toJson(map);

2. Это всего лишь небольшой пример, но в реальном мире я использую API, которые возвращают итерируемое..

Ответ №1:

Более чистый подход мог бы быть

 final Iterable<Entry<String, Integer>> iterable = getIterable();
final Gson gson = new Gson();
final JsonArray jsonArray = new JsonArray();

for (final Entry<String, Integer> entry : iterable) {
    final JsonElement jsonElement = gson.toJsonTree(entry);
    jsonElement.getAsJsonObject().remove("hash");
    jsonArray.add(jsonElement);
}
  

Или Stream версию, которая мне нравится

 StreamSupport.stream(iterable.spliterator(), false)
             .map(gson::toJsonTree)
             .map(JsonElement::getAsJsonObject)
             .peek(obj -> obj.remove("hash"))
             .collect(of(
                     JsonArray::new,
                     (array, obj) -> array.add(obj),
                     (output, toMerge) -> {
                         output.addAll(toMerge);
                         return output;
                     }
             ));
  

вывод: [{"key":"1","value":1},{"key":"2","value":2}]


TL; DR: вам нужен пользовательский TypeAdapterFactory и пользовательский TypeAdapter .

Смотрите этот метод на TypeAdapters

 public static <TT> TypeAdapterFactory newFactory(
    final TypeToken<TT> type, final TypeAdapter<TT> typeAdapter) {
  return new TypeAdapterFactory() {
    @SuppressWarnings("unchecked") // we use a runtime check to make sure the 'T's equal
    @Override public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
      return typeToken.equals(type) ? (TypeAdapter<T>) typeAdapter : null;
    }
  };
}
  

введите описание изображения здесь

Без пользовательского TypeAdapterFactory

 typeToken.equals(type)
  

возвращает false , и даже пользовательский TypeAdapter<Entry> параметр не используется.


Проблема заключается здесь, в ReflectiveTypeAdapterFactory#write

 @Override public void write(JsonWriter out, T value) throws IOException {
  if (value == null) {
    out.nullValue();
    return;
  }

  out.beginObject();
  try {
    for (BoundField boundField : boundFields.values()) {
      if (boundField.writeField(value)) {
        out.name(boundField.name);
        boundField.write(out, value);
      }
    }
  } catch (IllegalAccessException e) {
    throw new AssertionError(e);
  }
  out.endObject();
}
  

и в ReflectiveTypeAdapterFactory#getBoundFields

 private Map<String, BoundField> getBoundFields(Gson context, TypeToken<?> type, Class<?> raw) {
  Map<String, BoundField> result = new LinkedHashMap<String, BoundField>();

  if (raw.isInterface()) {
    return resu<
  }
  

Gson распознает входные данные Entry ( Class<?> raw параметр) как

 interface Map.Entry<K, V> { ... }
  

Поэтому

 if (raw.isInterface())
  

выдает true , и возвращается пустое boundFields LinkedHashMap значение.
Таким образом, здесь

 for (BoundField boundField : boundFields.values()) { ... }
  

цикл не выполняется, и никакие значения не извлекаются и не записываются с помощью

 boundField.write(...)
  

Комментарии:

1. @Eyalleshem Это ничего. В конечном счете, я бы рекомендовал вам сериализовать саму карту, а не ее entrySet. Настройка происходит за счет обслуживания, и я много раз обжигался на этом 😉