Java SnakeYaml - 防止转储引用名称

时间:2013-08-13 06:56:29

标签: java snakeyaml

我有以下方法用于将对象转换为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),但这不是至关重要的

2 个答案:

答案 0 :(得分:5)

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

看看Serializer line~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集合中,你的问题就会得到解决,但是如果循环引用,你的程序将循环无穷。

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

答案 1 :(得分: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());

这只做地图, 在必要时使用锚点,即地图指的是祖先。如果需要,它将需要类似于集合和列表的扩展。 (就我而言,这是空地图,是最大的罪犯。)

(在Representer.representData上的SnakeYAML代码库中更容易处理,查看选项,例如setAllowAnchors默认为true,如果不允许,则在递归后重置representedObjects。我在https://stackoverflow.com/a/18419489/109079处讨论了无限循环的可能性,但使用此策略检测对父映射的任何引用并快速失败将是直截了当的。)