如何避免 kotlinx 序列化中的空对象 JSON

How to avoid an emty object in kotlinx-serialization JSON

我有以下 classes(为清楚起见删除了一些细节)并使用 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
    }
}

但是如果我向瞬态添加一些非空值 'original' 属性

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 与第一种情况相同,因为 'original' 属性 是瞬态的,因此不应影响输出。

我怎样才能避免这种情况?我是否应该覆盖 FieldModifier class 的 hashCode()/equals() 方法以从中排除 'original' 字段(也许在这种情况下框架会将 FieldModifier("some summary") 视为默认值 FieldModifier())?还有其他想法吗?

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

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

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

Should I override hashCode()/equals() methods of the FieldModifier class to exclude the 'original' field from them (maybe in this case the framework would treat FieldModifier("some summary") as the default value 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 class 的默认序列化器(通过 @Serializable(with = IssueSerializerConcise::class) 注释),因为它依赖于插件生成的序列化器并且它不会如果设置了 class 的非默认序列化器,则生成。所以你必须手动传递它:

format.encodeToString(IssueSerializerConcise, modifier)