Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java SnakeYaml - prevent dumping reference names

Tags:

java

snakeyaml

I have the following method which I use to get the object converted to yaml representation (which I can eg. print to console)

@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);
}

All is good, but for some object contained within ObjectToPrint structure I get something like reference name and not the real object content, eg.

!!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

As you can see from the example above I have ppiYear object printed (marked as $id001) and the same object is used in ppiPeriod but only the reference name is printed there, not the object content. How to print the object content everytime I use that object within my structure, which I want to be converted to yaml (ObjectToPrint). PS. It would be nice not to print the reference name at all (&id001) but thats not crucial

like image 517
Kris Avatar asked Aug 13 '13 06:08

Kris


2 Answers

This is because you reference the same object at different places. To avoid this you need to create copies of those objects. Yaml does not have a flag to switch this off, because you might get into endless loops in case of cyclic references. However you might tweak Yaml source code to ignore double references:

look at Serializer line ~170 method 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);
    }

if you find a way to avoid Yaml to put nodes into the serializedNodes collection, your problem will be solved, however your programm will loop endless in case of cyclic references.

Best solution is to add a hook which avoids registering only the class you want to be written plain.

like image 82
R.Moeller Avatar answered Nov 07 '22 03:11

R.Moeller


As a way to do this without changing the SnakeYAML source code, you can define:

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);
        }        
    }

}

and use it as e.g. new Yaml(new SafeConstructor(), new NonAnchorRepresenter());

This only does maps, and it does use anchors when it has to, i.e. where a map refers to an ancestor. It would need similar extensions for sets and lists if required. (In my case it was empty maps that were the biggest offender.)

(It would be more easily handled in the SnakeYAML codebase on Representer.representData looking at an option e.g. setAllowAnchors defaulting to true, with the logic above to reset representedObjects after recursing if disallowed. I take the point at https://stackoverflow.com/a/18419489/109079 about the possibility of endless loops but it would be straightforward using this strategy to detect any reference to a parent map and fail fast.)

like image 34
Partly Cloudy Avatar answered Nov 07 '22 03:11

Partly Cloudy