构建自定义 SnakeYAML 构造函数以模块化方式反序列化 yaml 文件
Build custom SnakeYAML Constructor to deserialize yaml file in a modular way
我想使用 SnakeYAML 像下面这样解析 yaml 文件:
config:
someBoolean: true
someString: testing action descriptors
actions:
- print: Hello world
- print: Next action is add
- add:
left: 25
right: 17
- print: done
此文档的目标类型是 DocumentRoot
:
public class DocumentRoot {
public Config config;
public List<Map<String, Object>> actions;
}
public class Config {
public String someString;
public boolean someBoolean;
}
所以大部分文档应该被SnakeYAML直接解析成Java-Objects,比如config
-Attribute。但是 actions
-Attribute 应该以模块化的方式进行解析。考虑以下 ActionDescriptor
s:
public interface ActionDescriptor<T> {
String actionKey();
Class<T> actionValueType();
void runAction(T actionValue);
}
public class AddExpression {
public int left;
public int right;
}
private static List<ActionDescriptor<?>> createDescriptors() {
return List.of(new ActionDescriptor<String>() {
@Override
public String actionKey() {
return "print";
}
@Override
public Class<String> actionValueType() {
return String.class;
}
@Override
public void runAction(String actionValue) {
System.out.println(actionValue);
}
}, new ActionDescriptor<AddExpression>() {
@Override
public String actionKey() {
return "add";
}
@Override
public Class<AddExpression> actionValueType() {
return AddExpression.class;
}
@Override
public void runAction(AddExpression actionValue) {
System.out.println("calculated: " + (actionValue.left + actionValue.right));
}
});
}
我现在想通过以下方式使用这些 ActionDescriptor
来使用 actions
属性:
public static void main(String[] args) throws IOException {
List<ActionDescriptor<?>> descriptors = createDescriptors();
DocumentRoot documentRoot = createYaml(descriptors).loadAs(new FileInputStream("data/input.yaml"),
DocumentRoot.class);
Map<String, ActionDescriptor<?>> descriptorMap = descriptors.stream()
.collect(Collectors.toMap(ActionDescriptor::actionKey, Function.identity()));
if (documentRoot.config.someBoolean) {
System.out.println(documentRoot.config.someString);
for (Map<String, Object> actionMap : documentRoot.actions) {
for (Entry<String, Object> entry : actionMap.entrySet()) {
runAction(entry.getValue(), descriptorMap.get(entry.getKey()));
}
}
}
}
private static <T> void runAction(Object actionValue, ActionDescriptor<T> descriptor) {
Class<T> valueType = descriptor.actionValueType();
if (valueType.isInstance(actionValue)) {
descriptor.runAction(valueType.cast(actionValue));
} else {
System.out.println("expected '" + valueType + "' but got '" + actionValue.getClass() + "'");
}
}
目前我使用以下方法创建 SnakeYAML 的 Yaml
实例:
private static Yaml createYaml(List<ActionDescriptor<?>> descriptors) {
Constructor constructor = new Constructor(DocumentRoot.class);
for (ActionDescriptor<?> descriptor : descriptors) {
// ???
constructor.addTypeDescription(new TypeDescription(descriptor.actionValueType()));
}
Yaml yaml = new Yaml(constructor);
yaml.setBeanAccess(BeanAccess.FIELD);
return yaml;
}
当运行程序我得到以下输出:
testing action descriptors
Hello world
Next action is add
expected 'class animatex.so.AddExpression' but got 'class java.util.LinkedHashMap'
done
但我想要下面的:
testing action descriptors
Hello world
Next action is add
calculated: 42
done
很明显,SnakeYAML 没有使用所需的类型来反序列化操作值。所以我需要以某种方式告诉位于 ???
位置的 SnakeYaml,如果它反序列化映射条目中的值(其映射是属性 actions
列表中的条目),那么它应该使用类型 descriptor.actionValueType()
如果映射条目的相应键是 descriptor.actionKey()
.
我已经使用 TypeDescriptor
s、Constructor
s 和 Construct
s 尝试了一些东西并深入研究了 SnakeYaml 的代码,但我只是不太明白它是如何工作的所以我无法为这个用例构建一个有效的构造函数。
如果有帮助,我还可以扩展 ActionDescriptor
接口以提供 TypeDescriptor
、Constructor
、Construct
...
我真的很想避免在 yaml 文件中添加标签,但如果没有其他解决方案,我可能会咬紧牙关。
我的问题是:如何构建这样一个 Constructor
?期待您的评论和回答:-)
你在这里和 YAML 本身作斗争。 YAML 定义你应该使用 tags 来表示节点的类型,如果你需要明确地这样做的话。它看起来像这样:
config:
someBoolean: true
someString: testing action descriptors
actions:
- !print Hello world
- !print Next action is add
- !add
left: 25
right: 17
- !print done
通过 SnakeYAML 加载这将相当简单,您甚至可以让 DocumentRoot.actions
直接成为 List<ActionDescriptor<?>>
类型。
你的方法试图做“穷人的标签”而不是使用现有的特征。 SnakeYAML 的界面在这种情况下很难使用,因为它希望您使用实际标签来做这样的事情。
您告诉 YAML actions
映射中的值是 Object
。如果这样做,YAML 将构造通用集合类型,例如 LinkedHashMap
因为您没有给它任何更多细节,这就是导致错误的原因。为了克服这个问题,您必须根据 SnakeYAML 生成的一般 LinkedHashMap
值手动构建内部结构。
我强烈建议改用标签。 shows how to define custom tags for classes implementing an abstract interface. You also need to set the actual type of the actions
content as described in the SnakeYAML docs.
第一步是避免嵌套泛型。为此,我们可以调整 class DocumentRoot
如下:
public class DocumentRoot {
public Config config;
public List<ActionMap> actions;
}
public class ActionMap {
private final Map<String, Object> actions;
public ActionMap(Map<String, Object> actions) {
this.actions = actions;
}
}
我们将地图包装到 ActionMap
类型的对象中。现在我们需要告诉 SnakeYAML 如何将 MappingNode
(在 yaml 文件中看起来像地图的任何东西)解析为 ActionMap
类型的对象。我找到了一种扩展 class org.yaml.snakeyaml.constructor.Constructor
的方法,这样很容易实现:
public class MyConstructor extends Constructor {
public MyConstructor(Class<?> rootClass,
Map<Class<?>, BiFunction<Function<Node, Object>, MappingNode, Object>> mappingNodeConstructors,
Map<Class<?>, BiFunction<Function<Node, Object>, SequenceNode, Object>> sequenceNodeConstructors) {
super(rootClass);
this.yamlClassConstructors.put(NodeId.mapping, new ConstructMapping() {
@Override
public Object construct(Node node) {
for (Entry<Class<?>, BiFunction<Function<Node, Object>, MappingNode, Object>> entry : mappingNodeConstructors
.entrySet()) {
if (entry.getKey().isAssignableFrom(node.getType())) {
if (node.isTwoStepsConstruction()) {
throw new YAMLException("Unexpected 2nd step. Node: " + node);
} else {
return entry.getValue().apply(MyConstructor.this::constructObject, (MappingNode) node);
}
}
}
return super.construct(node);
}
@Override
public void construct2ndStep(Node node, Object object) {
throw new YAMLException("Unexpected 2nd step. Node: " + node);
}
});
this.yamlClassConstructors.put(NodeId.sequence, new ConstructSequence() {
@Override
public Object construct(Node node) {
for (Entry<Class<?>, BiFunction<Function<Node, Object>, SequenceNode, Object>> entry : sequenceNodeConstructors
.entrySet()) {
if (entry.getKey().isAssignableFrom(node.getType())) {
if (node.isTwoStepsConstruction()) {
throw new YAMLException("Unexpected 2nd step. Node: " + node);
} else {
return entry.getValue().apply(MyConstructor.this::constructObject, (SequenceNode) node);
}
}
}
return super.construct(node);
}
@Override
public void construct2ndStep(Node node, Object object) {
throw new YAMLException("Unexpected 2nd step. Node: " + node);
}
});
}
}
请注意,我们完全忽略了 SnakeYAML 所谓的第二步,据我所知,它仅用于使用引用的 yaml 文件。因为我不需要这个功能,所以我忽略了它。另请注意,对于此示例,我们不需要处理 SequenceNode
,但对某些人来说它可能仍然有用。
SnakeYAML 的解析工作如下:
- 将文档解析为
Node
-Objects
- 标记
Node
- 具有目标类型的对象
- 将
Node
对象转换为所需的目标类型
对于第三步,SnakeYAML 使用 ConstructMapping
的 construct
方法将 MappingNode
(任何在 yaml 文件中看起来像地图的东西)转换成它的目标类型.类似地,它使用 SequenceMapping
的 construct
方法将 SequenceNode
(任何在 yaml 文件中看起来像列表的东西)转换成它的目标类型。
现在我们可以使用 MyConstructor
的实例来告诉 SnakeYAML 如何将 MappingNode
解析为 ActionMap
:
private static Yaml createYaml(List<ActionDescriptor<?>> descriptors) {
Yaml yaml = new Yaml(createConstructor(descriptors));
yaml.setBeanAccess(BeanAccess.FIELD);
return yaml;
}
private static Constructor createConstructor(List<ActionDescriptor<?>> descriptors) {
Map<String, ActionDescriptor<?>> descriptorMap = descriptors.stream()
.collect(Collectors.toMap(ActionDescriptor::actionKey, Function.identity()));
Constructor result = new MyConstructor(DocumentRoot.class, Map.of(ActionMap.class, (constructor, mnode) -> {
Map<String, Object> actionMap = new LinkedHashMap<>();
for (NodeTuple entry : mnode.getValue()) {
Node actionKeyNode = entry.getKeyNode();
Node actionValueNode = entry.getValueNode();
/* (1) */ String actionKey = (String) constructor.apply(actionKeyNode);
/* (2) */ Class<?> actionValueType = descriptorMap.get(actionKey).actionValueType();
/* (3) */ actionValueNode.setType(actionValueType);
/* (4) */ Object actionValue = constructor.apply(actionValueNode);
/* (5) */ actionMap.put(actionKey, actionValue);
}
return new ActionMap(actionMap);
}), Map.of());
TypeDescription typeDescription = new TypeDescription(DocumentRoot.class);
typeDescription.addPropertyParameters("actions", ActionMap.class);
result.addTypeDescription(typeDescription);
return result;
}
在这里,我们告诉 MyConstructor
它可以使用给定的 lambda 将 MappingNode
转换为 ActionMap
。此 lambda 迭代 MappingNode
的所有条目。对于每个条目,它 (1) 提取 actionKey
,(2) 根据 actionKey
确定 actionValueType
,(3) 将条目的值 Node
标记为actionValueType
,(4) 回调 SnakeYAML 将值 Node
转换为 actionValueType
并且 (5) 在 actionMap
中为确定的 [=37 创建一个新条目=] 和 actionValue
。最后它将 actionMap
包装成 ActionMap
.
最后方法createConstructor
创建了一个TypeDescriptor
来告诉SnakeYAMLclassDocumentRoot
的actions
属性的泛型类型参数是ActionMap
。由于 Java 的类型擦除,这是必需的。
我将代码调整为 运行 如下操作:
public static void main(String[] args) throws IOException {
List<ActionDescriptor<?>> descriptors = createDescriptors();
DocumentRoot documentRoot = createYaml(descriptors).loadAs(new FileInputStream("data/input.yaml"),
DocumentRoot.class);
if (documentRoot.config.someBoolean) {
System.out.println(documentRoot.config.someString);
for (ActionMap actionMap : documentRoot.actions) {
for (ActionDescriptor<?> descriptor : descriptors) {
runAction(actionMap, descriptor);
}
}
}
}
private static <T> void runAction(ActionMap actionMap, ActionDescriptor<T> descriptor) {
actionMap.getActionValue(descriptor).ifPresent(v -> descriptor.runAction(v));
}
其中getActionValue
是class中的一个方法 ActionMap
:
public <T> Optional<T> getActionValue(ActionDescriptor<T> descriptor) {
if (actions.containsKey(descriptor.actionKey())) {
Object actionValue = actions.get(descriptor.actionKey());
Class<T> valueType = descriptor.actionValueType();
if (valueType.isInstance(actionValue)) {
return Optional.of(valueType.cast(actionValue));
} else {
throw new RuntimeException("expected '" + valueType + "' but got '" + actionValue.getClass() + "'");
}
} else {
return Optional.empty();
}
}
正如@flyx 在他们的回答中指出的那样,这种方法实现了“穷人的标签”,而不是使用 yaml 和 SnakeYAML 的现有标签功能。因此,在使用这种方法之前,请考虑使用 yaml 和 SnakeYAML 中现有的标记功能。
然而,这种方法正是我想要的,而且我可能不是唯一的方法,例如 ansible 似乎在其任务列表中使用了类似的 yaml 布局。在我的实际用例中,在单个列表条目中执行多个操作也很有意义,而 yaml 标签无法直接实现。
在现实世界的应用程序中,人们可能想要添加更好的错误处理和一些比类型 BiFunction<Function<Node, Object>, MappingNode, Object>>
更专业的 class。我省略了这些改进,以防止这个答案变得更长。
我想使用 SnakeYAML 像下面这样解析 yaml 文件:
config:
someBoolean: true
someString: testing action descriptors
actions:
- print: Hello world
- print: Next action is add
- add:
left: 25
right: 17
- print: done
此文档的目标类型是 DocumentRoot
:
public class DocumentRoot {
public Config config;
public List<Map<String, Object>> actions;
}
public class Config {
public String someString;
public boolean someBoolean;
}
所以大部分文档应该被SnakeYAML直接解析成Java-Objects,比如config
-Attribute。但是 actions
-Attribute 应该以模块化的方式进行解析。考虑以下 ActionDescriptor
s:
public interface ActionDescriptor<T> {
String actionKey();
Class<T> actionValueType();
void runAction(T actionValue);
}
public class AddExpression {
public int left;
public int right;
}
private static List<ActionDescriptor<?>> createDescriptors() {
return List.of(new ActionDescriptor<String>() {
@Override
public String actionKey() {
return "print";
}
@Override
public Class<String> actionValueType() {
return String.class;
}
@Override
public void runAction(String actionValue) {
System.out.println(actionValue);
}
}, new ActionDescriptor<AddExpression>() {
@Override
public String actionKey() {
return "add";
}
@Override
public Class<AddExpression> actionValueType() {
return AddExpression.class;
}
@Override
public void runAction(AddExpression actionValue) {
System.out.println("calculated: " + (actionValue.left + actionValue.right));
}
});
}
我现在想通过以下方式使用这些 ActionDescriptor
来使用 actions
属性:
public static void main(String[] args) throws IOException {
List<ActionDescriptor<?>> descriptors = createDescriptors();
DocumentRoot documentRoot = createYaml(descriptors).loadAs(new FileInputStream("data/input.yaml"),
DocumentRoot.class);
Map<String, ActionDescriptor<?>> descriptorMap = descriptors.stream()
.collect(Collectors.toMap(ActionDescriptor::actionKey, Function.identity()));
if (documentRoot.config.someBoolean) {
System.out.println(documentRoot.config.someString);
for (Map<String, Object> actionMap : documentRoot.actions) {
for (Entry<String, Object> entry : actionMap.entrySet()) {
runAction(entry.getValue(), descriptorMap.get(entry.getKey()));
}
}
}
}
private static <T> void runAction(Object actionValue, ActionDescriptor<T> descriptor) {
Class<T> valueType = descriptor.actionValueType();
if (valueType.isInstance(actionValue)) {
descriptor.runAction(valueType.cast(actionValue));
} else {
System.out.println("expected '" + valueType + "' but got '" + actionValue.getClass() + "'");
}
}
目前我使用以下方法创建 SnakeYAML 的 Yaml
实例:
private static Yaml createYaml(List<ActionDescriptor<?>> descriptors) {
Constructor constructor = new Constructor(DocumentRoot.class);
for (ActionDescriptor<?> descriptor : descriptors) {
// ???
constructor.addTypeDescription(new TypeDescription(descriptor.actionValueType()));
}
Yaml yaml = new Yaml(constructor);
yaml.setBeanAccess(BeanAccess.FIELD);
return yaml;
}
当运行程序我得到以下输出:
testing action descriptors Hello world Next action is add expected 'class animatex.so.AddExpression' but got 'class java.util.LinkedHashMap' done
但我想要下面的:
testing action descriptors Hello world Next action is add calculated: 42 done
很明显,SnakeYAML 没有使用所需的类型来反序列化操作值。所以我需要以某种方式告诉位于 ???
位置的 SnakeYaml,如果它反序列化映射条目中的值(其映射是属性 actions
列表中的条目),那么它应该使用类型 descriptor.actionValueType()
如果映射条目的相应键是 descriptor.actionKey()
.
我已经使用 TypeDescriptor
s、Constructor
s 和 Construct
s 尝试了一些东西并深入研究了 SnakeYaml 的代码,但我只是不太明白它是如何工作的所以我无法为这个用例构建一个有效的构造函数。
如果有帮助,我还可以扩展 ActionDescriptor
接口以提供 TypeDescriptor
、Constructor
、Construct
...
我真的很想避免在 yaml 文件中添加标签,但如果没有其他解决方案,我可能会咬紧牙关。
我的问题是:如何构建这样一个 Constructor
?期待您的评论和回答:-)
你在这里和 YAML 本身作斗争。 YAML 定义你应该使用 tags 来表示节点的类型,如果你需要明确地这样做的话。它看起来像这样:
config:
someBoolean: true
someString: testing action descriptors
actions:
- !print Hello world
- !print Next action is add
- !add
left: 25
right: 17
- !print done
通过 SnakeYAML 加载这将相当简单,您甚至可以让 DocumentRoot.actions
直接成为 List<ActionDescriptor<?>>
类型。
你的方法试图做“穷人的标签”而不是使用现有的特征。 SnakeYAML 的界面在这种情况下很难使用,因为它希望您使用实际标签来做这样的事情。
您告诉 YAML actions
映射中的值是 Object
。如果这样做,YAML 将构造通用集合类型,例如 LinkedHashMap
因为您没有给它任何更多细节,这就是导致错误的原因。为了克服这个问题,您必须根据 SnakeYAML 生成的一般 LinkedHashMap
值手动构建内部结构。
我强烈建议改用标签。 actions
content as described in the SnakeYAML docs.
第一步是避免嵌套泛型。为此,我们可以调整 class DocumentRoot
如下:
public class DocumentRoot {
public Config config;
public List<ActionMap> actions;
}
public class ActionMap {
private final Map<String, Object> actions;
public ActionMap(Map<String, Object> actions) {
this.actions = actions;
}
}
我们将地图包装到 ActionMap
类型的对象中。现在我们需要告诉 SnakeYAML 如何将 MappingNode
(在 yaml 文件中看起来像地图的任何东西)解析为 ActionMap
类型的对象。我找到了一种扩展 class org.yaml.snakeyaml.constructor.Constructor
的方法,这样很容易实现:
public class MyConstructor extends Constructor {
public MyConstructor(Class<?> rootClass,
Map<Class<?>, BiFunction<Function<Node, Object>, MappingNode, Object>> mappingNodeConstructors,
Map<Class<?>, BiFunction<Function<Node, Object>, SequenceNode, Object>> sequenceNodeConstructors) {
super(rootClass);
this.yamlClassConstructors.put(NodeId.mapping, new ConstructMapping() {
@Override
public Object construct(Node node) {
for (Entry<Class<?>, BiFunction<Function<Node, Object>, MappingNode, Object>> entry : mappingNodeConstructors
.entrySet()) {
if (entry.getKey().isAssignableFrom(node.getType())) {
if (node.isTwoStepsConstruction()) {
throw new YAMLException("Unexpected 2nd step. Node: " + node);
} else {
return entry.getValue().apply(MyConstructor.this::constructObject, (MappingNode) node);
}
}
}
return super.construct(node);
}
@Override
public void construct2ndStep(Node node, Object object) {
throw new YAMLException("Unexpected 2nd step. Node: " + node);
}
});
this.yamlClassConstructors.put(NodeId.sequence, new ConstructSequence() {
@Override
public Object construct(Node node) {
for (Entry<Class<?>, BiFunction<Function<Node, Object>, SequenceNode, Object>> entry : sequenceNodeConstructors
.entrySet()) {
if (entry.getKey().isAssignableFrom(node.getType())) {
if (node.isTwoStepsConstruction()) {
throw new YAMLException("Unexpected 2nd step. Node: " + node);
} else {
return entry.getValue().apply(MyConstructor.this::constructObject, (SequenceNode) node);
}
}
}
return super.construct(node);
}
@Override
public void construct2ndStep(Node node, Object object) {
throw new YAMLException("Unexpected 2nd step. Node: " + node);
}
});
}
}
请注意,我们完全忽略了 SnakeYAML 所谓的第二步,据我所知,它仅用于使用引用的 yaml 文件。因为我不需要这个功能,所以我忽略了它。另请注意,对于此示例,我们不需要处理 SequenceNode
,但对某些人来说它可能仍然有用。
SnakeYAML 的解析工作如下:
- 将文档解析为
Node
-Objects - 标记
Node
- 具有目标类型的对象 - 将
Node
对象转换为所需的目标类型
对于第三步,SnakeYAML 使用 ConstructMapping
的 construct
方法将 MappingNode
(任何在 yaml 文件中看起来像地图的东西)转换成它的目标类型.类似地,它使用 SequenceMapping
的 construct
方法将 SequenceNode
(任何在 yaml 文件中看起来像列表的东西)转换成它的目标类型。
现在我们可以使用 MyConstructor
的实例来告诉 SnakeYAML 如何将 MappingNode
解析为 ActionMap
:
private static Yaml createYaml(List<ActionDescriptor<?>> descriptors) {
Yaml yaml = new Yaml(createConstructor(descriptors));
yaml.setBeanAccess(BeanAccess.FIELD);
return yaml;
}
private static Constructor createConstructor(List<ActionDescriptor<?>> descriptors) {
Map<String, ActionDescriptor<?>> descriptorMap = descriptors.stream()
.collect(Collectors.toMap(ActionDescriptor::actionKey, Function.identity()));
Constructor result = new MyConstructor(DocumentRoot.class, Map.of(ActionMap.class, (constructor, mnode) -> {
Map<String, Object> actionMap = new LinkedHashMap<>();
for (NodeTuple entry : mnode.getValue()) {
Node actionKeyNode = entry.getKeyNode();
Node actionValueNode = entry.getValueNode();
/* (1) */ String actionKey = (String) constructor.apply(actionKeyNode);
/* (2) */ Class<?> actionValueType = descriptorMap.get(actionKey).actionValueType();
/* (3) */ actionValueNode.setType(actionValueType);
/* (4) */ Object actionValue = constructor.apply(actionValueNode);
/* (5) */ actionMap.put(actionKey, actionValue);
}
return new ActionMap(actionMap);
}), Map.of());
TypeDescription typeDescription = new TypeDescription(DocumentRoot.class);
typeDescription.addPropertyParameters("actions", ActionMap.class);
result.addTypeDescription(typeDescription);
return result;
}
在这里,我们告诉 MyConstructor
它可以使用给定的 lambda 将 MappingNode
转换为 ActionMap
。此 lambda 迭代 MappingNode
的所有条目。对于每个条目,它 (1) 提取 actionKey
,(2) 根据 actionKey
确定 actionValueType
,(3) 将条目的值 Node
标记为actionValueType
,(4) 回调 SnakeYAML 将值 Node
转换为 actionValueType
并且 (5) 在 actionMap
中为确定的 [=37 创建一个新条目=] 和 actionValue
。最后它将 actionMap
包装成 ActionMap
.
最后方法createConstructor
创建了一个TypeDescriptor
来告诉SnakeYAMLclassDocumentRoot
的actions
属性的泛型类型参数是ActionMap
。由于 Java 的类型擦除,这是必需的。
我将代码调整为 运行 如下操作:
public static void main(String[] args) throws IOException {
List<ActionDescriptor<?>> descriptors = createDescriptors();
DocumentRoot documentRoot = createYaml(descriptors).loadAs(new FileInputStream("data/input.yaml"),
DocumentRoot.class);
if (documentRoot.config.someBoolean) {
System.out.println(documentRoot.config.someString);
for (ActionMap actionMap : documentRoot.actions) {
for (ActionDescriptor<?> descriptor : descriptors) {
runAction(actionMap, descriptor);
}
}
}
}
private static <T> void runAction(ActionMap actionMap, ActionDescriptor<T> descriptor) {
actionMap.getActionValue(descriptor).ifPresent(v -> descriptor.runAction(v));
}
其中getActionValue
是class中的一个方法 ActionMap
:
public <T> Optional<T> getActionValue(ActionDescriptor<T> descriptor) {
if (actions.containsKey(descriptor.actionKey())) {
Object actionValue = actions.get(descriptor.actionKey());
Class<T> valueType = descriptor.actionValueType();
if (valueType.isInstance(actionValue)) {
return Optional.of(valueType.cast(actionValue));
} else {
throw new RuntimeException("expected '" + valueType + "' but got '" + actionValue.getClass() + "'");
}
} else {
return Optional.empty();
}
}
正如@flyx 在他们的回答中指出的那样,这种方法实现了“穷人的标签”,而不是使用 yaml 和 SnakeYAML 的现有标签功能。因此,在使用这种方法之前,请考虑使用 yaml 和 SnakeYAML 中现有的标记功能。
然而,这种方法正是我想要的,而且我可能不是唯一的方法,例如 ansible 似乎在其任务列表中使用了类似的 yaml 布局。在我的实际用例中,在单个列表条目中执行多个操作也很有意义,而 yaml 标签无法直接实现。
在现实世界的应用程序中,人们可能想要添加更好的错误处理和一些比类型 BiFunction<Function<Node, Object>, MappingNode, Object>>
更专业的 class。我省略了这些改进,以防止这个答案变得更长。