【问题标题】:Gson serialize null values only when the field is in a nested object仅当字段位于嵌套对象中时,Gson 才序列化空值
【发布时间】:2020-03-23 02:54:58
【问题描述】:

我面临的问题是,当仅涉及非顶级属性时,我想对空值进行 ser/des,而我不知道如何实现。所以假设我有一个 User 类:

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

还有一个地址类:

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

现在,我可以使用下面的 Gson 实例来 ser/des null 值:

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 的顶级属性时,我确实想要 ser/des nulls。例如以下用户:

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

我想要一个如下的输出并且没有 address 字段:

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

我现在正在尝试使用自定义序列化程序对每个顶级属性进行硬编码,如果它们为空,则将其删除:

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 实例来 ser/des 其他对象)? 任何 cmets 都表示赞赏。

【问题讨论】:

    标签: java gson


    【解决方案1】:

    因为你在使用

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

    显示空值。

    要跳过显示 null,让我们试试

    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,我遇到了 null 值的问题。我搜索并找到了原因以及建议here

    在 JSON“对象”(又名字典)中,有两种方式来表示缺失值:要么根本没有键/值对,要么有一个 JSON 值为 null 的键。

    因此,您要么使用带有适当值的 .add,在构建 JSON 时将其转换为 null,要么您没有 .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

    由于以前的版本不是通用的,我想更新答案。

    对于通用,我创建了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;
        }
    }
    

    主要思想仍然与以前的版本相同,但我把它变成了一个通用的。

    我还添加了一些额外的属性来测试这段代码使用结果构建的字符串

    {
       "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"); 
    }
    

    更新 #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);
    }
    

    我用 update#2 测试并得到了相同的结果

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

    【讨论】:

    • 对不起,我没有把我的问题说清楚。我实际上确实想序列化空值,但仅适用于非顶级属性。话虽这么说:我想在街道为空时序列化地址,但我不想在地址本身为空时序列化地址。
    • 哦,谢谢你说得更清楚。抱歉,我的解决方案无济于事。
    • @foxicoder 我添加了一些调查,希望对您有所帮助。
    • 非常感谢您的调查,您的方法绝对有效。但是,我仍然不知道我在上面做错了什么,而且您的方法似乎不太可扩展(即不是实现此目的的通用方法,如果我们想在其中添加更多字段,我们必须手动检查每个字段,这使得它无法扩展未来)。但非常感谢您的帮助!
    • 我的荣幸。我也可以从中学习:) 上次我认为您需要一种特定的方式,但您是对的,我需要创建一个更通用的解决方案,并且我更新了我的答案。
    猜你喜欢
    • 2013-08-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多