allow subclasses of Constructor to provide "soft" override of root document tag
Constructor
doesn't seem to allow you to specify a default root tag without overriding any explicit tags given in the YAML file.
(I'm using SnakeYAML 1.15, but 1.16 seems to have the same problem)
I have a YAML document that I want to map to a custom Bean class (TemplateConfiguration
). I've been doing this in the past as follows:
static TemplateConfiguration loadYaml(FileInputStream fileInputStream) {
Constructor constructor = new Constructor(TemplateConfiguration.class);
TypeDescription typedesc = new TypeDescription(TemplateConfiguration.class);
constructor.addTypeDescription(typedesc);
Yaml yaml = new Yaml(constructor);
return (TemplateConfiguration)yaml.load(fileInputStream);
}
Works great.
Recently I have a compatibility issue where I have to change the YAML schema and allow a different one instead. I'm going to use tags so the document can specify !v1
or !v2
or something to specify whether I used TemplateConfigurationV1
or TemplateConfigurationV2
implementation classes in Java (both descendants of a TemplateConfigurationFile
interface), with this code to load:
FileInputStream fis = new FileInputStream(file);
Constructor constructor = new Constructor();
addTagTypeDescription(constructor, TemplateConfigurationV1.class, "!v1");
addTagTypeDescription(constructor, TemplateConfigurationV2.class, "!v2");
Yaml yaml = new Yaml(constructor);
TemplateConfigurationFile tcfg = (TemplateConfigurationFile)yaml.load(fis);
This also works great. But I need to support old YAML files which don't have a type tag. And I can't get my code to work; BaseConstructor.getSingleData()
seems to be the culprit:
/**
* Ensure that the stream contains a single document and construct it
*
* @return constructed instance
* @throws ComposerException
* in case there are more documents in the stream
*/
public Object getSingleData(Class<?> type) {
// Ensure that the stream contains a single document and construct it
Node node = composer.getSingleNode();
if (node != null) {
if (Object.class != type) {
node.setTag(new Tag(type));
} else if (rootTag != null) {
node.setTag(rootTag);
}
return constructDocument(node);
}
return null;
}
Here the node tag override is controlled completely by the type given to getSingleData()
and the rootTag
; SnakeYAML ignores the node's tag from the YAML document itself.
I figured, great, I'll just subclass Constructor
and override getSingleData()
to setTag()
only if the node's tag matches Tag.MAP
. But I can't, because composer
and constructObject
are private, so my overriden method can't use them. :-(
Please help support this use case, at least by making getSingleData()
overrideable in practice.
Comments (4)
-
reporter -
reporter I found a hacky workaround for the moment.
/** * override of what's in org.yaml.snakeyaml.Yaml to get around issue * https://bitbucket.org/asomov/snakeyaml/issues/320/allow-subclasses-of-constructor-to-provide */ static private class YamlHack { protected BaseConstructor constructor; YamlHack(BaseConstructor constructor) { this.constructor = constructor; } @SuppressWarnings("unchecked") public <T> T loadAs(InputStream input, Class<T> type, String defaultTag) { return (T) loadFromReader(new StreamReader(new UnicodeReader(input)), type, defaultTag); } @SuppressWarnings("unchecked") public <T> T loadAs(InputStream input, Class<T> type) { return loadAs(input, type, null); } private Object loadFromReader(StreamReader sreader, Class<?> type, final String defaultTag) { if (defaultTag != null) { /* * override document tag, if appropriate, before the constructor * gets to it */ Composer composer = new Composer(new ParserImpl(sreader), new Resolver()) { @Override public Node getSingleNode() { Node node = super.getSingleNode(); if (node.getTag() == Tag.MAP) { node.setTag(new Tag(defaultTag)); } return node; } }; constructor.setComposer(composer); } else { Composer composer = new Composer(new ParserImpl(sreader), new Resolver()); constructor.setComposer(composer); } return constructor.getSingleData(Object.class); } } ... FileInputStream fis = new FileInputStream(file); org.yaml.snakeyaml.constructor.Constructor constructor = new org.yaml.snakeyaml.constructor.Constructor(); addTagTypeDescription(constructor, TemplateConfigurationV1.class, "!v1"); addTagTypeDescription(constructor, TemplateConfigurationV2.class, "!v2"); YamlHack yaml = new YamlHack(constructor); TemplateConfigurationFile tcfg = yaml.loadAs(fis, TemplateConfigurationFile.class, "!v1");
-
Done. https://bitbucket.org/asomov/snakeyaml/commits/e18bb04c65e5a93f4f72b3c81142d0afb615549f
It was probably
constructDocument()
method instead ofconstructObject()
.Feel free to test the latest snapshot.
-
- changed status to resolved
It will be delivered in version 1.17
- Log in to comment
could you make a
final protected getComposer()
, and makefinal protected constructObject()
instead of private?