【问题标题】:Gson - deserialize or defaultGson - 反序列化或默认
【发布时间】:2020-10-15 10:33:18
【问题描述】:

我有一堂课:

data class Stam(@SerializedName("blabla") val blabla: String = "")

我想做gson.fromJson("{\"blabla\":null}", Stam::class.java)

但是,它会失败,因为 blabla 不可为空。

我想这样做,所以如果 gson 无法反序列化某个变量,它将采用我给它的默认值。

如何实现?

【问题讨论】:

  • 你试过这个解决方案吗? stackoverflow.com/questions/30216317/…
  • @nt95 我不认为那里提出的解决方案在 kotlin 中是可以接受的,您需要将 val 声明为可空 String?。我会尝试做的(不确定它是否会起作用)是将blabla 设置为String,但创建一个可为空的设置器,并将默认设置保留在那里,getter(不需要生成它)和 val本身仍然是非空的。至于在纯 Gson 中实现,不知道,我为 json 对象使用了另一个库。
  • 你能做同样的事情并取消引号吗?

标签: kotlin gson


【解决方案1】:

我认为 GSON 不可能,这是创建 kotlinx.serialization 库的原因之一。有了这个库,这很容易:

@Serializable
data class Stam(@SerialName("blabla") val blabla: String = "") //actually, @SerialName may be omitted if it is equal to field name

Json { coerceInputValues = true }.decodeFromString<Stam>("{\"blabla\":null}")

【讨论】:

  • 因为proguard,序列名不能省略
【解决方案2】:

我不会说这在 Gson 中是不可能的,但 Gson 绝对不是最好的选择:

  • Gson 没有提及 Kotlin 及其运行时和细节,因此最好使用更方便且支持 Kotlin 的工具。这里的典型问题是:如何检测数据类(如果真的很重要,可以在 Kotlin 中轻松完成)、如何在运行时检测非空参数和字段等。
  • Kotlin 中的数据类似乎提供了一个可由 Gson 解析的默认构造函数,因此 Gson 可以调用它(尽管它可以使用不安全的机制在没有构造函数的情况下实例化类实例)委托给具有默认参数的“全功能”构造函数。这里的技巧是从输入 JSON 中删除空值属性,因此 Gson 将保持“默认参数”字段不受影响。

我使用 Java,但我相信以下代码可以轻松转换(如果您认为 Gson 仍然是正确的选择):

final class StripNullTypeAdapterFactory
        implements TypeAdapterFactory {

    // The rule to check whether this type adapter should be applied.
    // Externalizing the rule makes it much more flexible.
    private final Predicate<? super TypeToken<?>> isClassSupported;

    private StripNullTypeAdapterFactory(final Predicate<? super TypeToken<?>> isClassSupported) {
        this.isClassSupported = isClassSupported;
    }

    static TypeAdapterFactory create(final Predicate<? super TypeToken<?>> isClassSupported) {
        return new StripNullTypeAdapterFactory(isClassSupported);
    }

    @Override
    @Nullable
    public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> typeToken) {
        if ( !isClassSupported.test(typeToken) ) {
            return null;
        }
        // If the type is supported by the rule, get the type "real" delegate
        final TypeAdapter<T> delegate = gson.getDelegateAdapter(this, typeToken);
        return new StripNullTypeAdapter<>(delegate);
    }

    private static final class StripNullTypeAdapter<T>
            extends TypeAdapter<T> {

        private final TypeAdapter<T> delegate;

        private StripNullTypeAdapter(final TypeAdapter<T> delegate) {
            this.delegate = delegate;
        }

        @Override
        public void write(final JsonWriter out, final T value)
                throws IOException {
            delegate.write(out, value);
        }

        @Override
        public T read(final JsonReader in) {
            // Another disadvantage in using Gson:
            // the null-stripped object must be buffered into memory regardless how big it is.
            // So it may generate really big memory footprints.
            final JsonObject buffer = JsonParser.parseReader(in).getAsJsonObject();
            // Strip null properties from the object
            for ( final Iterator<Map.Entry<String, JsonElement>> i = buffer.entrySet().iterator(); i.hasNext(); ) {
                final Map.Entry<String, JsonElement> property = i.next();
                if ( property.getValue().isJsonNull() ) {
                    i.remove();
                }
            }
            // Now there is no null values so Gson would only use properties appearing in the buffer
            return delegate.fromJsonTree(buffer);
        }

    }

}

测试:

public final class StripNullTypeAdapterFactoryTest {

    private static final Collection<Class<?>> supportedClasses = ImmutableSet.of(Stam.class);

    private static final Gson gson = new GsonBuilder()
            .disableHtmlEscaping()
            // I don't know how easy detecting data classes and non-null parameters is
            // but since the rule is externalized, let's just lookup it
            // in the "known classes" registry
            .registerTypeAdapterFactory(StripNullTypeAdapterFactory.create(typeToken -> supportedClasses.contains(typeToken.getRawType())))
            .create();

    @Test
    public void test() {
        final Stam stam = gson.fromJson("{\"blabla\":null}", Stam.class);
        // The test is "green" since 
        Assertions.assertEquals("", stam.getBlabla());
    }

}

我仍然认为 Gson 不是这里的最佳选择。

【讨论】:

  • 感谢您的详尽回答,但它仍然无法满足我的需求。如果反序列化失败,我需要它采用默认值,而不是 null
  • 意思是当属性的结果内容为空,但属性本身不可为空时
  • @LenaBru 您的整个问题主要是围绕不允许使用可能的空值的想法构建的(这在 Kotlin 中很常见),当您引用“反序列化失败”的另一个状态时-- 除了空值问题,你的实际意思是什么?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-10-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-11-24
  • 1970-01-01
相关资源
最近更新 更多