Десериализация Gson: установить конечное поле - PullRequest
3 голосов
/ 02 декабря 2011

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

Пример:

public final class Screen{

    @Expose
    private final List<WidgetDefinition> children       = null;
    @Expose
    private final String name           = null;
}

public final class AWidget implements WidgetDefinition {
    @Expose
    private final String name           = null;
}

Я десериализирую экран с помощью пользовательского десериализатора для WidgetDefinition, показанногониже.'name' на экране установлено правильно, 'name' в AWidget остается нулевым.

final class Deserializer implements JsonDeserializer<WidgetDefinition> {

    public WidgetDefinition deserialize(final JsonElement json, final Type type,
                                        final JsonDeserializationContext context) {

        JsonObject jsonObject = json.getAsJsonObject();

        String typeName = jsonObject.get("type").getAsString();
        if (typeName.equals("awidget")) {
            return context.deserialize(json, AWidget.class);
        } else {
            return null;
        }
    }
}

Редактировать: Интересно, нужно ли что-то делать с этим:

Gson 1.7 не будет сериализовать поля подкласса в элементах коллекции.2.0 добавляет эту дополнительную информацию.

(https://sites.google.com/site/gson/gson-roadmap)

Ответы [ 4 ]

12 голосов
/ 27 июня 2012

Gson использует отражение для установки конечных полей (через .setAccessible(true)), поэтому описываемая вами проблема (и другие, связанные с ней), вероятно, связаны с тем, как Java обрабатывает финалы ...

JLS 17.5.3 Последующая модификация конечных полей

В некоторых случаях, например, при десериализации, системе потребуется изменить конечные поля объекта после построения.конечные поля могут быть изменены с помощью отражения и других зависящих от реализации средств.Единственный шаблон, в котором это имеет разумную семантику, это шаблон, в котором объект создается, а затем обновляются конечные поля объекта.Объект не должен быть видимым для других потоков, а также не должны читаться последние поля, пока не будут завершены все обновления последних полей объекта.Замораживание конечного поля происходит как в конце конструктора, в котором установлено последнее поле, так и сразу после каждой модификации конечного поля с помощью отражения или другого специального механизма.

Даже в этом случае существует числоосложнений.Если конечное поле инициализируется константным выражением времени компиляции (§15.28) в объявлении поля, изменения в последнем поле могут не наблюдаться, так как использование этого конечного поля заменяется во время компиляции значением константного выражения.

Другая проблема заключается в том, что спецификация допускает агрессивную оптимизацию конечных полей.Внутри потока допустимо переупорядочивать операции чтения конечного поля с теми изменениями конечного поля, которые не имеют места в конструкторе.

6 голосов
/ 05 декабря 2011

Немного трудно решить точную проблему в первоначальном вопросе, поскольку полный минимальный код и примеры не были предоставлены.Возможно, поможет следующее понимание того, как Gson создает экземпляры целевых объектов во время десериализации.

Gson 1.7.1 и 2.0 оба учитывают окончательные назначения полей во время операций десериализации ванили, а начальные конечные назначения полей не изменяются.Например:

import com.google.gson.Gson;

public class GsonFoo
{
  public static void main(String[] args)
  {
    // {"name":"Fred","id":42}
    String json1 = "{\"name\":\"Fred\",\"id\":42}";
    System.out.println(new Gson().fromJson(json1, Bar1.class));
    // output:
    // Bar1: name=Fred, id=-1
  }
}

class Bar1
{
  String name = "BLANK";
  final int id = -1;

  @Override
  public String toString()
  {
    return String.format("Bar1: name=%s, id=%d", name, id);
  }
}

С другой стороны, например, создание экземпляра во время десериализации, поскольку Gson использует sun.misc.Unsafe - а не определяемый пользователем конструктор - присваивания конечных полей, явно определенных в любом конструкторе,не соблюдаетсяНапример:

import com.google.gson.Gson;

public class GsonFoo
{
  public static void main(String[] args)
  {
    // {"name":"Fred","id":42}
    String json1 = "{\"name\":\"Fred\",\"id\":42}";
    System.out.println(new Gson().fromJson(json1, Bar1.class));
    // output:
    // Bar1: name=Fred, id=42
  }
}

class Bar1
{
  String name = "BLANK";
  final int id;

  Bar1()
  {
    id = -1;
  }

  @Override
  public String toString()
  {
    return String.format("Bar1: name=%s, id=%d", name, id);
  }
}

Таким образом, во время десериализации ванили невозможно переназначить окончательное присвоение поля любым данным из входящего JSON, хотя это может показаться возможным, если происходит окончательное присвоение поляв определяемом пользователем конструкторе. **

** Я не уверен, допускает ли спецификация JVM некоторую снисходительность реализации, так что при работе на разных JVM может наблюдаться поведение, отличное от описанного выше.

5 голосов
/ 22 января 2015

Специфика вопроса не совсем соответствует общему названию, но у меня возникла та же проблема, и вопрос для меня является лучшим результатом Google, поэтому я отвечу здесь, несмотря на возраст вопроса.

Чтобы суммировать результаты других ответов здесь, кажется, что прошлые и текущие версии Gson десериализуют объектов, но не будут десериализовать примитивы , если онибыли инициализированы полем.

public final class A {
    // Gson will properly deserialise foo, regardless of initialisation.
    public final String foo = "bar";
}
public final class B {
    // i will always be 42.
    public final int i = 42;
}
public final class C {
    // Gson will properly deserialise j
    public final int j;
    public C() { j = 37; }
}

Различные несоответствия в этом поведении было достаточно для меня, чтобы решить определить пользовательские адаптеры типов и опустить конструкторы по умолчанию.Поскольку Gson 2.1, TypeAdapter делает это очень просто.Недостатком является то, что Gson теперь не может обрабатывать это автоматически.

Учитывая тип Point, определенный как:

public final class Point {
    public final int x;
    public final int y;
    public Point(final int x, final int y) {
        this.x = x;
        this.y = y;
    }
}

Немного скорректированный вариант примера из документации

public class PointAdapter extends TypeAdapter<Point> {
  public Point read(JsonReader reader) throws IOException {
    if (reader.peek() == JsonToken.NULL) {
      reader.nextNull();
      return null;
    }
    String xy = reader.nextString();
    String[] parts = xy.split(",");
    int x = Integer.parseInt(parts[0]);
    int y = Integer.parseInt(parts[1]);
    return new Point(x, y);
  }
  public void write(JsonWriter writer, Point point) throws IOException {
    if (point == null) {
      writer.nullValue();
      return;
    }
    String xy = point.x + "," + point.y;
    writer.value(xy);
  }
}

дает желаемый результат.

1 голос
/ 17 октября 2014

Я попытался поэкспериментировать с последними полями - сначала установив их с инициализаторами, например:

class Test {
    final int x = 1;
    private Test() {}
}

GSON не будет десериализовать это должным образом.

, а затем в конструкторе ...

class Test {
    final int x;
    private Test() {
    x = 1;
    }
}

Это сработало.Возможно, это оптимизация компилятора Java, когда конечные переменные примитива или переменные типа String с инициализаторами обрабатываются как константы времени созрева, без создания реальной переменной, тогда как, если инициализация выполняется в конструкторе, создается переменная?

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...