Gson: Як виключити конкретні поля з серіалізації без анотацій


413

Я намагаюся навчитися Гсону і борюся з виключенням з поля. Ось мої заняття

public class Student {    
  private Long                id;
  private String              firstName        = "Philip";
  private String              middleName       = "J.";
  private String              initials         = "P.F";
  private String              lastName         = "Fry";
  private Country             country;
  private Country             countryOfBirth;
}

public class Country {    
  private Long                id;
  private String              name;
  private Object              other;
}

Я можу використовувати GsonBuilder і додати ExcludStrategy для імені поля на кшталт firstNameабо, countryале я, здається, не в змозі виключити властивості певних полів, таких як country.name.

Використовуючи метод public boolean shouldSkipField(FieldAttributes fa), FieldAttributes не містить достатньо інформації для відповідності поля з таким фільтром country.name.

PS: Я хочу уникати анотацій, оскільки хочу покращити це питання і використовувати RegEx для фільтрації полів.

Редагувати : Я намагаюся зрозуміти, чи можна наслідувати поведінку плагіна Struts2 JSON

за допомогою Gson

<interceptor-ref name="json">
  <param name="enableSMD">true</param>
  <param name="excludeProperties">
    login.password,
    studentList.*\.sin
  </param>
</interceptor-ref>

Редагувати: Я знову відкрив питання із наступним доповненням:

Для подальшого з'ясування цієї проблеми я додав друге поле з тим же типом. В основному я хочу виключити, country.nameале ні countrOfBirth.name. Я також не хочу виключати країну як тип. Тож типи однакові, це фактичне місце в графіку об'єкта, яке я хочу точно визначити та виключити.


1
Досі з версії 2.2 я все ще не можу вказати шлях до поля, який потрібно виключити. flexjson.sourceforge.net вважає гарною альтернативою.
Liviu T.

Погляньте на мою відповідь на досить схоже запитання. Він заснований на створенні спеціального JsonSerializerтипу для певного типу - Countryу вашому випадку - для якого потім застосовується анкета, ExclusionStrategyяка визначає, які поля для серіалізації.
pirho

Відповіді:


625

Будь-які поля, які ви не хочете серіалізувати взагалі, вам слід скористатися модифікатором "перехідних", це також стосується серіалізаторів json (принаймні, це стосується кількох, які я використовував, включаючи gson).

Якщо ви не хочете, щоб ім’я відображалося в серіалізованому json, введіть йому перехідне ключове слово, наприклад:

private transient String name;

Детальніше в документації Gson


6
це майже те саме, що і примітка про виключення, як це стосується всіх примірників цього класу. Мені хотілося динамічного виключення часу виконання. У деяких випадках я хочу, щоб деякі поля були виключені, щоб надати більш легку / обмежену відповідь, а в інших я хочу, щоб повний об'єкт був серіалізований
Liviu T.

34
Варто зазначити, що тимчасові наслідки як серіалізації, так і десеріалізації. Він також буде випромінювати значення з серіалізованого в об’єкт, навіть якщо воно присутнє в JSON.
Конг

3
Проблема з використанням transientзамість того @Expose, що вам все одно доведеться знущатися над POJO на своєму клієнті з усіма полями, які, можливо, можуть увійти. У випадку бек-інтерфейсу API, який може бути спільним для проектів, це може бути проблематично у випадку додаткові поля додаються. По суті, це білий список та чорний список полів.
theblang

11
Цей підхід не працював для мене, оскільки він не лише виключав поле з серіалізації gson, але й виключав поле з внутрішньої серіалізації додатків (використовуючи інтерфейс Serializable).
pkk

8
тимчасовий перешкоджає СЕРІАЛІЗАЦІЇ та ДЕЗЕРІАЛІЗАЦІЇ поля. Це не відповідає моїм потребам.
Loenix

318

Nishant забезпечив хороше рішення, але є більш простий шлях. Просто позначте потрібні поля за допомогою анотації @Expose, наприклад:

@Expose private Long id;

Залиште поля, які ви не хочете серіалізувати. Тоді просто створіть свій об’єкт Gson таким чином:

Gson gson = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create();

95
Чи можливо мати щось на кшталт "notExpose" і ігнорувати лише ті випадки, коли всі, крім одного поля, повинні бути серіалізовані та додавання анотацій до всіх зайвих?
Даніїл Шевельов

2
@DaSh Нещодавно у мене був такий сценарій. Написати користувальницьку програму ExclusionStrategy було дуже просто, яка саме так і зробила. Дивіться відповідь Нішанта. Єдина проблема полягала в тому, щоб включити купу класів з контейнерами та скрипками з skipclass vs skipfield (поля можуть бути класами ...)
keyser

1
@DaSh Моя відповідь нижче робить саме це.
Дерек Шокі

Яке чудове рішення. Я зіткнувся зі сценарієм, коли я хочу поле, серіалізоване на диск, але його ігнорувати при відправці його на сервер через gson. Ідеально, дякую!
Слинк

1
@Danlil Ви повинні мати можливість використовувати @Expose (serialize = false, deserialize = false)
Hrk

237

Отже, ви хочете виключити firstName і country.name. Ось як ExclusionStrategyслід виглядати

    public class TestExclStrat implements ExclusionStrategy {

        public boolean shouldSkipClass(Class<?> arg0) {
            return false;
        }

        public boolean shouldSkipField(FieldAttributes f) {

            return (f.getDeclaringClass() == Student.class && f.getName().equals("firstName"))||
            (f.getDeclaringClass() == Country.class && f.getName().equals("name"));
        }

    }

Якщо ви бачите уважно, він повертається trueза Student.firstNameі Country.name, що ви хочете виключити.

Вам потрібно застосувати це ExclusionStrategyтак,

    Gson gson = new GsonBuilder()
        .setExclusionStrategies(new TestExclStrat())
        //.serializeNulls() <-- uncomment to serialize NULL fields as well
        .create();
    Student src = new Student();
    String json = gson.toJson(src);
    System.out.println(json);

Це повертає:

{ "middleName": "J.", "initials": "P.F", "lastName": "Fry", "country": { "id": 91}}

Я припускаю, що об'єкт країни ініціалізований id = 91Lу класі для студентів.


Ви можете пофантазувати. Наприклад, ви не хочете серіалізувати жодне поле, яке містить рядок "ім'я" у своєму імені. Зробити це:

public boolean shouldSkipField(FieldAttributes f) {
    return f.getName().toLowerCase().contains("name"); 
}

Це поверне:

{ "initials": "P.F", "country": { "id": 91 }}

EDIT: Додано більше інформації за запитом.

Це ExclusionStrategyзробить річ, але вам потрібно пройти "Повністю кваліфіковане ім'я поля". Дивись нижче:

    public class TestExclStrat implements ExclusionStrategy {

        private Class<?> c;
        private String fieldName;
        public TestExclStrat(String fqfn) throws SecurityException, NoSuchFieldException, ClassNotFoundException
        {
            this.c = Class.forName(fqfn.substring(0, fqfn.lastIndexOf(".")));
            this.fieldName = fqfn.substring(fqfn.lastIndexOf(".")+1);
        }
        public boolean shouldSkipClass(Class<?> arg0) {
            return false;
        }

        public boolean shouldSkipField(FieldAttributes f) {

            return (f.getDeclaringClass() == c && f.getName().equals(fieldName));
        }

    }

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

    Gson gson = new GsonBuilder()
        .setExclusionStrategies(new TestExclStrat("in.naishe.test.Country.name"))
        //.serializeNulls()
        .create();
    Student src = new Student();
    String json = gson.toJson(src);
    System.out.println(json); 

Він повертає:

{ "firstName": "Philip" , "middleName": "J.", "initials": "P.F", "lastName": "Fry", "country": { "id": 91 }}

Дякую за відповідь. Мені потрібно створити ExcludStrategy, який може приймати рядок типу country.nameі виключити поле лише nameпри серіалізації поля country. Це має бути достатньо загальним, щоб застосувати до кожного класу, який має властивість з назвою країна класу Country. Я не хочу створювати Стратегію виключення для кожного класу
Liviu T.

@Liviu T. Відповідь я оновив. Це вимагає загального підходу. Ви можете стати ще більш креативним, але я залишився елементарним.
Нішант

Введіть для оновлення. Що я намагаюся побачити, чи можна дізнатися, де в об’єктному графіку я знаходжусь, коли метод, який він викликав, я можу виключити деякі поля країни, але не countryOfBirth (наприклад), такий самий клас, але різні властивості. Я відредагував своє запитання, щоб уточнити, чого я намагаюся досягти
Liviu T.

Чи є спосіб виключити поля, які мають порожні значення?
Юсуф К.

12
Цю відповідь слід позначити як кращу відповідь. На відміну від інших відповідей, які наразі мають більше голосів, це рішення не вимагає від вас модифікації класу бобів. Це величезний плюс. Що робити, якщо хтось інший використовував той самий клас квасолі, і ви позначили поле, яке вони хотіли б зберігати як "перехідне"?
user64141

221

Прочитавши всі доступні відповіді, я з’ясував, що найбільш гнучким, в моєму випадку, було використання спеціальних @Excludeанотацій. Отже, я реалізував просту стратегію для цього (я не хотів позначати всі поля, @Exposeа також не хотів використовувати, з transientякими конфліктує при Serializableсеріалізації додатків ):

Анотація:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Exclude {
}

Стратегія:

public class AnnotationExclusionStrategy implements ExclusionStrategy {

    @Override
    public boolean shouldSkipField(FieldAttributes f) {
        return f.getAnnotation(Exclude.class) != null;
    }

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

Використання:

new GsonBuilder().setExclusionStrategies(new AnnotationExclusionStrategy()).create();

16
В якості додаткової примітки, якщо ви хочете використовувати свою стратегію виключення лише для серіалізації або лише десеріалізації, використовуйте: addSerializationExclusionStrategyабо addDeserializationExclusionStrategyзамістьsetExclusionStrategies
GLee

9
Ідеально! Тимчасове рішення не працює для мене, тому що я використовую Realm для БД, і я хочу виключити поле лише з Gson, але не з Realm (що робить перехідний)
Marcio Granzotto

3
Це має бути прийнятою відповіддю. Щоб ігнорувати нульові поля, просто змініть f.getAnnotation(Exclude.class) != nullнаf.getAnnotation(Exclude.class) == null
Sharp Edge

3
Відмінно, коли ви не можете користуватися transientчерез потреби інших бібліотек. Дякую!
Мартін Д

1
Також чудово підходить для мене, оскільки Android серіалізує мої заняття між видами діяльності, але я хочу, щоб вони були виключені лише тоді, коли я використовую GSON. Це давайте мій додаток продовжує функціонувати так само, поки він не захоче їх завершити, щоб надсилати іншим.
ThePartyTurtle

81

Я зіткнувся з цим питанням, в якому я мав невелику кількість полів, які хотів виключити лише з серіалізації, тому я розробив досить просте рішення, яке використовує анотацію Гсона @Exposeзі спеціальними стратегіями виключення.

Єдиний вбудований спосіб використання @Expose- це встановлення GsonBuilder.excludeFieldsWithoutExposeAnnotation(), але як вказує назва, поля без явного @Exposeігноруються. Оскільки у мене було лише кілька полів, які я хотів виключити, я виявив перспективу додати примітку до кожного поля дуже громіздкою.

Я фактично хотів зворотного, в який все було включено, якщо явно не використовував його @Exposeдля виключення. Для цього я використовував такі стратегії виключення:

new GsonBuilder()
        .addSerializationExclusionStrategy(new ExclusionStrategy() {
            @Override
            public boolean shouldSkipField(FieldAttributes fieldAttributes) {
                final Expose expose = fieldAttributes.getAnnotation(Expose.class);
                return expose != null && !expose.serialize();
            }

            @Override
            public boolean shouldSkipClass(Class<?> aClass) {
                return false;
            }
        })
        .addDeserializationExclusionStrategy(new ExclusionStrategy() {
            @Override
            public boolean shouldSkipField(FieldAttributes fieldAttributes) {
                final Expose expose = fieldAttributes.getAnnotation(Expose.class);
                return expose != null && !expose.deserialize();
            }

            @Override
            public boolean shouldSkipClass(Class<?> aClass) {
                return false;
            }
        })
        .create();

Тепер я можу легко виключити кілька полів з @Expose(serialize = false)або @Expose(deserialize = false)примітками (зауважте, що значення за замовчуванням для обох @Exposeатрибутів є true). Можна, звичайно, використовувати @Expose(serialize = false, deserialize = false), але це більш стисло виконується, оголосивши поле transientзамість цього (що все ще набуває чинності за допомогою цих спеціальних стратегій виключення).


Для підвищення ефективності я бачу випадок для використання @Expose (serialize = false, deserialize = false), а не тимчасового.
paiego

1
@paiego Чи можете ви розширити це? Мені зараз багато років позбавлено використання Gson, і я не розумію, чому анотація є більш ефективною, ніж маркування її перехідною.
Дерек Шокі

Ах, я помилився, дякую, що це зрозумів. Я неправильно сприйняв мінливість для перехідних. (напр., немає кешування і, отже, немає проблем кеш-когерентності з летючими, але це менш ефективно). У будь-якому випадку, ваш код працював чудово!
paiego

18

Ви можете дослідити дерево json за допомогою gson.

Спробуйте щось подібне:

gson.toJsonTree(student).getAsJsonObject()
.get("country").getAsJsonObject().remove("name");

Ви також можете додати деякі властивості:

gson.toJsonTree(student).getAsJsonObject().addProperty("isGoodStudent", false);

Перевірено з гзоном 2.2.4.


3
Цікаво, чи це занадто сильне враження від продуктивності, якщо ви хочете позбутися складного властивості, яке потрібно розібрати перед видаленням. Думки?
Бен

Однозначно не масштабоване рішення, уявіть собі всю головний біль, який вам доведеться пережити, якщо ви зміните структуру свого об'єкта або додасте / вилучите речі.
codenamezero

16

Я придумав фабрику класів, щоб підтримати цю функціональність. Передайте будь-яку комбінацію полів або класів, які ви хочете виключити.

public class GsonFactory {

    public static Gson build(final List<String> fieldExclusions, final List<Class<?>> classExclusions) {
        GsonBuilder b = new GsonBuilder();
        b.addSerializationExclusionStrategy(new ExclusionStrategy() {
            @Override
            public boolean shouldSkipField(FieldAttributes f) {
                return fieldExclusions == null ? false : fieldExclusions.contains(f.getName());
            }

            @Override
            public boolean shouldSkipClass(Class<?> clazz) {
                return classExclusions == null ? false : classExclusions.contains(clazz);
            }
        });
        return b.create();

    }
}

Для використання створіть два списки (кожен не є обов'язковим) та створіть свій об’єкт GSON:

static {
 List<String> fieldExclusions = new ArrayList<String>();
 fieldExclusions.add("id");
 fieldExclusions.add("provider");
 fieldExclusions.add("products");

 List<Class<?>> classExclusions = new ArrayList<Class<?>>();
 classExclusions.add(Product.class);
 GSON = GsonFactory.build(null, classExclusions);
}

private static final Gson GSON;

public String getSomeJson(){
    List<Provider> list = getEntitiesFromDatabase();
    return GSON.toJson(list);
}

Звичайно, це можна змінити, щоб подивитися на повністю кваліфіковану назву атрибута і виключити це після збігу ...
Доменік Д.

Я роблю нижче приклад. Це не працює. Pls пропонують приватний статичний кінцевий Gson GSON; static {Список <String> fieldExcptions = новий ArrayList <String> (); fieldExcptions.add ("id"); GSON = GsonFactory.build (полеExcluditions, null); } приватний статичний рядок getSomeJson () {String jsonStr = "[{\" id \ ": 111, \" name \ ": \" praveen \ ", \" age \ ": 16}, {\" id \ ": 222, \ "ім'я \": \ "прашант \", \ "вік \": 20}] "; повернути jsonStr; } загальнодоступна статична пустота main (String [] args) {String jsonStr = getSomeJson (); System.out.println (GSON.toJson (jsonStr)); }
Praveen Hiremath

13

Я вирішив цю проблему за допомогою спеціальних анотацій. Це мій клас анотацій "SkipSerialisation":

@Target (ElementType.FIELD)
public @interface SkipSerialisation {

}

і це мій GsonBuilder:

gsonBuilder.addSerializationExclusionStrategy(new ExclusionStrategy() {

  @Override public boolean shouldSkipField (FieldAttributes f) {

    return f.getAnnotation(SkipSerialisation.class) != null;

  }

  @Override public boolean shouldSkipClass (Class<?> clazz) {

    return false;
  }
});

Приклад:

public class User implements Serializable {

  public String firstName;

  public String lastName;

  @SkipSerialisation
  public String email;
}

5
Gson: Як виключити конкретні поля з серіалізації без анотацій
Бен

3
Ви також повинні додати @Retention(RetentionPolicy.RUNTIME)свою примітку.
David Novák

9

Або можете сказати, що не буде відкрито з поля:

Gson gson = gsonBuilder.excludeFieldsWithModifiers(Modifier.TRANSIENT).create();

у вашому класі за атрибутом:

private **transient** boolean nameAttribute;

17
Тимчасові та статичні поля за замовчуванням виключаються; для цього не потрібно закликати excludeFieldsWithModifiers().
Дерек Шокіе

9

Я використовував цю стратегію: я виключив кожне поле, яке не позначене анотацією @SerializedName , тобто:

public class Dummy {

    @SerializedName("VisibleValue")
    final String visibleValue;
    final String hiddenValue;

    public Dummy(String visibleValue, String hiddenValue) {
        this.visibleValue = visibleValue;
        this.hiddenValue = hiddenValue;
    }
}


public class SerializedNameOnlyStrategy implements ExclusionStrategy {

    @Override
    public boolean shouldSkipField(FieldAttributes f) {
        return f.getAnnotation(SerializedName.class) == null;
    }

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


Gson gson = new GsonBuilder()
                .setExclusionStrategies(new SerializedNameOnlyStrategy())
                .create();

Dummy dummy = new Dummy("I will see this","I will not see this");
String json = gson.toJson(dummy);

Це повертається

{"VisibleValue": "Я побачу це"}


6

Інший підхід (особливо корисний, якщо вам потрібно прийняти рішення про виключення поля під час виконання) - це зареєструвати TypeAdapter у вашому екземплярі gson. Приклад нижче:

Gson gson = new GsonBuilder()
.registerTypeAdapter(BloodPressurePost.class, new BloodPressurePostSerializer())

У нижченаведеному випадку сервер очікував би одного з двох значень, але оскільки вони були обома входами, то gson би серіалізував їх обоє. Моєю метою було опустити будь-яке значення, що дорівнює нулю (або менше) з json, розміщеного на сервері.

public class BloodPressurePostSerializer implements JsonSerializer<BloodPressurePost> {

    @Override
    public JsonElement serialize(BloodPressurePost src, Type typeOfSrc, JsonSerializationContext context) {
        final JsonObject jsonObject = new JsonObject();

        if (src.systolic > 0) {
            jsonObject.addProperty("systolic", src.systolic);
        }

        if (src.diastolic > 0) {
            jsonObject.addProperty("diastolic", src.diastolic);
        }

        jsonObject.addProperty("units", src.units);

        return jsonObject;
    }
}

6

Котлін в @Transientанотації також робить трюк , по- видимому.

data class Json(
    @field:SerializedName("serialized_field_1") val field1: String,
    @field:SerializedName("serialized_field_2") val field2: String,
    @Transient val field3: String
)

Вихід:

{"serialized_field_1":"VALUE1","serialized_field_2":"VALUE2"}

1

Я працюю лише над тим, щоб помістити @Exposeанотацію, ось моя версія, яку я використовую

compile 'com.squareup.retrofit2:retrofit:2.0.2'
compile 'com.squareup.retrofit2:converter-gson:2.0.2'

У Modelкласі:

@Expose
int number;

public class AdapterRestApi {

У Adapterкласі:

public EndPointsApi connectRestApi() {
    OkHttpClient client = new OkHttpClient.Builder()
            .connectTimeout(90000, TimeUnit.SECONDS)
            .readTimeout(90000,TimeUnit.SECONDS).build();

    Retrofit retrofit = new Retrofit.Builder()
            .baseUrl(ConstantRestApi.ROOT_URL)
            .addConverterFactory(GsonConverterFactory.create())
            .client(client)
            .build();

    return retrofit.create  (EndPointsApi.class);
}

1

У мене є версія Котліна

@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.FIELD)
internal annotation class JsonSkip

class SkipFieldsStrategy : ExclusionStrategy {

    override fun shouldSkipClass(clazz: Class<*>): Boolean {
        return false
    }

    override fun shouldSkipField(f: FieldAttributes): Boolean {
        return f.getAnnotation(JsonSkip::class.java) != null
    }
}

і як Ви можете додати це до Retrofit GSONConverterFactory:

val gson = GsonBuilder()
                .setExclusionStrategies(SkipFieldsStrategy())
                //.serializeNulls()
                //.setDateFormat(DateFormat.LONG)
                //.setFieldNamingPolicy(FieldNamingPolicy.UPPER_CAMEL_CASE)
                //.setPrettyPrinting()
                //.registerTypeAdapter(Id.class, IdTypeAdapter())
                .create()
        return GsonConverterFactory.create(gson)

0

Це те, що я завжди використовую:

Поведінка за замовчуванням, реалізована в Gson, полягає в тому, що нульові об'єктні поля ігноруються.

Значить об'єкт Gson не серіалізує поля з нульовими значеннями на JSON. Якщо поле в об’єкті Java є нульовим, Gson виключає його.

Ви можете використовувати цю функцію для перетворення якогось об'єкта в нульовий або добре встановлений власним

     /**
   * convert object to json
   */
  public String toJson(Object obj) {
    // Convert emtpy string and objects to null so we don't serialze them
    setEmtpyStringsAndObjectsToNull(obj);
    return gson.toJson(obj);
  }

  /**
   * Sets all empty strings and objects (all fields null) including sets to null.
   *
   * @param obj any object
   */
  public void setEmtpyStringsAndObjectsToNull(Object obj) {
    for (Field field : obj.getClass().getDeclaredFields()) {
      field.setAccessible(true);
      try {
        Object fieldObj = field.get(obj);
        if (fieldObj != null) {
          Class fieldType = field.getType();
          if (fieldType.isAssignableFrom(String.class)) {
            if(fieldObj.equals("")) {
              field.set(obj, null);
            }
          } else if (fieldType.isAssignableFrom(Set.class)) {
            for (Object item : (Set) fieldObj) {
              setEmtpyStringsAndObjectsToNull(item);
            }
            boolean setFielToNull = true;
            for (Object item : (Set) field.get(obj)) {
              if(item != null) {
                setFielToNull = false;
                break;
              }
            }
            if(setFielToNull) {
              setFieldToNull(obj, field);
            }
          } else if (!isPrimitiveOrWrapper(fieldType)) {
            setEmtpyStringsAndObjectsToNull(fieldObj);
            boolean setFielToNull = true;
            for (Field f : fieldObj.getClass().getDeclaredFields()) {
              f.setAccessible(true);
              if(f.get(fieldObj) != null) {
                setFielToNull = false;
                break;
              }
            }
            if(setFielToNull) {
              setFieldToNull(obj, field);
            }
          }
        }
      } catch (IllegalAccessException e) {
        System.err.println("Error while setting empty string or object to null: " + e.getMessage());
      }
    }
  }

  private void setFieldToNull(Object obj, Field field) throws IllegalAccessException {
    if(!Modifier.isFinal(field.getModifiers())) {
      field.set(obj, null);
    }
  }

  private boolean isPrimitiveOrWrapper(Class fieldType)  {
    return fieldType.isPrimitive()
        || fieldType.isAssignableFrom(Integer.class)
        || fieldType.isAssignableFrom(Boolean.class)
        || fieldType.isAssignableFrom(Byte.class)
        || fieldType.isAssignableFrom(Character.class)
        || fieldType.isAssignableFrom(Float.class)
        || fieldType.isAssignableFrom(Long.class)
        || fieldType.isAssignableFrom(Double.class)
        || fieldType.isAssignableFrom(Short.class);
  }
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.