【问题标题】:Find out if class field annotated by Gson SerializedName actually existes in Json查明由 Gson SerializedName 注解的类字段是否真的存在于 Json 中
【发布时间】:2015-10-23 19:54:46
【问题描述】:

我正在使用 google Gson 来解析 json 并创建一个合适的对象:

public class Settings {
    @SerializedName("version")
    public String version = "1";

    @SerializedName("ad_native_enabled")
    public boolean nativeAdEnabled = false;
}

问题是我需要知道nativeAdEnabled的值是真的从json解析出来的还是我指定的默认值,即ad_native_enabled键是否存在于json中?我尝试使用 Boolean 而不是 boolean 并进行空值检查,但 Gson 反序列化失败。这是我的 json 的 sn-p:

{
    "status": "success",
    "ad_native_enabled": false,
}

免责声明

在我的情况下,手动解析 json 并在该级别进行检测是不相关和优雅的(我想在这种情况下,我必须事先欠我想要的键的存在列表去检查)。在对象级别以某种方式推断所需信息是非常可取的。

我在布尔值的示例中描述了一个问题,但该问题可能是泛化的,并且可能涉及所有原始类型。因此,如果有一个通用的解决方案来解决这个问题,那就太好了。

【问题讨论】:

    标签: java json parsing serialization gson


    【解决方案1】:

    我知道你说过你已经尝试过了,但是使用 Boolean 字段应该可以。我把你的例子缩小了一点,它按预期工作。

    我这样定义Settings 类:

    public static class Settings {
        @SerializedName("ad_native_enabled")
        public Boolean nativeAdEnabled;
    }
    

    如果您随后解析包含该字段的 JSON:

    String json = "{\"ad_native_enabled\": false}";
    Settings settings = gson.fromJson(json, Settings.class);
    System.out.println(settings.nativeAdEnabled);  // prints false
    

    而如果您解析不包含该字段的 JSON:

    String emptyJson = "{}";
    Settings emptySettings = gson.fromJson(emptyJson, Settings.class);
    System.out.println(emptySettings.nativeAdEnabled);  // prints null
    

    您是否将字段的默认值保留为false?如果是这样,第二个示例也将打印false。此外,似乎 GSON 并不特别喜欢 JSON 对象中最后一个属性之后的尾随逗号 - 也许这就是您收到错误的原因?


    看到您的评论后,我想了更多关于是否有可能以某种方式支持默认值,同时仍然能够判断该字段是否存在于 JSON 中。我能想出的最佳解决方案是引入一个新的包装器类型,带有一个自定义的反序列化器。

    我首先定义了这个包装器类型,它只包含字段的实际值,以及该值是否为默认值的指示符:

    public static class ValueWrapper<T> {
        public final T value;
        public final boolean isDefaultValue;
    
        public ValueWrapper(T value, boolean isDefaultValue) {
            this.value = value;
            this.isDefaultValue = isDefaultValue;
        }
    }
    

    Settings 类如下所示:

    public static class Settings {
        @SerializedName("ad_native_enabled")
        public ValueWrapper<Boolean> nativeAdEnabled = new ValueWrapper<>(false, true);
    }
    

    在这里,我将字段定义为默认值为false,这就是为什么isDefaultValue 设置为true

    然后我为这种类型定义了一个自定义反序列化器。基本思路是取你试图反序列化的ValueWrapper的类型,提取其泛型参数,将JSON中的实际值反序列化为泛型参数类型,然后返回一个新的ValueWrapper,其中设置了isDefaultValuefalse。这个反序列化器看起来像这样:

    public static class ValueWrapperDeserializer implements JsonDeserializer<ValueWrapper<?>> {
        @Override
        public ValueWrapper<?> deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
            ParameterizedType parameterizedType = (ParameterizedType) typeOfT;
            Type valueType = parameterizedType.getActualTypeArguments()[0];
            Object value = context.deserialize(json, valueType);
            return new ValueWrapper<>(value, false);
        }
    }
    

    现在我们需要做的就是注册自定义反序列化器:

        Gson gson = new GsonBuilder()
                .registerTypeAdapter(ValueWrapper.class, new ValueWrapperDeserializer())
                .create();
    

    然后我们可以从上面运行我的两个示例:

    String json = "{\"ad_native_enabled\": false}";
    Settings settings = gson.fromJson(json, Settings.class);
    System.out.println(settings.nativeAdEnabled.value);  // prints false
    System.out.println(settings.nativeAdEnabled.isDefaultValue);  // prints false
    
    String emptyJson = "{}";
    Settings emptySettings = gson.fromJson(emptyJson, Settings.class);
    System.out.println(emptySettings.nativeAdEnabled.value);  // prints false
    System.out.println(emptySettings.nativeAdEnabled.isDefaultValue);  //prints true
    

    所以这允许我们使用默认值,但仍然能够使用isDefaultValue 判断该字段是否已设置。这显然对Settings 对象的用户的API 产生了相当大的影响,但也许这比处理空值并将默认值存储在别处更简洁。

    【讨论】:

    • 原来我使用的 Gson lib 已经过时并且是定制的。更新库后,您描述的方法开始工作。这种解决方法的一个缺点是我不能有默认值,或者我必须以某种方式将字段映射保留为可能的默认值,并且在字段为空的情况下从映射中获取值。
    • 我在思考这样的事情:),我什至弄乱了 JsonDeserializer,但由于某些未知原因,我的 Gson registerTypeAdapter 版本失败了。所以在更新 Gson 之后,我更进一步,决定用 TypeAdapter 而不是 JsonDeserializer 做一些魔法,但是过度杀伤的想法并没有让我继续:)无论如何,我接受你的回答!
    • 嗨,我遇到了一个奇怪的行为,如果我的 Settings 类由于某种类型转换问题而反序列化失败( java.lang.Exception: Unable to deserialize class com.socialin.android. apiv3.model.Settings java.lang.IllegalStateException: 需要一个字符串,但在第 1 行第 1659 列是 BEGIN_ARRAY) 我发现我所有的布尔值都设置为 false,但我希望它们在这种情况下为空,你有什么线索吗?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-05-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多