9

我有以下方法用于将对象转换为yaml表示形式(例如,我可以打印到控制台)

@Nonnull
private String outputObject(@Nonnull final ObjectToPrint packageSchedule) {
    DumperOptions options = new DumperOptions();
    options.setAllowReadOnlyProperties(true);
    options.setPrettyFlow(true);
    return new Yaml(new Constructor(), new JodaTimeRepresenter(), options).dump(ObjectToPrint);
}

一切都很好,但是对于ObjectToPrint结构中包含的某些对象,我得到的是参考名称,而不是真实的对象内容,例如。

!!com.blah.blah.ObjectToPrint
businessYears:
- businessYearMonths: 12
  ppiYear: &id001 {
    endDate: 30-06-2013,
    endYear: 2013,
    startDate: 01-07-2012,
    startYear: 2012
  }
  ppiPeriod:
    ppiYear: *id001
    endDate: 27-03-2014
    startDate: 21-06-2013
    units: 24.000
  number: 1

正如您从上面的示例中看到的那样,我ppiYear打印了对象(标记为$id001),并且使用了相同的对象,ppiPeriod但只打印了引用名称,而不是对象内容。每次在我的结构中使用该对象时如何打印对象内容,我想将其转换为 yaml ( ObjectToPrint)。PS。完全不打印参考名称会很好(&id001)但这并不重要

4

3 回答 3

8

这是因为你在不同的地方引用了同一个对象。为避免这种情况,您需要创建这些对象的副本。Yaml 没有关闭此功能的标志,因为在循环引用的情况下您可能会陷入无限循环。但是,您可以调整 Yaml 源代码以忽略双重引用:

看看 Serializer 行 ~170 方法 serializeNode:

...
 if ( this.serializedNodes.contains(node) ) {
    this.emmitter.emit( new AliasEvent( ... ) );
 } else {
    serializedNodes.add(node); // <== Replace with myHook(serializedNodes,node);
 ...

 void myHook(serializedNodes,node) {
    if ( node's class != myClass(es) to avoid ) {
        serializedNodes.add(node);
    }

如果您找到避免 Yaml 将节点放入 serializedNodes 集合的方法,您的问题将得到解决,但是在循环引用的情况下您的程序将无限循环。

最好的解决方案是添加一个钩子,避免只注册你想写的简单的类。

于 2013-08-24T14:16:00.980 回答
1

作为一种在不更改 SnakeYAML 源代码的情况下执行此操作的方法,您可以定义:

public class NonAnchorRepresenter extends Representer {

    public NonAnchorRepresenter() {
        this.multiRepresenters.put(Map.class, new RepresentMap() {
            public Node representData(Object data) {
                return representWithoutRecordingDescendents(data, super::representData);
            }            
        });
    }

    protected Node representWithoutRecordingDescendents(Object data, Function<Object,Node> worker) {
        Map<Object,Node> representedObjectsOnEntry = new LinkedHashMap<Object,Node>(representedObjects);
        try {
            return worker.apply(data);
        } finally {
            representedObjects.clear();
            representedObjects.putAll(representedObjectsOnEntry);
        }        
    }

}

并将其用作例如new Yaml(new SafeConstructor(), new NonAnchorRepresenter());

这仅适用于地图,并且在必要时确实使用锚点,即地图引用祖先的地方。如果需要,它需要对集合和列表进行类似的扩展。(就我而言,最大的违规者是空白地图。)

(它会更容易在 SnakeYAML 代码库中Representer.representData查看一个选项,例如setAllowAnchors默认为 true,如果不允许,上面的逻辑将在递归后重置representedObjects。我在https://stackoverflow.com/a/18419489/109079关于无限循环的可能性,但使用这种策略来检测对父映射的任何引用并快速失败会很简单。)

于 2018-05-08T17:39:37.733 回答
0

如果可能的话,我提出的另一个解决方案是使用io.circlesnake-yaml

斯卡拉代码:

 private[this] def removeAnchors(configYaml: String): String = {
    val withoutAnchorsObj = io.circe.yaml.parser.parse(configYaml).valueOr(throw _)
    val withoutAnchorString = io.circe.yaml.Printer(dropNullKeys = true, mappingStyle = Printer.FlowStyle.Block).pretty(withoutAnchorsObj)
    logger.info(s"Removed Anchors from configYaml: $configYaml, result: $withoutAnchorString")
    withoutAnchorString
  }

构建.sbt:

val circeVersion = "0.12.0"

libraryDependencies ++= Seq(
  "io.circe" %% "circe-yaml" % circeVersion,
  "io.circe" %% "circe-parser" % circeVersion,
)
于 2021-09-12T14:20:52.963 回答