如何避免 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)
我有以下 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)