Десериализация Gson: установить конечное поле

Я десериализую иерархию виджетов с помощью 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, показанный ниже. «имя» на экране установлено правильно, «имя» в 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)


person Fabian Zeindl    schedule 02.12.2011    source источник
comment
почему вы хотите десериализовать в финальное поле? final предназначен для того, чтобы объявить, что это не изменится, так что на самом деле вы, похоже, взламываете свою собственную модель.   -  person aishwarya    schedule 02.12.2011
comment
Мне нужны неизменяемые объекты. Я нигде не создаю эти объекты, кроме парсинга их из gson, и нет необходимости их потом менять. В моем понимании неизменность всегда лучше.   -  person Fabian Zeindl    schedule 02.12.2011
comment
да, но вы могли бы сделать их частными и не предоставлять сеттеры - это послужило бы той же цели?   -  person aishwarya    schedule 03.12.2011
comment
Почти. Есть несколько элементов, которые я устанавливаю внутри, и мне нравится различать их, имея в своих элементах конечный раздел и не конечный раздел. мне просто интересно, почему gson так себя ведет.   -  person Fabian Zeindl    schedule 03.12.2011
comment
@FabianZeindl Немного опоздал на вечеринку, но я согласен с вашим мнением. Меня постоянно раздражает, что Java не предоставляет возможности внутренней сериализации для обработки неизменяемых объектов, когда они так важны для упрощения многопоточной модели.   -  person Jared    schedule 22.10.2014


Ответы (4)


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

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

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

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

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

person Jan Chimiak    schedule 27.06.2012

Немного сложно устранить точную проблему в исходном вопросе, поскольку полный минимальный код и примеры не были предоставлены. Возможно, поможет следующее понимание того, как 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 можно было наблюдать поведение, отличное от описанного выше.

person Programmer Bruce    schedule 05.12.2011
comment
Я узнал больше поведения. Если у меня есть поле, которое является окончательным и ему присвоено значение null (без конструктора), gson заполнит это поле. Если это примитивный тип или он назначен чему-то другому, например строке, gson его не тронет. - person Fabian Zeindl; 06.12.2011
comment
Подтвердить Тогда Гсон еще более непоследователен, чем я предполагал. - person Programmer Bruce; 06.12.2011

Специфика вопроса не совсем соответствует общему названию, но у меня была та же проблема, и этот вопрос является для меня самым популярным результатом 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);
  }
}

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

person mkjeldsen    schedule 22.01.2015

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

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

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

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

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

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

person Phil Doble    schedule 17.10.2014