Включить определенное временное поле в сериализацию JSON с помощью GSON

#java #android #json #gson

#java #Android #json #gson

Вопрос:

У меня есть этот класс

 Myclass
{
  transient String field1;
  transient String field2;
  ... // other non transient fields
}
  

Я храню сериализованные объекты, использую их через сеть таким образом (временные поля исключены).

Однако, только для одного конкретного случая, мне нужно включить field2 в сериализацию.

Есть ли способ не исключать определенное временное поле при сериализации с использованием gson?

Ответ №1:

Решение 0:

Используйте пользовательский адаптер типа для класса.

Учитывая

 @JsonAdapter(KA.class)
class K{
    private transient String name;
    private transient String password;
}

class Entity_Adapter extends TypeAdapter<Entity>{
    @Override
    public void write(JsonWriter out, Entity value) throws IOException {
        out.beginObject();
        
        out.name("name");
        out.value(value.getName());
        
        out.name("password");
        out.value(value.getPassword());
        
        out.endObject();
    }

    @Override
    public Entity read(JsonReader in) throws IOException {
        Entity k=new Entity();
        in.beginObject();
        
        in.nextName();
        k.setName(in.nextString());
        
        in.nextName();
        k.setPassword(in.nextString());
        
        in.endObject();
        return k;
    }
}
  

полный пример здесь

Решение 1: (не надежное)

Добавьте другое не transient поле и всегда копируйте любое новое установленное значение field2 для него. например

 transient String field1;
transient String field2;

@SerializedName("field2")
private String field2_non_trans;

public void setField2(String arg_val){
this.field2 = arg_val;
this.field2_non_trans = arg_val;
}

public String getField2(){
  if(field2 == null){
    field2 = field2_non_trans;
  } 
  return field2;
}

  

полный пример здесь

Но вы ДОЛЖНЫ отслеживать каждое изменение в этом field2 , чтобы всегда сохранять копию значения для field2_non_trans обновления, поэтому, если это field2 задано конструктором или вне его setter функции, вы должны быть уверены, что вы установили значение copy для field2_non_trans

То же самое для десериализации, вы должны либо:

  • после завершения десериализации вам необходимо установить десериализованное значение field2_non_trans to field2 с помощью метода.
  • Или просто вернуть field2_non_trans getField2() методом, где field2 null

Решение 2:

Отметьте, что field2 оно не является переходным.

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

1. Решения 1 и 2 создадут поле ‘field2’ для других сериализаций, что в моем случае не подходит. Но адаптер типа, sol. 0, выглядит многообещающе, я рассмотрю его, когда у меня будет такая возможность. Спасибо.

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

Ответ №2:

Вы можете дублировать свое временное поле другим не переходным и записать значение из установщика временных полей. Идея заключается в обновлении поля клонирования при каждом обновлении временного поля.

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

1. Да, но разве это не создаст поле ‘field2’ для других сериализаций? В моем случае это не подходит. Я думаю, мне нужен smt как пользовательский конструктор для этого конкретного случая

Ответ №3:

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

 public final class TransientExclusionStrategy
        implements ExclusionStrategy {

    private static final ExclusionStrategy instance = new TransientExclusionStrategy();

    private TransientExclusionStrategy() {
    }

    public static ExclusionStrategy getInstance() {
        return instance;
    }

    @Override
    public boolean shouldSkipField(final FieldAttributes attributes) {
        @Nullable
        final Expose expose = attributes.getAnnotation(Expose.class);
        if ( expose == null ) {
            return attributes.hasModifier(Modifier.TRANSIENT);
        }
        return !expose.serialize();
    }

    @Override
    public boolean shouldSkipClass(final Class<?> clazz) {
        return false;
    }

}
  

Эта реализация прошла бы следующий модульный тест:

 public final class TransientExclusionStrategyTest {

    private static final String EXPOSED = "EXPOSED";
    private static final String IGNORED = "IGNORED";

    @SuppressWarnings("all")
    private static final class MyClass {

        final String s0 = EXPOSED; // no explicit expose, serialized by default

        @Expose(serialize = false)
        final String s1 = IGNORED; // ignored by annotation

        @Expose(serialize = true)
        final String s2 = EXPOSED; // serialized by annotation

        final transient String ts0 = IGNORED; // no explicit expose, ignored by default

        @Expose(serialize = false)
        final transient String ts1 = IGNORED; // ignored by annotation

        @Expose(serialize = true)
        final transient String ts2 = EXPOSED; // serialized by annotation

    }

    @Test
    public void test() {
        final Gson gson = new GsonBuilder()
                .addSerializationExclusionStrategy(TransientExclusionStrategy.getInstance())
                .create();
        final JsonObject json = (JsonObject) gson.toJsonTree(new MyClass());
        for ( final Map.Entry<String, JsonElement> e : json.entrySet() ) {
            final String stringValue = e.getValue().getAsString();
            Assertions.assertEquals(EXPOSED, stringValue, () -> "Expected "   EXPOSED   " but was "   stringValue   " for "   e.getKey());
        }
    }

}
  

Таким образом, вам не нужно обрабатывать какие-либо специальные адаптеры типов для каждого такого «специального» класса или вводить промежуточные поля (это не обязательно не конфликтует с другими библиотеками и фреймворками, которые вы используете).