CAD97 CAD97 - 19 days ago 5
YAML Question

Collapse single-child bean in SnakeYAML

I have a type which is a simple wrapper around a

Map<String, Integer>
, which I am dumping to YAML with SnakeYAML.

As an example:

class Flags {
private final Map<String, Boolean> _flags = new HashMap<>();
Boolean get(String flag) {
return _flags.containsKey(flag) ? _flags.get(flag) : false;
}
boolean put(String flag, Boolean value) {
return _flags.put(flag, value);
}
}


Currently, I'm using a
DumperOptions
with
allowReadOnlyProperties = true
and
BeanAccess
of
FIELD
to dump this class correctly. When put in a containing class, I get a
dumpAsMap
YAML like this:

flags:
_flags: {}


I would like to get SnakeYAML to dump this instead:

flags: {}


How can I accomplish this flattening of the single-element layer? As the inner variable is private, and the wrapping type should effectively act as the wrapped type, it would make sense that the wrapping type should be serialized as if it were the wrapped type.

Basically, I want the wrapping type to be serialized as if it were the wrapped type. There are not and will never be other variables which need to be serialized on this class outside the wrapped variable. I am open to changing the type declaration of the wrapping type, but the wrapping type must remain for my use case.

Full source for runnable reproduction case:

import org.yaml.snakeyaml.*;
import org.yaml.snakeyaml.introspector.*;
import java.util.*;

public class Example {
private final Flags flags = new Flags();
public Flags getFlags() { return flags; }

public static void main(String[] args) {
DumperOptions options = new DumperOptions();
options.setAllowReadOnlyProperties(true);
Yaml yaml = new Yaml(options);
yaml.setBeanAccess(BeanAccess.FIELD);
System.out.println(yaml.dumpAsMap(new Example()));
}
}

class Flags {
private final Map<String, Boolean> _flags = new HashMap<>();
Boolean get(String flag) {
return _flags.containsKey(flag) ? _flags.get(flag) : false;
}
boolean put(String flag, Boolean value) {
return _flags.put(flag, value);
}
}

Answer

Something like this may work (did not test the code):

class FlagsRepresenter extends Representer {
    public FlagsRepresenter() {
        this.representers.put(Flags.class, new RepresentFlags());
    }

    private class RepresentFlags implements Represent {
        public Node representData(Object data) {
            // going the hacky, painful way of accessing a private field here.
            // YMMV.
            try {
                final Field f = data.getClass().getDeclaredField("_flags");
                f.setAccessible(true);
                final Map<String, Boolean> inner =
                        (Map<String, Boolean>) f.get(data);
                return representMapping(Tag.MAP, inner, null);
            } catch (final Exception ignored) {
                // will not occur as long as field _flags exists and has the
                // expected type.
                return null;
            }

        }
    }
}

Use it like this:

Yaml yaml = new Yaml(new FlagsRepresenter(), new DumperOptions());