Wiki
Clone wikisnakeyaml / Documentation
SnakeYAML Documentation
- SnakeYAML Documentation
- Installation
- Tutorial
- Deviation from PyYAML
- YAML syntax
- YAML tags and Java types
- Collections
- Merge
- Deviations from the specification
This documentation is very brief and incomplete. Feel free to fix or improve it.
Installation
If you use Maven just add a dependency as described here.
Tutorial
Start with instantiating the org.yaml.snakeyaml.Yaml
instance.
Yaml yaml = new Yaml();
Loading YAML
The method Yaml.load()
converts a YAML document to a Java object.
LoaderOptions options = new LoaderOptions(); Yaml yaml = new Yaml(options); String document = "\n- Hesperiidae\n- Papilionidae\n- Apatelodidae\n- Epiplemidae"; List<String> list = (List<String>) yaml.load(document); System.out.println(list); ['Hesperiidae', 'Papilionidae', 'Apatelodidae', 'Epiplemidae']
Yaml.load()
accepts a String or
an InputStream
object. Yaml.load(InputStream stream)
detects the encoding
by checking the BOM (byte order mark) sequence at the beginning of the
stream. If no BOM is present, the utf-8 encoding is assumed.
Yaml.load()
returns a Java object.
public void testLoadFromString() { Yaml yaml = new Yaml(); String document = "hello: 25"; Map map = (Map) yaml.load(document); assertEquals("{hello=25}", map.toString()); assertEquals(new Long(25), map.get("hello")); } public void testLoadFromStream() throws FileNotFoundException { InputStream input = new FileInputStream(new File("src/test/resources/reader/utf-8.txt")); Yaml yaml = new Yaml(); Object data = yaml.load(input); assertEquals("test", data); // data = yaml.load(new ByteArrayInputStream("test2".getBytes())); assertEquals("test2", data); }
If a String or a stream contains several documents, you may load them all with the
Yaml.loadAll()
method.
--- Time: 2001-11-23 15:01:42 -5 User: ed Warning: This is an error message for the log file --- Time: 2001-11-23 15:02:31 -5 User: ed Warning: A slightly different error message. --- Date: 2001-11-23 15:03:17 -5 User: ed Fatal: Unknown variable "bar" Stack: - file: TopClass.py line: 23 code: | x = MoreObject("345\n") - file: MoreClass.py line: 58 code: |- foo = bar
public void testLoadManyDocuments() throws FileNotFoundException { InputStream input = new FileInputStream(new File( "src/test/resources/specification/example2_28.yaml")); Yaml yaml = new Yaml(); int counter = 0; for (Object data : yaml.loadAll(input)) { System.out.println(data); counter++; } assertEquals(3, counter); }
{Time=Fri Nov 23 21:01:42 CET 2001, User=ed, Warning=This is an error message for the log file} {Time=Fri Nov 23 21:02:31 CET 2001, User=ed, Warning=A slightly different error message.} {Date=Fri Nov 23 21:03:17 CET 2001, User=ed, Fatal=Unknown variable "bar", Stack=[{file=TopClass.py, line=23, code=x = MoreObject("345\n") }, {file=MoreClass.py, line=58, code=foo = bar}]}
SnakeYAML allows you to construct a Java object of any type.
none: [~, null] bool: [true, false, on, off] int: 42 float: 3.14159 list: [LITE, RES_ACID, SUS_DEXT] map: {hp: 13, sp: 5}
public void testLoad() throws IOException { String doc = Util.getLocalResource("examples/any-object-example.yaml"); Yaml yaml = new Yaml(); Map<String, Object> object = (Map<String, Object>) yaml.load(doc); System.out.println(object); }
{none=[null, null], bool=[true, false, true, false], int=42, float=3.14159, list=[LITE, RES_ACID, SUS_DEXT], map={hp=13, sp=5}}
Even instances of custom Java classes can be constructed.
/** * create JavaBean */ public void testGetBeanAssumeClass() { String data = "--- !org.yaml.snakeyaml.constructor.Person\nfirstName: Andrey\nage: 99"; Object obj = construct(data); assertNotNull(obj); assertTrue("Unexpected: " + obj.getClass().toString(), obj instanceof Person); Person person = (Person) obj; assertEquals("Andrey", person.getFirstName()); assertNull(person.getLastName()); assertEquals(99, person.getAge().intValue()); } /** * create instance using constructor arguments */ public void testGetConstructorBean() { String data = "--- !org.yaml.snakeyaml.constructor.Person [ Andrey, Somov, 99 ]"; Object obj = construct(data); assertNotNull(obj); assertTrue(obj.getClass().toString(), obj instanceof Person); Person person = (Person) obj; assertEquals("Andrey", person.getFirstName()); assertEquals("Somov", person.getLastName()); assertEquals(99, person.getAge().intValue()); } /** * create instance using scalar argument */ public void testGetConstructorFromScalar() { String data = "--- !org.yaml.snakeyaml.constructor.Person 'Somov'"; Object obj = construct(data); assertNotNull(obj); assertTrue(obj.getClass().toString(), obj instanceof Person); Person person = (Person) obj; assertNull("Andrey", person.getFirstName()); assertEquals("Somov", person.getLastName()); assertNull(person.getAge()); }
Note: if you want to limit objects to standard Java objects like List or Long you need to use SafeConstructor.
Yaml yaml = new Yaml(new SafeConstructor(new LoaderOptipon()));
Providing the top level type
It is possible to load a YAML document without any explicit tags. For instance, to load this document (example 2.27 from the YAML specification)
invoice: 34843 date : 2001-01-23 billTo: &id001 given : Chris family : Dumars address: lines: | 458 Walkman Dr. Suite #292 city : Royal Oak state : MI postal : 48046 shipTo: *id001 product: - sku : BL394D quantity : 4 description : Basketball price : 450.00 - sku : BL4438H quantity : 1 description : Super Hoop price : 2392.00 tax : 251.42 total: 4443.52 comments: Late afternoon is best. Backup contact is Nancy Billsmer @ 338-4338.
Yaml yaml = new Yaml(new Constructor(Invoice.class, new LoaderOptions()));
Implicit types
When a tag for a scalar node is not explicitly defined, SnakeYAML tries to detect the type applying regular expressions to the content of the scalar node.
1.0 -> Float 42 -> Integer 2009-03-30 -> Date
An example how to change the default implicit types can be found here
Type safe collections
When type-safe (generic) collections are JavaBean
properties SnakeYAML
dynamically detects the required class.
A lot of examples can be found here
It does not work if generic type is abstract class (interface). You have to put an explicit tag in the YAML or provide
the explicit TypeDescription
.
TypeDescription
serves the goal to collect more information and use it while loading/dumping.
Let's say we have this document:
plate: 12-XP-F4 wheels: - {id: 1} - {id: 2} - {id: 3} - {id: 4} - {id: 5}
public class Car { private String plate; private List<Wheel> wheels; public String getPlate() { return plate; } public void setPlate(String plate) { this.plate = plate; } public List<Wheel> getWheels() { return wheels; } public void setWheels(List<Wheel> wheels) { this.wheels = wheels; } }
List<Wheel>
)
TypeDescription
must be provided:
Constructor constructor = new Constructor(Car.class);//Car.class is root TypeDescription carDescription = new TypeDescription(Car.class); /* * Before * carDescription.putListPropertyType("wheels", Wheel.class); */ carDescription.addPropertyParameters("wheels", Wheel.class); constructor.addTypeDescription(carDescription); Yaml yaml = new Yaml(constructor);
testTypeSafeList2()
).
A similar approach works for Maps. Please note that both keys and values of the Map can be of any type:
plate: 00-FF-Q2 wheels: ? {brand: Pirelli, id: 1} : 2008-01-16 ? {brand: Dunkel, id: 2} : 2002-12-24 ? {brand: Pirelli, id: 3} : 2008-01-16 ? {brand: Pirelli, id: 4} : 2008-01-16 ? {brand: Pirelli, id: 5} : 2008-01-16
public class MyCar { private String plate; private Map<MyWheel, Date> wheels; public String getPlate() { return plate; } public void setPlate(String plate) { this.plate = plate; } public Map<MyWheel, Date> getWheels() { return wheels; } public void setWheels(Map<MyWheel, Date> wheels) { this.wheels = wheels; } }
Constructor constructor = new Constructor(MyCar.class); TypeDescription carDescription = new TypeDescription(MyCar.class); /* * Before: * carDescription.putMapPropertyType("wheels", MyWheel.class, Object.class); */ carDescription.addPropertyParameters("wheels", MyWheel.class, Object.class); constructor.addTypeDescription(carDescription); Yaml yaml = new Yaml(constructor); MyCar car = (MyCar) yaml.load(<see above>);
Dumping YAML
The Yaml.dump(Object data)
method accepts a Java object and produces a YAML document.
(the source is here)
public void testDump() { Map<String, Object> data = new HashMap<String, Object>(); data.put("name", "Silenthand Olleander"); data.put("race", "Human"); data.put("traits", new String[] { "ONE_HAND", "ONE_EYE" }); Yaml yaml = new Yaml(); String output = yaml.dump(data); System.out.println(output); }
name: Silenthand Olleander traits: [ONE_HAND, ONE_EYE] race: Human
Yaml.dump(Object data, Writer output)
will write the produced YAML document into
the specified file/stream.
public void testDumpWriter() { Map<String, Object> data = new HashMap<String, Object>(); data.put("name", "Silenthand Olleander"); data.put("race", "Human"); data.put("traits", new String[] { "ONE_HAND", "ONE_EYE" }); Yaml yaml = new Yaml(); StringWriter writer = new StringWriter(); yaml.dump(data, writer); System.out.println(writer.toString()); }
If you need to dump several YAML documents to a single stream, use the method
Yaml.dumpAll(Iterator<Object> data)
. It accepts an Iterator of
Java objects to be serialized into a YAML document. A Writer can also be used.
public void testDumpMany() { List<Integer> docs = new LinkedList<Integer>(); for (int i = 1; i < 4; i++) { docs.add(i); } DumperOptions options = new DumperOptions(); options.explicitStart(true); Yaml yaml = new Yaml(options); System.out.println(yaml.dump(docs)); System.out.println(yaml.dumpAll(docs.iterator())); }
--- [1, 2, 3] --- 1 --- 2 --- 3
You may even dump instances of JavaBeans
.
public void testDumpCustomJavaClass() { Hero hero = new Hero("Galain Ysseleg", -3, 2); Yaml yaml = new Yaml(); String output = yaml.dump(hero); System.out.println(output); assertEquals("!!examples.Hero {hp: -3, name: Galain Ysseleg, sp: 2}\n", output); }
!!examples.Hero {hp: -3, name: Galain Ysseleg, sp: 2}
As you can see the JavaBean
data is sorted althabetically.
DumperOptions
specifies
formatting details for the emitter. For instance, you may set the
preferred intendation and width, use the canonical YAML format or
force preferred style for scalars and collections.
public void testDumperOptions() { List<Integer> data = new LinkedList<Integer>(); for (int i = 0; i < 50; i++) { data.add(i); } Yaml yaml = new Yaml(); String output = yaml.dump(data); System.out.println(output); // DumperOptions options = new DumperOptions(); options.setWidth(50); options.setIndent(4); yaml = new Yaml(options); output = yaml.dump(data); System.out.println(output); }
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49] [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49]
public void testDumperOptionsCanonical() { List<Integer> data = new LinkedList<Integer>(); for (int i = 0; i < 5; i++) { data.add(i); } DumperOptions options = new DumperOptions(); options.setCanonical(true); Yaml yaml = new Yaml(options); String output = yaml.dump(data); System.out.println(output); }
--- !!seq [ !!int "0", !!int "1", !!int "2", !!int "3", !!int "4", ]
public void testDumperOptionsFlowStyle() { List<Integer> data = new LinkedList<Integer>(); for (int i = 0; i < 5; i++) { data.add(i); } DumperOptions options = new DumperOptions(); options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK); Yaml yaml = new Yaml(options); String output = yaml.dump(data); System.out.println(output); }
- 0 - 1 - 2 - 3 - 4
public void testDumperOptionsStyle() { List<Integer> data = new LinkedList<Integer>(); for (int i = 0; i < 5; i++) { data.add(i); } DumperOptions options = new DumperOptions(); options.setDefaultScalarStyle(DumperOptions.ScalarStyle.DOUBLE_QUOTED); Yaml yaml = new Yaml(options); String output = yaml.dump(data); System.out.println(output); }
- !!int "0" - !!int "1" - !!int "2" - !!int "3" - !!int "4"
Dumping a custom YAML document
The main goal of dumping in SnakeYAML is to produce a YAML document which can be loaded back to the instance. There are cases where a created YAML document does not look as expected:
- immutable object. Immutable objects may have getters but they do not have setters. By default it is emitted as a map and therefore it can not be parsed later. Immutable objects must be emitted as a sequence where the order is very important
- comments. Having comments all over the place help humans to edit the YAML document
- skip some JavaBean properties (empty collections, null, calculated properties etc).
- define order for
JavaBean
properties. Order may be very informative: for instance ID or name are normally expected to be the first. (Currently it is alphabetically sorted by default.) - since standard types have more then one value it is possible to configure
Representer
to use different values. See examples for!!null
and for!!bool
Since a YAML document is nothing but a text document any Template processor can be used.
General algorithm:
-
Try to find out whether it is possible to use only
DumperOptions
-
If not, use
Yaml.dumpAs(obj, Tag.MAP)
to see how YAML should look like. -
Pick up your favourite template engine and go ahead.
-
Write a test which parses the generated YAML document back to the instance to be reminded by the test to follow the code changes.
-
You can always consider a possibility to extend SnakeYAML code. Take a look at org.yaml.snakeyaml.representer.Representer. If you find your changes useful do not forget to contribute the enhancements back to SnakeYAML so anybody can benefit.
-
Implement your own way to serialize an instance into a text YAML document
An example of using templates is here. Please note that you can use SnakeYAML to produce parts of the result and then provide these parts to the template.
JavaBeans
The spec says - "One of the main goals of the JavaBeans
architecture is to provide a platform neutral component
architecture."
Avoiding global tags significantly improves ability to exchange the YAML documents between different platforms and languages.
If the custom Java class conforms to the JavaBean
specification it can be loaded and dumped
without any extra code. For instance this JavaBean
public class CarWithWheel { private String plate; private String year; private Wheel wheel; private Object part; private Map<String, Integer> map; public String getPlate() { return plate; } public void setPlate(String plate) { this.plate = plate; } public Wheel getWheel() { return wheel; } public void setWheel(Wheel wheel) { this.wheel = wheel; } public Map<String, Integer> getMap() { return map; } public void setMap(Map<String, Integer> map) { this.map = map; } public Object getPart() { return part; } public void setPart(Object part) { this.part = part; } public String getYear() { return year; } public void setYear(String year) { this.year = year; } }
CarWithWheel car1 = new CarWithWheel(); car1.setPlate("12-XP-F4"); Wheel wheel = new Wheel(); wheel.setId(2); car1.setWheel(wheel); Map<String, Integer> map = new HashMap<String, Integer>(); map.put("id", 3); car1.setMap(map); car1.setPart(new Wheel(4)); car1.setYear("2008"); String output = new Yaml().dump(car1);
!!package.CarWithWheel map: {id: 3} part: !!org.yaml.snakeyaml.constructor.Wheel {id: 4} plate: 12-XP-F4 wheel: {id: 2} year: '2008'
CarWithWheel
class).
The example for the above JavaBean
can be found
here
The preferred way to dump a JavaBean
is to use Yaml.dumpAs(obj, Tag.MAP). This utility is emitting with the block layout and does not emit the root global tag with the class name (using implicit !!map tag).
The preferred way to parse a JavaBean
is to use Yaml.loadAs()
. It eliminates the need to cast returned instances to the specified class.
By default standard JavaBean
properties and public fields are included. BeanAccess.FIELD makes is possible to use private fields directly.
Shortcuts
There is a way to define local tags for custom classes.
!!org.yaml.snakeyaml.constructor.Car plate: 12-XP-F4 wheels: - !!org.yaml.snakeyaml.constructor.Wheel {id: 1} - !!org.yaml.snakeyaml.constructor.Wheel {id: 2} - !!org.yaml.snakeyaml.constructor.Wheel {id: 3} - !!org.yaml.snakeyaml.constructor.Wheel {id: 4} - !!org.yaml.snakeyaml.constructor.Wheel {id: 5}
Representer representer = new Representer(); representer.addClassTag(Car.class, new Tag("!car")); representer.addClassTag(Wheel.class, Tag.MAP);; Yaml yaml = new Yaml(representer, new DumperOptions()); String output = yaml.dump(car);
!car plate: 12-XP-F4 wheels: - {id: 1} - {id: 2} - {id: 3} - {id: 4} - {id: 5}
Constructor constructor = new Constructor(); constructor.addTypeDescription(new TypeDescription(Car.class, "!car")); Yaml yaml = new Yaml(constructor);
Constructors, representers, resolvers
You may define your own application-specific tags. (the example's source is here)
For instance, you may want to add a constructor and a representer for the following Dice class:
public class Dice { private Integer a; private Integer b; public Dice(Integer a, Integer b) { super(); this.a = a; this.b = b; } public Integer getA() { return a; } public Integer getB() { return b; } @Override public boolean equals(Object obj) { if (obj instanceof Dice) { return toString().equals(obj.toString()); } return false; } @Override public int hashCode() { return toString().hashCode(); } @Override public String toString() { return "Dice " + a + "d" + b; } }
The default representation for Dice
objects is not nice:
public void testRepresenter() throws IOException { Dice dice = new Dice(3, 6); Yaml yaml = new Yaml(); String output = yaml.dump(dice); System.out.println(output); }
!!examples.Dice {a: 3, b: 6}
Suppose you want a Dice
object to represented as AdB
in YAML:
System.out.println(yaml.dump(new Dice(3,6))); 3d6
First we define a representer that convert a dice object to scalar node
with the tag !dice
and register it.
class DiceRepresenter extends Representer { public DiceRepresenter() { this.representers.put(Dice.class, new RepresentDice()); } private class RepresentDice implements Represent { public Node representData(Object data) { Dice dice = (Dice) data; String value = dice.getA() + "d" + dice.getB(); return representScalar(new Tag("!dice"), value); } } }
Now you may dump an instance of the Dice
object:
public void testDiceRepresenter() throws IOException { Dice dice = new Dice(3, 6); Map<String, Dice> data = new HashMap<String, Dice>(); data.put("gold", dice); Yaml yaml = new Yaml(new DiceRepresenter(), new DumperOptions()); String output = yaml.dump(data); System.out.println(output); }
{gold: !dice '10d6'}
Let us add the code to construct a Dice object:
class DiceConstructor extends Constructor { public DiceConstructor() { this.yamlConstructors.put(new Tag("!dice"), new ConstructDice()); } private class ConstructDice extends AbstractConstruct { public Object construct(Node node) { String val = (String) constructScalar(node); int position = val.indexOf('d'); Integer a = Integer.parseInt(val.substring(0, position)); Integer b = Integer.parseInt(val.substring(position + 1)); return new Dice(a, b); } } }
Then you may load a Dice
object as well:
public void testConstructor() throws IOException { Yaml yaml = new Yaml(new DiceConstructor()); Object data = yaml.load("{initial hit points: !dice '8d4'}"); Map<String, Dice> map = (Map<String, Dice>) data; assertEquals(new Dice(8, 4), map.get("initial hit points")); }
You might want to not specify the tag !dice
everywhere. There is a way
to teach SankeYAML that any untagged plain scalar that looks like XdY has
the implicit tag !dice
. Use Yaml.addImplicitResolver(String tag, Pattern regexp, String first)
then you don't have to specify the tag to define a Dice
object:
public void testImplicitResolver() throws IOException { Yaml yaml = new Yaml(new DiceConstructor(), new DiceRepresenter(), new DumperOptions()); yaml.addImplicitResolver(new Tag("!dice"), Pattern.compile("\\d+d\\d+"), "123456789"); // dump Map<String, Dice> treasure = (Map<String, Dice>) new HashMap<String, Dice>(); treasure.put("treasure", new Dice(10, 20)); String output = yaml.dump(treasure); System.out.println(output); assertEquals("{treasure: 10d20}\n", output); // load Object data = yaml.load("{damage: 5d10}"); Map<String, Dice> map = (Map<String, Dice>) data; assertEquals(new Dice(5, 10), map.get("damage")); }
{treasure: 10d20}
Enum
SnakeYAML treats Enum
s in a special way. (an example can be found
here)
Normally an Enum
requires an explicit global tag:
public void testDumpEnum() { Yaml yaml = new Yaml(); String output = yaml.dump(Suit.CLUBS); assertEquals("!!org.yaml.snakeyaml.Suit 'CLUBS'\n", output); } public void testLoadEnum() { Yaml yaml = new Yaml(); Suit suit = (Suit) yaml.load("!!org.yaml.snakeyaml.Suit 'CLUBS'"); assertEquals(Suit.CLUBS, suit); }
But if the Enum is a JavaBean
property (and the class is implicitly defined) then the tags are not used:
public void testDumpEnumBean() { DumperOptions options = new DumperOptions(); options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK); Yaml yaml = new Yaml(options); EnumBean bean = new EnumBean(); bean.setId(17); bean.setSuit(Suit.SPADES); String output = yaml.dump(bean); System.out.println(output); }
!!org.yaml.snakeyaml.EnumBean id: 17 suit: SPADES
The same for loading:
public void testLoadEnumBean() { Yaml yaml = new Yaml(); EnumBean bean = (EnumBean) yaml.load("!!org.yaml.snakeyaml.EnumBean\nid: 174\nsuit: CLUBS"); assertEquals(Suit.CLUBS, bean.getSuit()); assertEquals(174, bean.getId()); }
Threading
The implementation is not thread-safe. Different threads may not call the same instance. Threads must have separate Yaml
instances. All the internal components of Yaml instance (construtor, representer) may not be shared in multi-threaded environment.
Spring
Example of Spring definition: (note: the scope is always 'prototype')
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:util="http://www.springframework.org/schema/util" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-2.0.xsd"> <!-- the most powerful way --> <bean id="yamlConstructor" class="examples.CustomConstructor" scope="prototype" /> <bean id="yamlRepresenter" class="org.yaml.snakeyaml.representer.Representer" scope="prototype" /> <bean id="yamlOptions" class="org.yaml.snakeyaml.DumperOptions" scope="prototype"> <property name="indent" value="2" /> </bean> <bean id="snakeYaml" class="org.yaml.snakeyaml.Yaml" scope="prototype"> <constructor-arg ref="yamlConstructor" /> <constructor-arg ref="yamlRepresenter" /> <constructor-arg ref="yamlOptions" /> </bean> <!-- for a single JavaBean --> <bean id="beanConstructor" class="org.yaml.snakeyaml.constructor.Constructor" scope="prototype"> <constructor-arg value="org.yaml.snakeyaml.Invoice" /> </bean> <bean id="javabeanYaml" class="org.yaml.snakeyaml.Yaml" scope="prototype"> <constructor-arg ref="beanConstructor" /> </bean> <!-- the simplest way --> <bean id="standardYaml" class="org.yaml.snakeyaml.Yaml" scope="prototype" /> </beans>
Low Level API
It is possible to parse or compose the incoming stream of characters. Examples can be found here for parsing or here for composing.
The input must be specified as java.io.Reader
. Use
UnicodeReader.java
if the input is java.io.InputStream
(for proper BOM support).
Generics
Not all JVM implementations provide a reliable way to recognize classes for JavaBean
properties at runtime if a JavaBean
is a generic class. Introspector.getBeanInfo()
returns a PropertyDescriptor
with Object as type instead of the real runtime class. It leads to emitting unnecessary global tags with class names.
As a consequence a YAML document produced in Java cannot be consumed by Python.
See some info here http://bugs.sun.com/view_bug.do?bug_id=6528714 .
On the other hand missing global tags will cause parsing exception when constructing a JavaBean
.
The easiest solution is to follow the simple rule: DO NOT use Generic class definitions (http://en.wikipedia.org/wiki/Generics_in_Java) at all. But if you have to:
-
Read the test to see the difference in behavior
-
Experiment with JVMs you use on both sides (loading and dumping)
-
To avoid parsing error one may need to provide custom Contructor or Representor to synchronize tags generation and consumption
-
Check issue 107 for the context
Immutable instances
The only way to create immutable instance is to provide all the required arguments for the appropriate constructor. In a YAML document it will be represented as a sequence.
!!org.yaml.snakeyaml.immutable.Point [1.17, 3.14]
Compact Object Notation
Custom Class Loader
In mobile applications one may need to use classes obtained from the network. It is possible to provide a custom class loader when parsing a YAML document. An example can be found here.
Restrict classes to be loaded
A public web-service may wish to restrict the classes to be loaded from a YAML document. For instance, this should be avoided:
!!java.io.FileOutputStream [woops.txt]
SnakeYAML
provides a way to filter classes to be loaded.
Tags
Explicit tags can be local (begin with single exclamation mark '!car') and global (begin with 2 exclamation marks '!!com.Car'). Global tags work well within a single programming language, because they can be parsed without extra configuration.
Local tags require additional context. Since every parser must provide more information at runtime to create instances, local tags help a YAML document to be exchanged with other languages (every parser is free to define what to do with !car
)
Implicit tags are assigned to every scalar and they are determined from the regular expressions. It is possible to add custom implicit tags. When a scalar is created the following order is used:
-
explicit tag - when it is present.
-
implicit tag - when the runtime class of the instance is unknown
It means that the implicit tag is ignored as soon as any other information about a scalar is available.
Deviation from PyYAML
- SnakeYAML does not emit '...' (end of document) for simple documents
- SnakeYAML emits Unicode (not ASCII) by default
- Global tags do not contain a "
namespace
" but only the full class name - '8e-06' and '8e6' scalars use implicit !!float tag in SnakeYAML. This is how it is defined in the YAML 1.2 specification (issue). Since the values do not match the regular expressions for floats, PyYAML is parsing these values as strings.
- SnakeYAML (and libyaml) allows TAB characters to appear inside a plain scalar
- SnakeYAML allows non ASCII characters as anchor names (see issue 468)
YAML syntax
A good introduction to the YAML syntax is Chapter 2 of the YAML specification.
Here we present most common YAML constructs together with the corresponding Java objects.
Documents
YAML stream is a collection of zero or more documents. An empty stream contains no documents.
Documents are separated with ---
. Documents may optionally end with ...
.
A single document may or may not be marked with ---
.
Example of an implicit document:
- Multimedia - Internet - Education
Example of an explicit document:
--- - Afterstep - CTWM - Oroborus ...
Example of several documents in the same stream:
--- - Ada - APL - ASP - Assembly - Awk --- - Basic --- - C - C# # Note that comments are denoted with ' #' (space and #). - C++ - Cold Fusion
Block sequences
In the block context, sequence entries are denoted by -
(dash and space):
# YAML - The Dagger 'Narthanc' - The Dagger 'Nimthanc' - The Dagger 'Dethanc'
# Java ["The Dagger 'Narthanc'", "The Dagger 'Nimthanc'", "The Dagger 'Dethanc'"]
Block sequences can be nested:
# YAML - - HTML - LaTeX - SGML - VRML - XML - YAML - - BSD - GNU Hurd - Linux
# Java [['HTML', 'LaTeX', 'SGML', 'VRML', 'XML', 'YAML'], ['BSD', 'GNU Hurd', 'Linux']]
It's not necessary to start a nested sequence with a new line:
# YAML - 1.1 - - 2.1 - 2.2 - - - 3.1 - 3.2 - 3.3
# Java [1.1, [2.1, 2.2], [[3.1, 3.2, 3.3]]]
A block sequence may be nested to a block mapping. Note that in this case it is not necessary to indent the sequence.
# YAML left hand: - Ring of Teleportation - Ring of Speed right hand: - Ring of Resist Fire - Ring of Resist Cold - Ring of Resist Poison
# Java {'right hand': ['Ring of Resist Fire', 'Ring of Resist Cold', 'Ring of Resist Poison'], 'left hand': ['Ring of Teleportation', 'Ring of Speed']}
Block mappings
In the block context, keys and values of mappings are separated by :
(colon and space):
# YAML base armor class: 0 base damage: [4,4] plus to-hit: 12 plus to-dam: 16 plus to-ac: 0
# Java {'plus to-hit': 12, 'base damage': [4, 4], 'base armor class': 0, 'plus to-ac': 0, 'plus to-dam': 16}
Complex keys are denoted with ?
(question mark and space):
# YAML ? !!python/tuple [0,0] : The Hero ? !!python/tuple [0,1] : Treasure ? !!python/tuple [1,0] : Treasure ? !!python/tuple [1,1] : The Dragon
# Java {(0, 1): 'Treasure', (1, 0): 'Treasure', (0, 0): 'The Hero', (1, 1): 'The Dragon'}
Block mapping can be nested:
# YAML hero: hp: 34 sp: 8 level: 4 orc: hp: 12 sp: 0 level: 2
# Java {'hero': {'hp': 34, 'sp': 8, 'level': 4}, 'orc': {'hp': 12, 'sp': 0, 'level': 2}}
A block mapping may be nested in a block sequence:
# YAML - name: PyYAML status: 4 license: MIT language: Python - name: PySyck status: 5 license: BSD language: Python
# Java [{'status': 4, 'language': 'Python', 'name': 'PyYAML', 'license': 'MIT'}, {'status': 5, 'license': 'BSD', 'name': 'PySyck', 'language': 'Python'}]
Flow collections
The syntax of flow collections in YAML is very close to the syntax of list and dictionary constructors in Python:
# YAML { str: [15, 17], con: [16, 16], dex: [17, 18], wis: [16, 16], int: [10, 13], chr: [5, 8] }
# Java {'dex': [17, 18], 'int': [10, 13], 'chr': [5, 8], 'wis': [16, 16], 'str': [15, 17], 'con': [16, 16]}
Scalars
There are 5 styles of scalars in YAML: plain, single-quoted, double-quoted, literal, and folded:
# YAML plain: Scroll of Remove Curse single-quoted: 'EASY_KNOW' double-quoted: "?" literal: | # Borrowed from http://www.kersbergen.com/flump/religion.html by hjw ___ __ /.-.\ / )_____________\\ Y /_ /=== == === === =\ _\_ ( /)=== == === === == Y \ `-------------------( o ) \___/ folded: > It removes all ordinary curses from all equipped items. Heavy or permanent curses are unaffected.
# Java {'plain': 'Scroll of Remove Curse', 'literal': 'by hjw ___\n' ' __ /.-.\\\n' ' / )_____________\\\\ Y\n' ' /_ /=== == === === =\\ _\\_\n' '( /)=== == === === == Y \\\n' ' `-------------------( o )\n' ' \\___/\n', 'single-quoted': 'EASY_KNOW', 'double-quoted': '?', 'folded': 'It removes all ordinary curses from all equipped items. Heavy or permanent curses are unaffected.\n'}
Each style has its own quirks. A plain scalar does not use indicators to denote its start and end, therefore it's the most restricted style. Its natural applications are names of attributes and parameters.
Using single-quoted scalars, you may express any value that does not contain special characters.
No escaping occurs for single quoted scalars except that duplicate quotes ''
are replaced
with a single quote '
.
Double-quoted is the most powerful style and the only style that can express any scalar value.
Double-quoted scalars allow escaping. Using escaping sequences \x**
and \u****
,
you may express any ASCII or Unicode character.
There are two kind of block scalar styles: literal and folded. The literal style is the most suitable style for large block of text such as source code. The folded style is similar to the literal style, but two consequent non-empty lines are joined to a single line separated by a space character.
Aliases
Using YAML you may represent objects of arbitrary graph-like structures. If you want to refer to the same object from different parts of a document, you need to use anchors and aliases.
Anchors are denoted by the &
indicator while aliases are denoted by *
. For instance,
the document
left hand: &A name: The Bastard Sword of Eowyn weight: 30 right hand: *A
SnakeYAML now fully supports recursive objects. For instance, the document
&A [ *A ]
Tags
Tags are used to denote the type of a YAML node. Standard YAML tags are defined at http://yaml.org/type/index.html.
Tags may be implicit:
boolean: true integer: 3 float: 3.14
#!python {'boolean': True, 'integer': 3, 'float': 3.14}
or explicit:
boolean: !!bool "true" integer: !!int "3" float: !!float "3.14"
#!python {'boolean': True, 'integer': 3, 'float': 3.14}
Plain scalars without explicitly defined tag are subject to implicit tag resolution. The scalar value is checked against a set of regular expressions and if one of them matches, the corresponding tag is assigned to the scalar. SnakeYAML allows an application to add custom implicit tag resolvers.
YAML tags and Java types
The following table describes how nodes with different tags are converted to Java objects.
YAML tag | Java type |
---|---|
Standard YAML tags | |
!!null |
null |
!!bool |
Boolean |
!!int |
Integer, Long, BigInteger |
!!float |
Double |
!!binary |
byte[] , String |
!!timestamp |
java.util.Date , java.sql.Date , java.sql.Timestamp |
!!omap , !!pairs |
List of Object[] |
!!set |
Set |
!!str |
String |
!!seq |
List |
!!map |
Map |
An example of loading and dumping Global tags can be found here.
Collections
Default implementations of collections are:
-
List:
ArrayList
-
Map:
LinkedHashMap
(the order is implicitly defined)
It is possible to define other default implementations. An example can be found here for List and here for Map
Merge
YAML has a "merge" specification: http://yaml.org/type/merge.html
SnakeYAML supports merging one or more mappings.
The syntax for a standard mapping is as follows:
- &item001 boolean: true integer: 3 - <<: *item001 integer: 7
The resulting YAML document will be a List of two Maps. The first one will map "boolean" to Boolean(true) and "integer" to Integer(3). The second will map "boolean" to Boolean(true) and "integer" to Integer(7) . Note that the updated values will overwrite the "merged" values.
In addition, a sequence of merges may be specified. In this case, the merged value is the first value found in the list of merged maps. For example:
- &item001 boolean: true integer: 7 float: 3.14 - &item002 boolean: false - <<: [ *item002, *item001 ]
The resulting list contains 3 maps. The first is {"boolean":Boolean(true), "integer":Integer(7), "float":3.14}. The second is {"boolean":Boolean(false)}. The third will be {"boolean":Boolean(false), "integer":Integer(7), "float":Float(3.14)}.
Note that resulting mapping will refer to the exact same object as the merged mapping does. In this example, there are 2 Booleans, 1 Integer and 1 Float.
Introduced in version 1.8 of SnakeYAML is the ability to merge mappings that are then converted into custom Java objects (as opposed to simple Map objects). A merged Java object is very similar, except that the type of object must be specified. An example, using a Weapon class with properties name, range and damage:
- !!Weapon &sword name: Sword range: 5 damage: 3 - !!Weapon <<: *sword name: Heavy Sword damage: 4
The resulting List will contain two Weapon objects. The first will be a "Sword" with range 5 and damage 3. The second will be a "Heavy Sword" with range 5 and damage 4.
The tags of the two merged mappings do not need to be the same. The following is also allowed:
- !!Weapon &shortsword name: Short Sword range: 5 damage: 3 - !!MagicWeapon <<: *shortsword name: Short Sword +1 bonus: 1
The resulting list contains a Weapon
named "Short Sword" and a MagicWeapon
named "Short Sword +1".
The Java types of the merged objects do not actually need to be the same, or even related:
- !!Person &the_queen name: Queen Elizabeth II - !!Ship <<: *the_queen designation: HMS
This document specifies a Person named "Queen Elizabeth II" and a Ship with designation "HMS" and name "Queen Elizabeth II": the ship named after the Queen. In this example, we assume that Person and Ship have no common ancestor except java.lang.Object, and so the properties are declared separately.
A few examples can be found here
Deviations from the specification
need to update this section
- rules for tabs in YAML are confusing. We are close, but not there yet. Perhaps both the spec and the parser should be fixed. Anyway, the best rule for tabs in YAML is to not use them at all.
- Byte order mark. The initial BOM is stripped, but BOMs inside the stream are considered as parts of the content. It can be fixed, but it's not really important now.
Empty plain scalars are not allowed if alias or tag is specified.This is done to prevent anomalities like[ !tag, value]
, which can be interpreted both as[ !<!tag,> value ]
and[ !<!tag> "", "value" ]
. The spec should be fixed.- Indentation of flow collections. The spec requires them to be indented more than their block parent node. Unfortunately this rule renders many intuitively correct constructs invalid, for instance,
block: { } # this is indentation violation according to the spec.
- ':' is not allowed for plain scalars in the flow mode.
{1:2}
is interpreted as{ 1 : 2 }
. - scalars 'x' and 'y' are not parsed as booleans but as strings. It is the same how it is done in other parsers and it is closer to how it is defined in the coming 1.2 specification
Updated