使用 snakeYaml 在根部解析带有映射的 YAML 文档
Parsing a YAML document with a map at the root using snakeYaml
我想将 YAML 文档读取到自定义对象的地图(而不是 snakeYaml 默认执行的地图)。所以这个:
19:
typeID: 2
limit: 300
20:
typeID: 8
limit: 100
将加载到如下所示的地图:
Map<Integer, Item>
其中项目是:
class Item {
private Integer typeId;
private Integer limit;
}
我找不到使用 snakeYaml 执行此操作的方法,我也找不到更好的库来完成这项任务。
文档只有 maps/collections 嵌套在其他对象中的示例,因此您可以执行以下操作:
TypeDescription typeDescription = new TypeDescription(ClassContainingAMap.class);
typeDescription.putMapPropertyType("propertyNameOfNestedMap", Integer.class, Item.class);
Constructor constructor = new Constructor(typeDescription);
Yaml yaml = new Yaml(constructor);
/* creating an input stream (is) */
ClassContainingAMap obj = (ClassContainingAMap) yaml.load(is);
但是当地图格式位于文档的根目录时,我该如何定义它呢?
您需要添加自定义 Constructor。但是,在您的情况下,您不想注册 "item" 或 "item-list" 标签。
实际上,您想将 Duck Typing 应用到您的 Yaml。它不是超级高效,但有一种相对简单的方法可以做到这一点。
class YamlConstructor extends Constructor {
@Override
protected Object constructObject(Node node) {
if (node.getTag() == Tag.MAP) {
LinkedHashMap<String, Object> map = (LinkedHashMap<String, Object>) super
.constructObject(node);
// If the map has the typeId and limit attributes
// return a new Item object using the values from the map
...
}
// In all other cases, use the default constructObject.
return super.constructObject(node);
这是我在非常相似的情况下所做的。我只是将我的整个 yml 文件放在一个选项卡上,并在顶部添加了一个 map: 标签。所以对于你的情况就是这样。
map:
19:
typeID: 2
limit: 300
20:
typeID: 8
limit: 100
然后在您的 class 中创建一个静态 class 来读取此文件,如下所示。
static class Items {
public Map<Integer, Item> map;
}
然后阅读您的地图只需使用。
Yaml yaml = new Yaml(new Constructor(Items));
Items items = (Items) yaml.load(<file>);
Map<Integer, Item> itemMap = items.map;
更新:
如果您不想或不能编辑您的 yml 文件,您可以在读取文件时使用类似这样的代码执行上述转换。
try (BufferedReader br = new BufferedReader(new FileReader(new File("example.yml")))) {
StringBuilder builder = new StringBuilder("map:\n");
String line;
while ((line = br.readLine()) != null) {
builder.append(" ").append(line).append("\n");
}
Yaml yaml = new Yaml(new Constructor(Items));
Items items = (Items) yaml.load(builder.toString());
Map<Integer, Item> itemMap = items.map;
}
为了保持类型安全,您需要控制根节点的构造。为此,可以使用Constructor的rootTag属性标记根节点,固定children的类型,构造map。
自定义构造函数应如下所示
public static class MyConstructor extends Constructor {
private TypeDescription itemType = new TypeDescription(Item.class);
public MyConstructor() {
this.rootTag = new Tag("myRoot");
this.addTypeDescription(itemType);
}
@Override
protected Object constructObject(Node node) {
if ("myRoot".equals(node.getTag().getValue()) && node instanceof MappingNode) {
MappingNode mNode = (MappingNode) node;
return mNode.getValue().stream().collect(
Collectors.toMap(
t -> super.constructObject(t.getKeyNode()),
t -> {
Node child = t.getValueNode();
child.setType(itemType.getType());
return super.constructObject(child);
}
)
);
} else {
return super.constructObject(node);
}
}
}
这是功能齐全的示例
import org.yaml.snakeyaml.TypeDescription;
import org.yaml.snakeyaml.Yaml;
import org.yaml.snakeyaml.constructor.Constructor;
import org.yaml.snakeyaml.nodes.MappingNode;
import org.yaml.snakeyaml.nodes.Node;
import org.yaml.snakeyaml.nodes.Tag;
import java.util.Map;
import java.util.stream.Collectors;
public class Foo {
public static void main(String[] args) {
String theYaml = "19:\n" +
" typeID: 2\n" +
" limit: 300\n" +
"20:\n" +
" typeID: 8\n" +
" limit: 100";
Yaml yaml = new Yaml(new MyConstructor());
Map<String, Item> data = yaml.load(theYaml);
System.out.println(data);
}
public static class Item {
private int typeID, limit;
public int getTypeID() {
return typeID;
}
public void setTypeID(int typeID) {
this.typeID = typeID;
}
public int getLimit() {
return limit;
}
public void setLimit(int limit) {
this.limit = limit;
}
}
public static class MyConstructor extends Constructor {
private TypeDescription itemType = new TypeDescription(Item.class);
public MyConstructor() {
this.rootTag = new Tag("myRoot");
this.addTypeDescription(itemType);
}
@Override
protected Object constructObject(Node node) {
if ("myRoot".equals(node.getTag().getValue()) && node instanceof MappingNode) {
MappingNode mNode = (MappingNode) node;
return mNode.getValue().stream().collect(
Collectors.toMap(
t -> super.constructObject(t.getKeyNode()),
t -> {
Node child = t.getValueNode();
child.setType(itemType.getType());
return super.constructObject(child);
}
)
);
} else {
return super.constructObject(node);
}
}
}
}
如果你不关心类型,只想鸭子打字(一切都是地图),你可以在没有设置的情况下加载。
Java
Map<String, Object> data = new Yaml().load(yamldata);
斯卡拉
val data: java.util.Map[String, Any] = new Yaml().load(yamlData)
从Groovy 3开始,您可以使用Groovy解析YAML。
import groovy.yaml.YamlSlurper
...
def yaml = new YamlSlurper().parseText(text)
如果你想将它映射到你的自定义类型,你可以这样做。
yaml.collect({key, item -> new Item(typeId: item?.typeID, limit: item?.limit)})
我意识到 Groovy 中对 YAML 的这种支持自 2019 年以来才可用。但现在 Groovy 确实为基于 JVM 的系统提供了一个简单的选项(即它可以混合使用与 Java), 我认为值得为其他可能遇到类似问题的人发帖。
我想将 YAML 文档读取到自定义对象的地图(而不是 snakeYaml 默认执行的地图)。所以这个:
19:
typeID: 2
limit: 300
20:
typeID: 8
limit: 100
将加载到如下所示的地图:
Map<Integer, Item>
其中项目是:
class Item {
private Integer typeId;
private Integer limit;
}
我找不到使用 snakeYaml 执行此操作的方法,我也找不到更好的库来完成这项任务。
文档只有 maps/collections 嵌套在其他对象中的示例,因此您可以执行以下操作:
TypeDescription typeDescription = new TypeDescription(ClassContainingAMap.class);
typeDescription.putMapPropertyType("propertyNameOfNestedMap", Integer.class, Item.class);
Constructor constructor = new Constructor(typeDescription);
Yaml yaml = new Yaml(constructor);
/* creating an input stream (is) */
ClassContainingAMap obj = (ClassContainingAMap) yaml.load(is);
但是当地图格式位于文档的根目录时,我该如何定义它呢?
您需要添加自定义 Constructor。但是,在您的情况下,您不想注册 "item" 或 "item-list" 标签。
实际上,您想将 Duck Typing 应用到您的 Yaml。它不是超级高效,但有一种相对简单的方法可以做到这一点。
class YamlConstructor extends Constructor {
@Override
protected Object constructObject(Node node) {
if (node.getTag() == Tag.MAP) {
LinkedHashMap<String, Object> map = (LinkedHashMap<String, Object>) super
.constructObject(node);
// If the map has the typeId and limit attributes
// return a new Item object using the values from the map
...
}
// In all other cases, use the default constructObject.
return super.constructObject(node);
这是我在非常相似的情况下所做的。我只是将我的整个 yml 文件放在一个选项卡上,并在顶部添加了一个 map: 标签。所以对于你的情况就是这样。
map:
19:
typeID: 2
limit: 300
20:
typeID: 8
limit: 100
然后在您的 class 中创建一个静态 class 来读取此文件,如下所示。
static class Items {
public Map<Integer, Item> map;
}
然后阅读您的地图只需使用。
Yaml yaml = new Yaml(new Constructor(Items));
Items items = (Items) yaml.load(<file>);
Map<Integer, Item> itemMap = items.map;
更新:
如果您不想或不能编辑您的 yml 文件,您可以在读取文件时使用类似这样的代码执行上述转换。
try (BufferedReader br = new BufferedReader(new FileReader(new File("example.yml")))) {
StringBuilder builder = new StringBuilder("map:\n");
String line;
while ((line = br.readLine()) != null) {
builder.append(" ").append(line).append("\n");
}
Yaml yaml = new Yaml(new Constructor(Items));
Items items = (Items) yaml.load(builder.toString());
Map<Integer, Item> itemMap = items.map;
}
为了保持类型安全,您需要控制根节点的构造。为此,可以使用Constructor的rootTag属性标记根节点,固定children的类型,构造map。
自定义构造函数应如下所示
public static class MyConstructor extends Constructor {
private TypeDescription itemType = new TypeDescription(Item.class);
public MyConstructor() {
this.rootTag = new Tag("myRoot");
this.addTypeDescription(itemType);
}
@Override
protected Object constructObject(Node node) {
if ("myRoot".equals(node.getTag().getValue()) && node instanceof MappingNode) {
MappingNode mNode = (MappingNode) node;
return mNode.getValue().stream().collect(
Collectors.toMap(
t -> super.constructObject(t.getKeyNode()),
t -> {
Node child = t.getValueNode();
child.setType(itemType.getType());
return super.constructObject(child);
}
)
);
} else {
return super.constructObject(node);
}
}
}
这是功能齐全的示例
import org.yaml.snakeyaml.TypeDescription;
import org.yaml.snakeyaml.Yaml;
import org.yaml.snakeyaml.constructor.Constructor;
import org.yaml.snakeyaml.nodes.MappingNode;
import org.yaml.snakeyaml.nodes.Node;
import org.yaml.snakeyaml.nodes.Tag;
import java.util.Map;
import java.util.stream.Collectors;
public class Foo {
public static void main(String[] args) {
String theYaml = "19:\n" +
" typeID: 2\n" +
" limit: 300\n" +
"20:\n" +
" typeID: 8\n" +
" limit: 100";
Yaml yaml = new Yaml(new MyConstructor());
Map<String, Item> data = yaml.load(theYaml);
System.out.println(data);
}
public static class Item {
private int typeID, limit;
public int getTypeID() {
return typeID;
}
public void setTypeID(int typeID) {
this.typeID = typeID;
}
public int getLimit() {
return limit;
}
public void setLimit(int limit) {
this.limit = limit;
}
}
public static class MyConstructor extends Constructor {
private TypeDescription itemType = new TypeDescription(Item.class);
public MyConstructor() {
this.rootTag = new Tag("myRoot");
this.addTypeDescription(itemType);
}
@Override
protected Object constructObject(Node node) {
if ("myRoot".equals(node.getTag().getValue()) && node instanceof MappingNode) {
MappingNode mNode = (MappingNode) node;
return mNode.getValue().stream().collect(
Collectors.toMap(
t -> super.constructObject(t.getKeyNode()),
t -> {
Node child = t.getValueNode();
child.setType(itemType.getType());
return super.constructObject(child);
}
)
);
} else {
return super.constructObject(node);
}
}
}
}
如果你不关心类型,只想鸭子打字(一切都是地图),你可以在没有设置的情况下加载。
Java
Map<String, Object> data = new Yaml().load(yamldata);
斯卡拉
val data: java.util.Map[String, Any] = new Yaml().load(yamlData)
从Groovy 3开始,您可以使用Groovy解析YAML。
import groovy.yaml.YamlSlurper
...
def yaml = new YamlSlurper().parseText(text)
如果你想将它映射到你的自定义类型,你可以这样做。
yaml.collect({key, item -> new Item(typeId: item?.typeID, limit: item?.limit)})
我意识到 Groovy 中对 YAML 的这种支持自 2019 年以来才可用。但现在 Groovy 确实为基于 JVM 的系统提供了一个简单的选项(即它可以混合使用与 Java), 我认为值得为其他可能遇到类似问题的人发帖。