#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. Настройка происходит за счет обслуживания, и я много раз обжигался на этом 😉