# Processing TOML

version 6.0.0-beta-1

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

## 1. TomlSlurper

`TomlSlurper` is a class that parses TOML 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 TOML `String` and recursively converts it to a list or map of objects. The other `parse*` methods are similar in that they return a TOML `String` but for different parameter types.

            def ts = new TomlSlurper()
            def toml = ts.parseText '''
    language = "groovy"
    sudo = "required"
    dist = "trusty"
    before_script = [ "unset _JAVA_OPTIONS\\n\\n    \\n" ]

    [[matrix.include]]
    jdk = "openjdk10"

    [[matrix.include]]
    jdk = "oraclejdk9"

    [[matrix.include]]
    jdk = "oraclejdk8"
    '''

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

Notice the result is a plain map and can be handled like a normal Groovy object instance. `TomlSlurper` parses the given TOML as defined by the Tom’s Obvious, Minimal Language.

As `TomlSlurper` is returning pure Groovy object instances without any special TOML classes in the back, its usage is transparent. In fact, `TomlSlurper` 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 TOML types and the corresponding Groovy data types:

TOML | 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`
offset/local date-time, local date, local time | `java.lang.String` (see the note below)

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

| For the untyped `parse`/`parseText` API, TOML date and time values are returned as `String`. This is a limitation of the underlying Jackson TOML support, which only surfaces `java.time.*` types when a target type is supplied. 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

`TomlSlurper` can parse TOML 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 TomlSlurper().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 TomlSlurper().parseText(toml) 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 `TomlBuilder.toToml` round-trip TOML 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 toml = groovy.toml.TomlBuilder.toToml(original)
    def parsed = new TomlSlurper().parseTextAs(Event, toml)

    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 TOML from Groovy is to use `TomlBuilder`. The builder provide a DSL which allows to formulate an object graph which is then converted to TOML.

            def builder = new TomlBuilder()
            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'
    records.car.make = 'Holden'
    records.car.year = 2006
    records.car.country = 'Australia'
    records.car.homepage = 'http://example.org'
    records.car.record.type = 'speed'
    records.car.record.description = 'production pickup truck with speed of 271kph'
    '''

### 1.4. Typed writing

`TomlBuilder.toToml(object)` serializes a typed object directly to TOML using Jackson databinding:

    static class ServerConfig {
        String host
        int port
    }

    @Test
    void testToToml() {
        def config = new ServerConfig(host: 'localhost', port: 8080)
        def toml = TomlBuilder.toToml(config)
        assert toml.contains('host')
        assert toml.contains('localhost')
        assert toml.contains('port')
        assert toml.contains('8080')
    }