# Processing YAML

version 6.0.0-beta-1

Groovy has an optional `groovy-yaml` module which provides support for converting between Groovy objects and YAML. The classes dedicated to YAML serialization and parsing are found in the `groovy.yaml` package.

## 1. YamlSlurper

`YamlSlurper` is a class that parses YAML text or reader content into Groovy data structures (objects) such as maps, lists and primitive types like `Integer`, `Double`, `Boolean` and `String`.

The class comes with a bunch of overloaded `parse` methods plus some special methods such as `parseText` and others. For the next example we will use the `parseText` method. It parses a YAML `String` and recursively converts it to a list or map of objects. The other `parse*` methods are similar in that they return a YAML `String` but for different parameter types.

            def ys = new YamlSlurper()
            def yaml = ys.parseText '''
    language: groovy
    sudo: required
    dist: trusty

    matrix:
      include:
        - jdk: openjdk10
        - jdk: oraclejdk9
        - jdk: oraclejdk8

    before_script:
      - |
        unset _JAVA_OPTIONS

            '''

            assert 'groovy' == yaml.language
            assert 'required' == yaml.sudo
            assert 'trusty' == yaml.dist
            assert ['openjdk10', 'oraclejdk9', 'oraclejdk8'] ==  yaml.matrix.include.jdk
            assert ['unset _JAVA_OPTIONS'] == yaml.before_script*.trim()

Notice the result is a plain map and can be handled like a normal Groovy object instance. `YamlSlurper` parses the given YAML as defined by the YAML Ain’t Markup Language (YAML™).

As `YamlSlurper` is returning pure Groovy object instances without any special YAML classes in the back, its usage is transparent. In fact, `YamlSlurper` results conform to GPath expressions. GPath is a powerful expression language that is supported by multiple slurpers for different data formats (`XmlSlurper` for XML being one example).

| For more details please have a look at the section on GPath expressions.

The following table gives an overview of the YAML types and the corresponding Groovy data types:

YAML | Groovy
--- | ---
string | `java.lang.String`
number | `java.lang.BigDecimal` or `java.lang.Integer`
object | `java.util.LinkedHashMap`
array | `java.util.ArrayList`
true | `true`
false | `false`
null | `null`
date/time | `java.lang.String` (see the note below)

| Whenever a value in YAML is `null`, `YamlSlurper` supplements it with the Groovy `null` value. This is in contrast to other YAML parsers that represent a `null` value with a library-provided singleton object.

| For the untyped `parse`/`parseText` API, YAML date and time values are returned as `String`. `YamlSlurper` routes the untyped path through a YAML-to-JSON conversion, and JSON has no native temporal types. Use the typed `parseAs`/`parseTextAs` API (see Typed date and time values) for `java.time.LocalDate`, `java.time.LocalTime`, `java.time.LocalDateTime`, and `java.time.OffsetDateTime` fidelity.

### 1.1. Typed parsing

`YamlSlurper` can parse YAML directly into typed objects using Jackson databinding. Standard Jackson annotations such as `@JsonProperty` and `@JsonFormat` are supported for property mapping and type conversion:

    static class ServerConfig {
        String host
        int port
    }

            def config = new YamlSlurper().parseTextAs(ServerConfig, '''\
    host: localhost
    port: 8080
    ''')
            assert config.host == 'localhost'
            assert config.port == 8080

| For simple cases, Groovy’s `as` coercion also works with the untyped result: `def config = new YamlSlurper().parseText(yaml) as ServerConfig`. The `parseTextAs` method uses Jackson databinding, which supports richer annotation-driven mapping via `@JsonProperty`, `@JsonFormat`, etc.

### 1.2. Typed date and time values

When a target type declares `java.time.*` fields, the typed `parseAs`/`parseTextAs` API and `YamlBuilder.toYaml` round-trip YAML temporal values with full fidelity, including the original zone offset for `OffsetDateTime`:

    static class Event {
        java.time.OffsetDateTime created
        java.time.LocalDate effective
        java.time.LocalTime windowStart
        java.time.LocalDateTime updated
        String name
    }

    def original = new Event(
            created: java.time.OffsetDateTime.parse('1979-05-27T07:32:00-08:00'),
            effective: java.time.LocalDate.of(1979, 5, 27),
            windowStart: java.time.LocalTime.of(7, 32, 0),
            updated: java.time.LocalDateTime.of(1979, 5, 27, 7, 32, 0),
            name: 'demo')

    def yaml = YamlBuilder.toYaml(original)
    def parsed = new YamlSlurper().parseTextAs(Event, yaml)

    assert parsed.created == original.created            // OffsetDateTime, non-UTC offset preserved
    assert parsed.effective == original.effective        // LocalDate
    assert parsed.windowStart == original.windowStart    // LocalTime
    assert parsed.updated == original.updated            // LocalDateTime
    assert parsed.name == original.name

### 1.3. Builders

Another way to create YAML from Groovy is to use `YamlBuilder`. The builder provide a DSL which allows to formulate an object graph which is then converted to YAML.

            def builder = new YamlBuilder()
            builder.records {
                car {
                    name 'HSV Maloo'
                    make 'Holden'
                    year 2006
                    country 'Australia'
                    homepage new URL('http://example.org')
                    record {
                        type 'speed'
                        description 'production pickup truck with speed of 271kph'
                    }
                }
            }

            assert builder.toString() == '''---
    records:
      car:
        name: "HSV Maloo"
        make: "Holden"
        year: 2006
        country: "Australia"
        homepage: "http://example.org"
        record:
          type: "speed"
          description: "production pickup truck with speed of 271kph"
    '''

### 1.4. Typed writing

`YamlBuilder.toYaml(object)` serializes a typed object directly to YAML using Jackson databinding:

    static class ServerConfig {
        String host
        int port
    }

    @Test
    void testToYaml() {
        def config = new ServerConfig(host: 'localhost', port: 8080)
        def yaml = YamlBuilder.toYaml(config)
        assert yaml.contains('host:')
        assert yaml.contains('localhost')
        assert yaml.contains('port:')
        assert yaml.contains('8080')
    }