Это происходит потому, что Gson работает через отражение и прогулки по всем полям в конкретном объекте для генерирования JSON и Gson полей итерация стратегия применяется более поле. На мой взгляд, вы можете создать специальные легкие классы для своего пользовательского объекта (см. Рисунок «Объект передачи данных») и создать поля, которые могут быть даже аннотированы с аннотациями Gson, такими как @SeriailzedName
и @Expose
. Преобразование между вашими наблюдаемыми списками и списками DTO является тривиальной операцией и может быть реализовано по-разному: Java 8 Streams API, Google Guava, Apache Collections (?) Или ваш собственный код в конце концов. В этом случае вы могли бы более точно контролировать стратегию трансформации на сайте вызова (я имею в виду, где вы его используете).
Далее Gson не знает о ObservableList
и его сериализованная JSON будет прекрасно (с ObservableList
является List
и List
случаи хорошо известны Gson) непосредственно перед десериализацией, где вы получите ClassCastException
где вы ожидаете ObservableList
(Gson будет десериализовать его как ArrayList
). Что касается SimpleStringProperty
: похоже, что здесь отлично играет отражение, и объекты свойств могут быть десериализованы с успехом.
Если вы хотите не иметь дело с DTO, вы можете настроить Gson для работы с материалами, связанными с JavaFx.
Во-первых, давайте предположим, что у вас есть простой объект с именем FooBar
:
final class FooBar {
final SimpleStringProperty foo;
final SimpleStringProperty bar;
FooBar(final String foo, final String bar) {
this.foo = new SimpleStringProperty(foo);
this.bar = new SimpleStringProperty(bar);
}
@Override
public String toString() {
return new StringBuilder("FooBar{")
.append("foo=").append(foo)
.append(", bar=").append(bar)
.append('}')
.toString();
}
}
ObservableStringValue
и его подкласс SimpleStringProperty
может иметь специальный тип адаптера (TypeAdapter<T>
), который будет иметь дело с потоковыми вперед JSON и назад писать и чтение ваших реальных объектов.Обратите внимание, что JsonSerializer<T>
и JsonDeserializer<T>
более просты в использовании, но поскольку они требуют использования рабочих деревьев JSON в памяти, тип адаптеров более эффективен, особенно если их легко реализовать (они могут быть очень сложными, хотя).
final class ObservableStringValueTypeAdapter
extends TypeAdapter<ObservableStringValue> {
private static final TypeAdapter<ObservableStringValue> observableStringValueTypeAdapter = new ObservableStringValueTypeAdapter();
private ObservableStringValueTypeAdapter() {
}
static TypeAdapter<ObservableStringValue> getObservableStringValueTypeAdapter() {
return observableStringValueTypeAdapter;
}
@Override
@SuppressWarnings("resource")
public void write(final JsonWriter out, final ObservableStringValue value)
throws IOException {
out.value(value.get());
}
@Override
public ObservableStringValue read(final JsonReader in)
throws IOException {
return new SimpleStringProperty(in.nextString());
}
}
Этот тип адаптера принимает любое наблюдаемое значение строки и записывает его в виде простой строки, а не все поля ObservableStringValue
экземпляр может иметь (это то, что вы получаете с name
, value
и valid
), но всегда преобразует строки в SimpleStringProperty
из-за отсутствия информации о типе. Информация типа может быть записана как часть объекта (полное имя, специальный код, представляющий реальный тип и т. Д.), Но я не думаю, что вам это нужно.
Далее ObservableListTypeAdapterFactory
реализации:
final class ObservableListTypeAdapterFactory
implements TypeAdapterFactory {
private static final TypeAdapterFactory observableListTypeAdapterFactory = new ObservableListTypeAdapterFactory();
private ObservableListTypeAdapterFactory() {
}
static TypeAdapterFactory getObservableListTypeAdapterFactory() {
return observableListTypeAdapterFactory;
}
@Override
public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> typeToken) {
if (ObservableList.class.isAssignableFrom(typeToken.getRawType())) {
final ParameterizedType parameterizedType = (ParameterizedType) typeToken.getType();
final Class<?> elementClass = (Class<?>) parameterizedType.getActualTypeArguments()[0];
final TypeAdapter<?> elementTypeAdapter = gson.getAdapter(elementClass);
@SuppressWarnings("unchecked")
final TypeAdapter<T> objectObservableListTypeAdapter = (TypeAdapter<T>) getObservableListTypeAdapter(elementTypeAdapter);
return objectObservableListTypeAdapter;
}
return null;
}
}
Как это работает: Gson использует маркеры типа точно определять типы и спрашивает завод, если он может обрабатывать тип, поставляемый вместе с маркером типа. Если нет, то возвращается null
. В приведенной выше реализации первая проверка проверяет, является ли данный тип ObservableList
, а затем его тип элемента извлекается из информации о параметризованном типе, а затем создается и возвращается новый наблюдаемый адаптер типа списка.
final class ObservableListTypeAdapter<E>
extends TypeAdapter<ObservableList<E>> {
private final TypeAdapter<E> elementTypeAdapter;
private ObservableListTypeAdapter(final TypeAdapter<E> elementTypeAdapter) {
this.elementTypeAdapter = elementTypeAdapter;
}
static <E> TypeAdapter<ObservableList<E>> getObservableListTypeAdapter(final TypeAdapter<E> elementTypeAdapter) {
return new ObservableListTypeAdapter<>(elementTypeAdapter);
}
@Override
@SuppressWarnings("resource")
public void write(final JsonWriter out, final ObservableList<E> value)
throws IOException {
out.beginArray();
for (final E element : value) {
elementTypeAdapter.write(out, element);
}
out.endArray();
}
@Override
public ObservableList<E> read(final JsonReader in)
throws IOException {
final ObservableList<E> list = observableArrayList();
in.beginArray();
while (in.peek() != END_ARRAY) {
list.add(elementTypeAdapter.read(in));
}
in.endArray();
return list;
}
}
Это один похож на ObservableStringValueTypeAdapter
и сделок с ObservableList
экземпляров и их соответствующих массивов. Опять же, TypeAdapter<T>
вместо Json(De)Serializer<T>
здесь, чтобы не создавать промежуточный JsonArray
, сохраняя память и производительность. Это немного сложнее, потому что он генерирует и анализирует массивы JSON, но только для маркеров [
и ]
: сериализация элементов выполняется делегированным экземпляром elementTypeAdapter
.
Теперь давайте соберем все вместе:
public final class Q42210761 {
private Q42210761() {
}
private static final Type fooBarObservableListType = new TypeToken<ObservableList<FooBar>>() {
}.getType();
private static final Gson gson = new GsonBuilder()
.registerTypeHierarchyAdapter(ObservableStringValue.class, getObservableStringValueTypeAdapter())
.registerTypeAdapterFactory(getObservableListTypeAdapterFactory())
.create();
public static void main(final String... args) {
final ObservableList<FooBar> source = observableArrayList(
new FooBar("foo-1", "bar-1"),
new FooBar("foo-2", "bar-2"),
new FooBar("foo-3", "bar-3")
);
out.println(source);
final String json = gson.toJson(source, fooBarObservableListType);
out.println(json);
final ObservableList<?> destination = gson.fromJson(json, fooBarObservableListType);
out.println(destination);
}
}
Обратите внимание, как настроен выше экземпляр Gson
. А вот эффективный выход:
[ИмяСтраницы {Foo = StringProperty [значение: Foo-1], бар = StringProperty [значение: бар-1]}, ИмяСтраницы {Foo = StringProperty [значение: Foo-2 ], bar = StringProperty [значение: bar-2]}, FooBar {foo = StringProperty [значение: foo-3], bar = StringProperty [значение: bar-3]}]
[{"foo": " Foo-1" , "бар": "бар-1"}, { "Foo": "Foo-2", "бар": "бар-2"}, { "Foo": "Foo-3",» bar ":" bar-3 "}]
[FooBar {foo = StringProperty [значение: foo-1], bar = StringProperty [значение: bar-1]}, FooBar {foo = StringProperty [значение: foo- 2], bar = StringProperty [значение: bar-2]}, FooBar {foo = StringProperty [значение: foo-3], bar = StringPropert y [значение: bar-3]}]
Обратите внимание, что выход JSON теперь намного короче, не содержит других элементов и может быть безопасно десериализован обратно.
Поскольку вы сериализуете объекты, которые, вероятно, не очень подходят для целей сериализации. Почему бы просто не преобразовать наблюдаемый список в легкий список serialiazable с простыми элементами DTO с полями «String»? Чтобы загрузить данные назад, вы можете просто получить сериализуемый список и преобразовать его в наблюдаемый список.Преобразование простое: Stream API, Google Guava или что-то еще. Кроме того, ваш JSON будет в несколько раз легче после сброса внутренних свойств SimpleStringProperty. –
Я могу сказать вам, что наблюдаемая сериализация ListArray очень хорошо работает с GSON. Это явление я наблюдал только в ситуации, описанной в моем вопросе. В любом случае, спасибо, я уже принял во внимание предлагаемые вами решения. – Bogojob
С технической точки зрения, Gson работает с полями только через отражение, и он может выглядеть «слишком глубоко» в конкретной структуре объекта, которая может быть изменена с версии Java до версии Java, что делает документы JSON несовместимыми между версиями/реализациями Java. Как правило, гораздо лучше не сериализовывать объекты неизвестной структуры (они все равно инкапсулированы) и использовать свой публичный API, чтобы читать его данные и строить их обратно. Например, я не смог воспроизвести «помощник»: {«наблюдаемый»: {}} 'локально, вероятно, из-за разной реализации JavaFX (или что бы это могло сделать). –