# Processing JSON

version 6.0.0-RC-1

Groovy comes with integrated support for converting between Groovy objects and JSON. The classes dedicated to JSON serialization and parsing are found in the `groovy.json` package.

## 1. JsonSlurper

`JsonSlurper` is a class that parses JSON 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`, `parseFile` and others. For the next example we will use the `parseText` method. It parses a JSON `String` and recursively converts it to a list or map of objects. The other `parse*` methods are similar in that they return a JSON `String` but for different parameter types.

    def jsonSlurper = new JsonSlurper()
    def object = jsonSlurper.parseText('{ "name": "John Doe" } /* some comment */')

    assert object instanceof Map
    assert object.name == 'John Doe'

Notice the result is a plain map and can be handled like a normal Groovy object instance. `JsonSlurper` parses the given JSON as defined by the ECMA-404 JSON Interchange Standard plus support for JavaScript comments and dates.

In addition to maps `JsonSlurper` supports JSON arrays which are converted to lists.

    def jsonSlurper = new JsonSlurper()
    def object = jsonSlurper.parseText('{ "myList": [4, 8, 15, 16, 23, 42] }')

    assert object instanceof Map
    assert object.myList instanceof List
    assert object.myList == [4, 8, 15, 16, 23, 42]

The JSON standard supports the following primitive data types: string, number, object, `true`, `false` and `null`. `JsonSlurper` converts these JSON types into corresponding Groovy types.

    def jsonSlurper = new JsonSlurper()
    def object = jsonSlurper.parseText '''
        { "simple": 123,
          "fraction": 123.66,
          "exponential": 123e12
        }'''

    assert object instanceof Map
    assert object.simple.class == Integer
    assert object.fraction.class == BigDecimal
    assert object.exponential.class == BigDecimal

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

JSON | 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 | `java.util.Date` based on the `yyyy-MM-dd’T’HH:mm:ssZ` date format

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

### 1.1. Parser Variants

`JsonSlurper` comes with a couple of parser implementations. Each parser fits different requirements, it could well be that for certain scenarios the `JsonSlurper` default parser is not the best bet for all situations. Here is an overview of the shipped parser implementations:

- The `JsonParserCharArray` parser basically takes a JSON string and operates on the underlying character array. During value conversion it copies character sub-arrays (a mechanism known as "chopping") and operates on them.

- The `JsonFastParser` is a special variant of the `JsonParserCharArray` and is the fastest parser. However, it is not the default parser for a reason. `JsonFastParser` is a so-called index-overlay parser. During parsing of the given JSON `String` it tries as hard as possible to avoid creating new char arrays or `String` instances. It keeps pointers to the underlying original character array only. In addition, it defers object creation as late as possible. If parsed maps are put into long-term caches care must be taken as the map objects might not be created and still consist of pointer to the original char buffer only. However, `JsonFastParser` comes with a special chop mode which dices up the char buffer early to keep a small copy of the original buffer. Recommendation is to use the `JsonFastParser` for JSON buffers under 2MB and keeping the long-term cache restriction in mind.

- The `JsonParserLax` is a special variant of the `JsonParserCharArray` parser. It has similar performance characteristics as `JsonFastParser` but differs in that it isn’t exclusively relying on the ECMA-404 JSON grammar. For example it allows for comments, no quote strings etc.

- The `JsonParserUsingCharacterSource` is a special parser for very large files. It uses a technique called "character windowing" to parse large JSON files (large means files over 2MB size in this case) with constant performance characteristics.

The default parser implementation for `JsonSlurper` is `JsonParserCharArray`. The `JsonParserType` enumeration contains constants for the parser implementations described above:

Implementation | Constant
--- | ---
`JsonParserCharArray` | `JsonParserType#CHAR_BUFFER`
`JsonFastParser` | `JsonParserType#INDEX_OVERLAY`
`JsonParserLax` | `JsonParserType#LAX`
`JsonParserUsingCharacterSource` | `JsonParserType#CHARACTER_SOURCE`

Changing the parser implementation is as easy as setting the `JsonParserType` with a call to `JsonSlurper#setType()`.

    def jsonSlurper = new JsonSlurper(type: JsonParserType.INDEX_OVERLAY)
    def object = jsonSlurper.parseText('{ "myList": [4, 8, 15, 16, 23, 42] }')

    assert object instanceof Map
    assert object.myList instanceof List
    assert object.myList == [4, 8, 15, 16, 23, 42]

### 1.2. Date-like strings

`JsonParserType#INDEX_OVERLAY` and `JsonParserType#LAX` return a `java.util.Date` for a string in a full ISO-8601 or JSON-date form, rather than the `String` the other two parser types return for the same document. It is on by default and `JsonSlurper#setCheckDates(false)` turns it off:

    def json = '{"when":"2026-09-04T10:00:00.000Z"}'

    assert new JsonSlurper(type: JsonParserType.INDEX_OVERLAY)
            .parseText(json).when instanceof Date

    assert new JsonSlurper(type: JsonParserType.INDEX_OVERLAY)
            .setCheckDates(false)
            .parseText(json).when instanceof String

    assert new JsonSlurper(type: JsonParserType.CHAR_BUFFER)
            .parseText(json).when instanceof String        // never converts

A date carrying no time, such as `"2026-09-04"`, is left as a `String` by all four.

`JsonSlurper#setDateHandling(JsonDateHandling)` says which type to produce, rather than only whether to convert. A `Date` is an instant, as is an `Instant`, so neither keeps an offset the document carried; `OFFSET_DATE_TIME` does:

    def offsetJson = '{"when":"2026-09-04T10:00:00+10:00"}'
    def slurp = { JsonDateHandling h ->
        new JsonSlurper(type: JsonParserType.INDEX_OVERLAY)
                .setDateHandling(h)
                .parseText(offsetJson).when
    }

    assert slurp(JsonDateHandling.STRING) instanceof String
    assert slurp(JsonDateHandling.UTIL_DATE) instanceof Date              // the default
    assert slurp(JsonDateHandling.INSTANT) instanceof java.time.Instant

    // only OFFSET_DATE_TIME keeps the offset the document carried
    def odt = slurp(JsonDateHandling.OFFSET_DATE_TIME)
    assert odt instanceof java.time.OffsetDateTime
    assert odt.offset == java.time.ZoneOffset.ofHours(10)

`setCheckDates(boolean)` remains, mapping onto `UTIL_DATE` and `STRING`, and is deprecated. Once `setDateHandling` has been called on a slurper the boolean has no further effect on it, whichever order the two are used in, so a choice made through the wider option is not undone by the narrower one.

## 2. JsonOutput

`JsonOutput` is responsible for serializing Groovy objects into JSON strings. It can be seen as companion object to JsonSlurper, being a JSON parser.

`JsonOutput` comes with overloaded, static `toJson` methods. Each `toJson` implementation takes a different parameter type. The static methods can either be used directly or by importing the methods with a static import statement.

The result of a `toJson` call is a `String` containing the JSON code.

    def json = JsonOutput.toJson([name: 'John Doe', age: 42])

    assert json == '{"name":"John Doe","age":42}'

`JsonOutput` does not only support primitive, maps or list data types to be serialized to JSON, it goes further and even has support for serializing POGOs, that is, plain-old Groovy objects.

    class Person { String name }

    def json = JsonOutput.toJson([ new Person(name: 'John'), new Person(name: 'Max') ])

    assert json == '[{"name":"John"},{"name":"Max"}]'

### 2.1. Customizing Output

If you need control over the serialized output you can use a `JsonGenerator`. The `JsonGenerator.Options` builder can be used to create a customized generator. One or more options can be set on this builder in order to alter the resulting output. When you are done setting the options simply call the `build()` method in order to get a fully configured instance that will generate output based on the options selected.

    class Person {
        String name
        String title
        int age
        String password
        Date dob
        URL favoriteUrl
    }

    Person person = new Person(name: 'John', title: null, age: 21, password: 'secret',
                                dob: Date.parse('yyyy-MM-dd', '1984-12-15'),
                                favoriteUrl: new URL('http://groovy-lang.org/'))

    def generator = new JsonGenerator.Options()
        .excludeNulls()
        .dateFormat('yyyy@MM')
        .excludeFieldsByName('age', 'password')
        .excludeFieldsByType(URL)
        .build()

    assert generator.toJson(person) == '{"name":"John","dob":"1984@12"}'

A closure can be used to transform a type. These closure converters are registered for a given type and will be called any time that type or a subtype is encountered. The first parameter to the closure is an object matching the type for which the converter is registered and this parameter is required. The closure may take an optional second `String` parameter and this will be set to the key name if one is available.

    class Person {
        String name
        URL favoriteUrl
    }

    Person person = new Person(name: 'John', favoriteUrl: new URL('http://groovy-lang.org/json.html#_jsonoutput'))

    def generator = new JsonGenerator.Options()
        .addConverter(URL) { URL u, String key ->
            if (key == 'favoriteUrl') {
                u.getHost()
            } else {
                u
            }
        }
        .build()

    assert generator.toJson(person) == '{"name":"John","favoriteUrl":"groovy-lang.org"}'

    // No key available when generating a JSON Array
    def list = [new URL('http://groovy-lang.org/json.html#_jsonoutput')]
    assert generator.toJson(list) == '["http://groovy-lang.org/json.html#_jsonoutput"]'

    // First parameter to the converter must match the type for which it is registered
    shouldFail(IllegalArgumentException) {
        new JsonGenerator.Options()
            .addConverter(Date) { Calendar cal -> }
    }

#### 2.1.1. Formatted Output

As we saw in previous examples, the JSON output is not pretty printed per default. However, the `prettyPrint` method in `JsonOutput` comes to rescue for this task.

    def json = JsonOutput.toJson([name: 'John Doe', age: 42])

    assert json == '{"name":"John Doe","age":42}'

    assert JsonOutput.prettyPrint(json) == '''\
    {
        "name": "John Doe",
        "age": 42
    }'''.stripIndent()

`prettyPrint` takes a `String` as single parameter; therefore, it can be applied on arbitrary JSON `String` instances, not only the result of `JsonOutput.toJson`.

## 3. Typed JSON Parsing

Since `JsonSlurper` parses JSON values into their natural JVM types (`Integer`, `BigDecimal`, `Boolean`, `List`, `Map`), Groovy’s built-in `as` coercion works well for converting parsed JSON directly into typed objects — no additional dependencies are required:

    static class ServerConfig {
        String host
        int port
        boolean debug
    }

    static class AppConfig {
        String name
        ServerConfig server
    }

    enum Color { RED, GREEN, BLUE }

    static class Item { String name; Color color }

    def json = '{"host":"localhost","port":8080,"debug":true}'
    def config = new JsonSlurper().parseText(json) as ServerConfig

    assert config instanceof ServerConfig
    assert config.host == 'localhost'
    assert config.port == 8080
    assert config.debug == true

Nested objects are automatically coerced:

    def json = '{"name":"myapp","server":{"host":"localhost","port":9090,"debug":false}}'
    def config = new JsonSlurper().parseText(json) as AppConfig

    assert config.name == 'myapp'
    assert config.server instanceof ServerConfig
    assert config.server.host == 'localhost'
    assert config.server.port == 9090

Enum values are also handled:

    def item = new JsonSlurper().parseText('{"name":"widget","color":"GREEN"}') as Item
    assert item.color == Color.GREEN

This covers most common use cases. For advanced scenarios — such as typed element collections (`List<Item>`), date/time parsing, or annotation-driven property mapping (`@JsonProperty`, `@JsonFormat`) — use `jackson-databind` directly:

    @Grab('com.fasterxml.jackson.core:jackson-databind')
    import com.fasterxml.jackson.databind.ObjectMapper

    def config = new ObjectMapper().readValue(jsonString, ServerConfig)

### 3.1. Builders

Another way to create JSON from Groovy is to use `JsonBuilder` or `StreamingJsonBuilder`. Both builders provide a DSL which allows to formulate an object graph which is then converted to JSON.

| For more details on builders, have a look at the builders chapter which covers both JsonBuilder and StreamingJsonBuilder.