Gson сериализует нулевые значения только тогда, когда поле находится во вложенном объекте - PullRequest
2 голосов
/ 23 марта 2020

Проблема, с которой я сталкиваюсь, заключается в том, что я хочу обслужить / обнулить нулевые значения, когда касается только атрибутов не верхнего уровня , и я не знаю, как этого добиться. Допустим, у меня есть класс User:

Class User {
   String name;
   int id;
   Address address;
}

и класс Address:

Class Address{
   String street;
   String city;
   String country;
}

Прямо сейчас я могу использовать ниже экземпляр Gson для ser / des нулевых значений:

Gson gson = new GsonBuilder().serializeNulls().create();
Address address = new Address(null, "New York", "US");
User user = new User("Adam", 123, address);  
String userJson = gson.toJson(user); 

Вывод:

{
  "name": "Adam",
  "id": 123,
  "address": {
      "street": null,
      "city": "New York",
      "country": "US"
  }
}

Однако я НЕ хочу обслужить / обнулить нули, когда дело доходит до атрибутов верхнего уровня пользователя. Например, для пользователя ниже:

User user = new User("Adam", 123, null);

Я хочу получить вывод, как показано ниже, и без поля address :

{
  "name": "Adam",
  "id": 123
}

Я сейчас пытаюсь использовать настроенный сериализатор для жесткого кодирования всех атрибутов верхнего уровня и удаления их, если они равны NULL:

public class SerializerForUser implements JsonSerializer<ConfigSnapshot> {

    @Override
    public JsonElement serialize(User user, Type type, JsonSerializationContext jsc) {
        Gson gson = new GsonBuilder().serializeNulls().create();
        JsonObject jsonObject = gson.toJsonTree(user).getAsJsonObject();
        if (user.getAddress() == null) {
            jsonObject.remove("address");
        }
        // if... (same with other top-level attributes)
        return jsonObject;
    }
}

Gson gson = new GsonBuilder().serializeNulls().registerTypeAdapter(User.class, new SerializerForUser()).create();

Но почему-то это не работает, я все равно получу вывод ниже, когда, например, адрес равен нулю:

{
  "name": "Adam",
  "id": 123,
  "address: null
}

Может кто-нибудь дать мне несколько советов о том, что я здесь не так? Или было бы замечательно, если бы кто-нибудь сказал мне, есть ли более прямой / общий способ достижения этого (поскольку я также хочу использовать тот же экземпляр gson для обслуживания / удаления других объектов)? Любые комментарии приветствуются.

1 Ответ

0 голосов
/ 23 марта 2020

Поскольку вы используете

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

, который показывает нулевое значение.

Чтобы пропустить показ нуля, давайте попробуем

Gson gson = new Gson();

Вы можете проверить здесь

public static void main(String[] args) {
    Gson yourGson = new GsonBuilder().serializeNulls().create(); // this is how you create your Gson object, which shows null value
    Address address = new Address(null, "New York", "US");
    User user = new User("Adam", 123, address);
    String userJson = yourGson.toJson(user);
    System.out.println(userJson);

    Gson newGson = new Gson(); // with this one, it doesn't show value
    System.out.println(newGson.toJson(user));
}

Обновление

Я несколько раз пытался переопределить метод serialize, и он не удался, пока я не попытался # 5

public class UserCustomSerializer implements JsonSerializer<User> {

    @Override
    public JsonElement serialize(User src, Type typeOfSrc, JsonSerializationContext context) {
        JsonObject obj = new JsonObject();

        if (src.name != null) {
            obj.addProperty("name", src.name);
        }
        obj.addProperty("id", src.id);

        if (src.address != null) {
            // try #1
            //JsonObject addressJsonObj = new JsonObject();
            //addressJsonObj.addProperty("street", src.address.street != null ? src.address.street : null);
            //addressJsonObj.addProperty("city", src.address.city != null ? src.address.city : null);
            //addressJsonObj.addProperty("country", src.address.country != null ? src.address.country : null);
            //obj.add("address", addressJsonObj);

            // try #2
            //Gson gson = new GsonBuilder().serializeNulls().create();
            //JsonElement jsonElement = gson.toJsonTree(src.address);
            //obj.add("address", jsonElement);

            // try #3
            //Gson gson2 = new GsonBuilder().serializeNulls().create();
            //obj.addProperty("address", gson2.toJson(src.address));

            // try #4
            //Gson gson = new GsonBuilder().serializeNulls().create();
            //JsonObject jsonObject = gson.toJsonTree(src.address).getAsJsonObject();
            //obj.add("address", jsonObject);

            // try #5
            JsonObject addressJsonObj = new JsonObject();
            addressJsonObj.addProperty("street", src.address.street != null ? src.address.street : "null");
            addressJsonObj.addProperty("city", src.address.city != null ? src.address.city : "null");
            addressJsonObj.addProperty("country", src.address.country != null ? src.address.country : "null");
            obj.add("address", addressJsonObj);
        }
        return obj;
    }
}

Для попытки № 3 я построил неправильную строку.

Для попытки № 1, № 2 и № 4 у меня проблема с нулевым значением. Я искал и нашел причину, а также предложение здесь

В JSON «объекте» (он же словарь) есть два способа представления отсутствующих значений: нет пары ключ / значение вообще, или у вас есть ключ с нулевым значением JSON.

Таким образом, вы либо используете .add с правильным значением, которое будет преобразовано в нулевое значение при построении JSON, или у вас нет вызова .add.

И мой подход № 5 заключается в проверке, является ли дочерний узел нулевым, я просто добавляю строку «null» буквально, а затем заменяю ее, когда Я строю json строку

private String parseToGson(User user){
    Gson gson = new GsonBuilder().registerTypeAdapter(User.class, new UserCustomSerializer()).create();
    return gson.toJson(user).replace("\"null\"", "null");
}

Вот некоторые тестовые случаи, которые я определил

@Test
public void children_attribute_is_null() throws Exception {
    String expected = "{\"name\":\"Adam\"," 
                     + "\"id\":123," 
                     + "\"address\":{" 
                                    + "\""+ "street\":null," 
                                    + "\"city\":\"New York\"," 
                                    + "\"country\":\"US" 
                                + "\"}" 
                     + "}";
    Address address = new Address(null, "New York", "US");
    User user = new User("Adam", 123, address);
    assertEquals(expected, parseToGson(user));

    Gson g = new Gson(); 
    User usr = g.fromJson( parseToGson(user), User.class);
    assertEquals("Adam", usr.name);
    assertEquals(123, usr.id);
    assertEquals(null, usr.address.street);
    assertEquals("New York", usr.address.city);
    assertEquals("US", usr.address.country);
}

@Test
public void parent_attribute_is_null() throws Exception {
    String expected = "{\"name\":\"Adam\"," 
                     + "\"id\":123" + "}";
    User user = new User("Adam", 123, null);
    assertEquals(expected, parseToGson(user));

    Gson g = new Gson(); 
    User usr = g.fromJson( parseToGson(user), User.class);
    assertEquals("Adam", usr.name);
    assertEquals(123, usr.id);
    assertEquals(null, usr.address);
}

@Test
public void parent_attribute_and_children_attribute_are_null() throws Exception {
    String expected = "{\"id\":123," 
                     + "\"address\":{" 
                                + "\"street\":null," 
                                + "\"city\":\"New York\","
                                + "\"country\":\"US" 
                                + "\"}" 
                     + "}";
    Address address = new Address(null, "New York", "US");
    User user = new User(null, 123, address);
    assertEquals(expected, parseToGson(user));

    Gson g = new Gson(); 
    User usr = g.fromJson( parseToGson(user), User.class);
    assertEquals(null, usr.name);
    assertEquals(123, usr.id);
    assertEquals(null, usr.address.street);
    assertEquals("New York", usr.address.city);
    assertEquals("US", usr.address.country);
}

Обновление # 2

Поскольку предыдущая версия не универсальная c, я хотел бы обновить ответ.

Для универсальных c я создал MyCustomSerializer следующим образом

public class MyCustomSerializer<T> implements JsonSerializer<T> {

    private final Class<T> type;

    public MyCustomSerializer(Class<T> type) {
        this.type = type;
    }

    public Class<T> getMyType() {
        return this.type;
    }

    @Override
    public JsonElement serialize(T src, Type typeOfSrc, JsonSerializationContext context) {
        JsonObject obj = new JsonObject();
        try {
            Field[] declaredFields = this.type.getDeclaredFields();
            for (Field field : declaredFields) {
                Object object = field.get(src);
                if (object != null) {
                    // Here, we check for 4 types of JsonObject.addProperty
                    if (object instanceof String) {
                        obj.addProperty(field.getName(), (String) object);
                        continue;
                    }
                    if (object instanceof Number) {
                        obj.addProperty(field.getName(), (Number) object);
                        continue;
                    }
                    if (object instanceof Boolean) {
                        obj.addProperty(field.getName(), (Boolean) object);
                        continue;
                    }
                    if (object instanceof Character) {
                        obj.addProperty(field.getName(), (Character) object);
                        continue;
                    }
                    // This is where we check for other types
                    // The idea is if it is an object, we need to care its child object as well, so parse it into json string and replace the null value.
                    Gson gson = new GsonBuilder().serializeNulls().create();
                    String json = gson.toJson(object);
                    json = json.replace("null", "\"null\""); // We have to build the string first, then replace it with our special keys. In this case, I use the string "null"
                    JsonObject convertedObject = new Gson().fromJson(json, JsonObject.class); // Then convert it back to json object
                    obj.add(field.getName(), convertedObject);
                }
            }
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        return obj;
    }
}

Основная идея заключается в все так же, как и в предыдущей версии, но я сделал это в общем c.

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

{
   "id":123,
   "address":{
      "street":null,
      "city":"New York",
      "country":"US",
      "info":{
         "zipcode":null,
         "address2":"stackoverflow",
         "workPlaceAddress":{
            "street":null,
            "companyName":"google"
         }
      }
   }
}

Чтобы вызвать это, нам нужно сделать

private String parseToGson(User user) {
    Gson gson = new GsonBuilder().registerTypeAdapter(User.class, new MyCustomSerializer<>(User.class)).create();
    return gson.toJson(user).replace("\"null\"", "null"); 
}

Update # 3

Поскольку вы по-прежнему беспокоитесь о своем решении, я также попытался адаптировать его

public class YourSerializer <T> implements JsonSerializer<T>{

    private final Class<T> type;

    public YourSerializer(Class<T> type) {
        this.type = type;
    }

    public Class<T> getMyType() {
        return this.type;
    }

    @Override
    public JsonElement serialize(T src, Type typeOfSrc, JsonSerializationContext context) {
           Gson gson = new GsonBuilder().serializeNulls().create();
            JsonObject jsonObject = gson.toJsonTree(src).getAsJsonObject();

            Field[] declaredFields = this.type.getDeclaredFields();
            for (Field field : declaredFields) {
                try {
                    if(field.get(src) == null) {
                        jsonObject.remove(field.getName());
                    }
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
            return jsonObject;
    }
}

. Причина в том, что вы использовали serializeNulls() неправильно, что делает ваш вывод неверным. Чтобы исправить это, вы должны registerTypeAdapter сначала создать свой пользовательский json, а затем позвонить serializeNulls

private String parseToGson(User user) {
    Gson gson = new GsonBuilder().registerTypeAdapter(User.class, new YourSerializer<>(User.class)).serializeNulls().create();  
    return gson.toJson(user);
}

Я проверил и получил тот же результат с обновлением # 2

{
   "id":123,
   "address":{
      "street":null,
      "city":"New York",
      "country":"US",
      "info":{
         "zipcode":null,
         "address2":"aaa",
         "workPlaceAddress":{
            "street":null,
            "companyName":"google"
         }
      }
   }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...