首页 > 解决方案 > Moshi 工厂在从 json 字符串反序列化时忽略空值并使用 kotlin 默认值

问题描述

基于 Moshi https://github.com/square/moshi/issues/843中的这个问题,关于 Moshi 为何以及如何以这种方式处理空值进行了一些很好的讨论。虽然这对我来说很有意义,但不幸的是,我正在使用一个间歇性返回 null 的 api,而我得到的最好的反馈是我们应该忽略它们。在上面链接的问题中,图书馆贡献者之一提到了这个可以提供帮助的工厂。

@Retention(RUNTIME)
@Target(CLASS)
annotation class DefaultIfNull

class DefaultIfNullFactory : JsonAdapter.Factory {
  override fun create(type: Type, annotations: MutableSet<out Annotation>,
      moshi: Moshi): JsonAdapter<*>? {
    if (!Types.getRawType(type).isAnnotationPresent(
            DefaultIfNull::class.java)) {
      return null
    }

    val delegate = moshi.nextAdapter<Any>(this, type, annotations)

    return object : JsonAdapter<Any>() {
      override fun fromJson(reader: JsonReader): Any? {
        @Suppress("UNCHECKED_CAST")
        val blob = reader.readJsonValue() as Map<String, Any?>
        val noNulls = blob.filterValues { it != null }
        return delegate.fromJsonValue(noNulls)
      }

      override fun toJson(writer: JsonWriter, value: Any?) {
        return delegate.toJson(writer, value)
      }
    }
  }
}

class NullSkipperTest {

  @DefaultIfNull
  data class ClassWithDefaults(val foo: String, val bar: String? = "defaultBar")

  @Test
  fun skipNulls() {
    //language=JSON
    val json = """{"foo": "fooValue", "bar": null}"""

    val adapter = Moshi.Builder()
        .add(DefaultIfNullFactory())
        .add(KotlinJsonAdapterFactory())
        .build()
        .adapter(ClassWithDefaults::class.java)

    val instance = adapter.fromJson(json)!!
    check(instance.bar == "defaultBar")
  }
}

这很好用,但对于我的用例来说并不是“最佳”的,因为有两个单独的问题:1. 它需要注释每个类。我有大约 200 个需要注释的类,所以我正在寻找一个可以做而不是选择加入的工厂。如果有的话,也许我想选择退出,但这并不重要现在 2. 从也发布的测试中,不清楚我的所有值是否都需要声明为可为空的。我还希望不必将每个字段都标记为可为空的类型,但测试似乎通过了val foo: String, val bar: String = "defaultBar"而不是val foo: String, val bar: String? = "defaultBar"

为了让这个为我自己工作,我将片段转换为这个。它似乎也解决了我上面提到的两个“问题”。

class DefaultIfNullFactory : JsonAdapter.Factory {
    override fun create(type: Type, annotations: MutableSet<out Annotation>,
                        moshi: Moshi): JsonAdapter<*>? {
        val delegate = moshi.nextAdapter<Any>(this, type, annotations)
        return object : JsonAdapter<Any>() {
            override fun fromJson(reader: JsonReader): Any? {
                    val blob1 = reader.readJsonValue()
                try {
                    val blob = blob1 as Map<String, Any?>
                    val noNulls = blob.filterValues { it != null }
                    return delegate.fromJsonValue(noNulls)
                } catch (e: Exception) {
                    return delegate.fromJsonValue(blob1)
                }
            }
            override fun toJson(writer: JsonWriter, value: Any?) {
                return delegate.toJson(writer, value)
            }
        }
    }
}

我不知道工厂是它在一个班级的每个领域以及班级中移动,所以当我最初拥有这个时,我认为它会很完美,但它确实在每个领域都被调用在每个类中,强制转换失败,然后只返回值。

我对它感到满意,而且我没有感觉到任何性能影响。有更多moshi经验的人会说这样做非常糟糕,或者这可能是我能得到的最好的限制?

编辑:

虽然我没有关于这是否是一个好方法的答案,但我可以确认这带来了两个隐藏的问题,我建议你走这条路。

  1. 如果您将它们调整为转换为 java String 类型,那么 json 中的 long 数字1583674200000将不起作用。你会认为你会得到“1583674200000”,但实际上你会得到“1.5836742E12”

  2. 当您有一个字符串数字时,它将被转换为双精度数。所以你可能有一个“100”,但它会被转换为 100.0。

这似乎是因为 moshi 中的数字是双倍的,根本原因是 readJsonValue() as Map,因为它首先必须将所有值映射到 moshi 可以理解的默认值,然后才能过滤掉空值。这是我的问题的原因。

TLDR 不要使用这个工厂。

编辑 2: 我被告知我的编辑在这里是错误的。我不确定是不是,但我们在这里讨论它https://github.com/square/moshi/issues/843

标签: kotlinmoshi

解决方案


推荐阅读