【问题标题】:How to avoid an emty object in kotlinx-serialization JSON如何避免 kotlinx 序列化 JSON 中的空对象
【发布时间】:2021-08-13 02:52:42
【问题描述】:

我有以下类(为了清楚起见删除了一些细节),并使用 kotlinx-serialization 1.2.1 和 kotlin 1.5 将它们序列化为 JSON:

@Serializable
data class FieldModifier<T>(
  @Transient private val original: T? = null,
  var set: T? = null,
  var unset: T? = null
)


@Serializable
data class IssueModifier(
  val summary: FieldModifier<String> = FieldModifier(),
  val description: FieldModifier<String> = FieldModifier(),
  val storyPoints: FieldModifier<Double> = FieldModifier(),
  val spentSp: FieldModifier<Double> = FieldModifier(),
  val originalStoryPoints: FieldModifier<Double> = FieldModifier()
) 

在下面的用例中一切正常:

 val format = Json {
    prettyPrint = true
    ignoreUnknownKeys = true
  }

val modifier = IssueModifier()
  modifier.spentSp.set = 2.5
  println(format.encodeToString(modifier))

我得到以下预期的 JSON:

{
    "spentSp": {
        "set": 2.5
    }
}

但是如果我向瞬态“原始”属性添加一些非空值

val modifier = IssueModifier(
    summary = FieldModifier("some summary"),
    description = FieldModifier("some description")
  )
  modifier.spentSp.set = 2.5
  println(format.encodeToString(modifier))

那么 JSON 包含“summary”和“description”属性的意外空对象:

{
    "summary": {
    },
    "description": {
    },
    "spentSp": {
        "set": 2.5
    }
}

这是正确的行为吗?我希望与第一种情况相同的 JSON,因为“原始”属性是瞬态的,因此它不应该影响输出。

我怎样才能避免这种情况?我是否应该覆盖 FieldModifier 类的 hashCode()/equals() 方法以从它们中排除“原始”字段(在这种情况下,框架可能会将FieldModifier("some summary") 视为默认值FieldModifier())?还有其他想法吗?

【问题讨论】:

    标签: json kotlin serialization


    【解决方案1】:

    在第一种情况下,您没有“summary”和“description”属性,因为by default JSON format doesn't encode properties equals to their default value。这也是在第二种情况下你得到空对象的原因。如果您设置encodeDefaults = true,您将在两种情况下获得相同的 JSON:

    {
        "summary": {
            "set": null,
            "unset": null
        },
        "description": {
            "set": null,
            "unset": null
        },
        "storyPoints": {
            "set": null,
            "unset": null
        },
        "spentSp": {
            "set": 2.5,
            "unset": null
        },
        "originalStoryPoints": {
            "set": null,
            "unset": null
        }
    }
    

    但我相信这不是您要寻找的一致性。

    我是否应该重写 FieldModifier 类的 hashCode()/equals() 方法以从它们中排除“原始”字段(在这种情况下,框架可能会将 FieldModifier("some summary") 视为默认值 FieldModifier() )?

    是的,您可以使用覆盖 equals()/hashCode() 方法:

    @Serializable
    data class FieldModifier<T>(
        @Transient private val original: T? = null,
        var set: T? = null,
        var unset: T? = null
    ) {
        override fun equals(other: Any?): Boolean {
            if (this === other) return true
            if (other !is FieldModifier<*>) return false
    
            if (set != other.set) return false
            if (unset != other.unset) return false
    
            return true
        }
    
        override fun hashCode(): Int {
            var result = set?.hashCode() ?: 0
            result = 31 * result + (unset?.hashCode() ?: 0)
            return result
        }
    }
    

    但还有另一种选择 - 即时转换生成的 JSON。

    这是一个辅助序列化程序,您可以使用它从生成的 JSON 中删除所有空对象:

    class RemoveEmptyObjectProperties<T : Any>(serializer: KSerializer<T>) : JsonTransformingSerializer<T>(serializer) {
        override fun transformSerialize(element: JsonElement) = removeEmptyObjectProperties(element)
            ?: element //if root element was empty object itself, just return it, don't return empty string
    
        private fun removeEmptyObjectProperties(element: JsonElement): JsonElement? = when (element) {
            is JsonObject -> {
                val filtered = element.filterValues { removeEmptyObjectProperties(it) != null }
                if (filtered.isEmpty()) null else JsonObject(filtered)
            }
            is JsonArray -> JsonArray(element.mapNotNull { removeEmptyObjectProperties(it) })
            else -> element
        }
    }
    
    object IssueSerializerConcise : KSerializer<IssueModifier> by RemoveEmptyObjectProperties(IssueModifier.serializer())
    

    但是它不能被设置为IssueModifier类的默认序列化器(通过@Serializable(with = IssueSerializerConcise::class)注解),因为它依赖于插件生成的序列化器,如果类的非默认序列化器是不会生成的放。所以你必须手动传递它:

    format.encodeToString(IssueSerializerConcise, modifier)
    

    【讨论】:

    • 完美解决方案,第二个选项正是我想要的!当然,我知道 encodeDefaults 选项,只是想知道为什么没有类似的标准选项来避免空对象和任何此类自定义的需要。
    猜你喜欢
    • 2011-03-09
    • 1970-01-01
    • 1970-01-01
    • 2020-02-02
    • 1970-01-01
    • 2019-11-10
    • 1970-01-01
    • 2020-03-09
    • 1970-01-01
    相关资源
    最近更新 更多