我如何反序列化并非所有键都具有相同子键的 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
不匹配所有对象,因为例如它不包含 t
和 z
中包含的 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 对象(即不是 String
、Int
、...)被反序列化为地图。此外,您自己没有任何类型信息,您需要检查每个元素是否是预期类型的实例,并且必须手动转换它。
例如,要获得元素 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 的方法。只有在必要时才应使用替代方案。
假设这个 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
不匹配所有对象,因为例如它不包含 t
和 z
中包含的 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 对象(即不是 String
、Int
、...)被反序列化为地图。此外,您自己没有任何类型信息,您需要检查每个元素是否是预期类型的实例,并且必须手动转换它。
例如,要获得元素 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 的方法。只有在必要时才应使用替代方案。