我如何反序列化并非所有键都具有相同子键的 YAML?

How can I deserialize a YAML where not all keys have the same subkeys?

假设这个 YAML:

base:
  foo:
    a: 'a'
    b: 'b'
    c: 'c'
  bar:
    a: 'a'
    b: 'b'
    c: 'c'
  baz:
    a: 'a'
    t: 't'
    z: 'z'

我找到的所有关于反序列化 YAML 的文档都暗示每个键都包含相同的数据。

因此,例如,忽略 baz,我会使用这些数据 类:

data class Base(val base: Data)
data class Data(val foo: Values, val bar: Values)
data class Values(val a: String, val b: String, val c :String)

我的文件实际上有超过 50 个 foo,bar,baz 级别的密钥,其中许多密钥不共享相同的密钥(甚至不是相同的数字),所以我如何定义我的数据 类 能够序列化吗?

我尝试了以下方法:

data class Base(val base: Data)
data class Data(val d: List<Map<String, Values>>)
data class Values(val v: List<Map<String, String>>)

但它会抱怨,因为它正在寻找键的精确匹配。

我假设您使用的是 support for yaml dataformat (com.fasterxml.jackson.dataformat:jackson-dataformat-yaml) 的 Jackson 对象映射器, 和 Kotlin module (com.fasterxml.jackson.module:jackson-module-kotlin).

您示例中的数据 class Values 不匹配所有对象,因为例如它不包含 tz 中包含的 base.baz值。

一种可能性是扩大 Values 的定义以包含可能存在的所有可能值,忽略任何不存在的值:

data class Values(
    val a: String?,
    val b: String?,
    val c: String?,
    val t: String?,
    val z: String?,
)

所有值都可以为空以允许它们不存在。

如果您不需要所有的值,您还可以添加一个 @JsonIgnoreProperties(ignoreUnknown = true) 注释,以告诉 Jackson 如果 yaml 中有未在您的数据中声明的值,这不是问题 class:

@JsonIgnoreProperties(ignoreUnknown = true)
data class Values(...

如果这不够灵活,您可以将所有内容反序列化为映射:

val objectMapper = ObjectMapper(YAMLFactory()).registerKotlinModule()
val content: Map<String, *>  = objectMapper.readValue(theYaml)

请注意,在这种情况下,您没有向 Jackson 提供类型信息,因此您的 yaml 中没有任何 non-trivial 对象(即不是 StringInt、...)被反序列化为地图。此外,您自己没有任何类型信息,您需要检查每个元素是否是预期类型的​​实例,并且必须手动转换它。 例如,要获得元素 base.foo.b 你可以这样写:

val base = content["base"]
val foo = (base as Map<String, *>)["foo"]
val b = (foo as Map<String, *>)["b"]

如果b是non-trivial类型,比方说Something,它可以使用

转换为正确的类型
val bWithType: Something = objectMapper.convertValue(b)

但请注意,这意味着 b 将再次序列化,然后反序列化以获取正确的类型。因此,更好的解决方案是使用上面更灵活的 Values class 的方法。只有在必要时才应使用替代方案。