Need help with your JSON?

Try our JSON Formatter tool to automatically identify and fix syntax errors in your JSON. JSON Formatter tool

Groovy JSON Handling in Gradle Scripts

Gradle, a powerful and flexible build automation tool, uses Groovy or Kotlin DSL for writing build scripts. When working with complex build configurations, external data, or task inputs/outputs, you might encounter the need to handle JSON data directly within your build.gradle files. Groovy's excellent support for JSON makes this relatively straightforward.

Why Handle JSON in Gradle?

There are several scenarios where processing JSON within your Gradle script can be beneficial:

  • Configuration Management: Reading build settings, versions, or credentials from a JSON config file.
  • Task Inputs/Outputs: Parsing a JSON report generated by another tool or generating a JSON file as a task output.
  • External Data Integration: Processing data fetched from an external source or API (though typically this might happen in a separate script or plugin called by Gradle).
  • Dynamic Task Configuration: Using data from a JSON structure to dynamically define tasks, dependencies, or properties.

Groovy's Built-in JSON Capabilities

Groovy provides excellent support for JSON parsing and generation through thegroovy.json package. The two main classes you'll interact with are:

  • JsonSlurper: For parsing JSON strings or streams into Groovy data structures (Lists, Maps, etc.).
  • JsonOutput: For generating JSON strings from Groovy data structures.

These classes are usually available in the Gradle Groovy environment without needing explicit dependencies in your typical build.gradle file.

Examples in Gradle Scripts

1. Parsing JSON from a String

You can directly parse a JSON string defined within your script:

import groovy.json.JsonSlurper

def jsonString = '''
{
  "name": "MyProject",
  "version": "1.0.0",
  "settings": {
    "buildDir": "dist",
    "minify": true
  },
  "tags": ["gradle", "json", "example"]
}
'''

def jsonSlurper = new JsonSlurper()
def config = jsonSlurper.parseText(jsonString)

println "Project Name: ${config.name}" // Access using dot notation
println "Project Version: ${config['version']}" // Access using map notation
println "Build Directory: ${config.settings.buildDir}"
println "First tag: ${config.tags[0]}"

// You can use this data later in your build configuration
gradle.ext.projectName = config.name
gradle.ext.projectVersion = config.version

task printConfig {
    doLast {
        println "Config from extension: ${gradle.ext.projectName} - ${gradle.ext.projectVersion}"
    }
}

In this example, JsonSlurper().parseText() converts the JSON string into a Groovy object which behaves like nested Maps and Lists, allowing easy access using dot notation or map-style indexing.

2. Parsing JSON from a File

Reading configuration or data from an external JSON file is a common pattern. Let's assume you have a file named config.json in your project root:

// Example config.json content:
// {
//   "apiUrl": "https://api.example.com/v1",
//   "timeout": 5000,
//   "features": {
//     "darkMode": false,
//     "analytics": true
//   }
// }

import groovy.json.JsonSlurper

def configFile = file('config.json')

if (configFile.exists()) {
    def jsonSlurper = new JsonSlurper()
    def projectConfig = jsonSlurper.parse(configFile) // Use parse() for files

    // Store config data in Gradle extensions for use across the build script
    gradle.ext.apiUrl = projectConfig.apiUrl
    gradle.ext.timeout = projectConfig.timeout
    gradle.ext.features = projectConfig.features

    task checkFeatures {
        doLast {
            if (gradle.ext.features.analytics) {
                println "Analytics feature is enabled."
            } else {
                println "Analytics feature is disabled."
            }
            println "API URL is: ${gradle.ext.apiUrl}"
        }
    }

} else {
    println "WARNING: config.json not found. Using default settings."
    // Define default settings if the file is missing
    gradle.ext.apiUrl = "http://localhost:8080"
    gradle.ext.timeout = 10000
    gradle.ext.features = [darkMode: false, analytics: false]
}

Using jsonSlurper.parse(file) directly handles reading the file content and parsing it. It's good practice to check if the file exists before attempting to parse it.

3. Generating JSON Output

You might need to generate a JSON file as part of a build task, for example, a report or a configuration file for another tool.

import groovy.json.JsonOutput

task generateBuildInfoJson {
    def buildInfoFile = layout.buildDirectory.file('reports/buildInfo.json').get().asFile

    outputs.file buildInfoFile // Declare the output file

    doLast {
        def buildInfo = [
            projectName: rootProject.name,
            version: version, // Project version
            buildTime: new Date().format("yyyy-MM-dd'T'HH:mm:ssZ"),
            gradleVersion: gradle.gradleVersion,
            javaVersion: System.getProperty('java.version')
        ]

        // Convert Groovy map to JSON string
        def jsonOutputString = JsonOutput.toJson(buildInfo)

        // Optional: make the output pretty-printed
        def prettyJsonOutputString = JsonOutput.prettyPrint(jsonOutputString)

        // Ensure parent directories exist
        buildInfoFile.getParentFile().mkdirs()

        // Write JSON string to file
        buildInfoFile.text = prettyJsonOutputString

        println "Generated build info file: ${buildInfoFile.getAbsolutePath()}"
    }
}

Here, we create a Groovy Map containing the build information. JsonOutput.toJson() converts this Map into a JSON string. JsonOutput.prettyPrint() improves readability. The task declares its output file using outputs.file, which helps Gradle with task up-to-date checks.

4. Using JSON Data in Task Configuration

JSON data can dynamically influence how tasks behave.

import groovy.json.JsonSlurper

// Assume features.json exists with content like:
// {
//   "enabledFeatures": ["auth", "payment"],
//   "disabledFeatures": ["admin"]
// }

def featuresFile = file('features.json')
def featureConfig = [enabledFeatures: [], disabledFeatures: []] // Default

if (featuresFile.exists()) {
    try {
        featureConfig = new JsonSlurper().parse(featuresFile)
    } catch (Exception e) {
        println "WARNING: Failed to parse features.json: ${e.message}"
        // Use default config on error
    }
} else {
     println "WARNING: features.json not found. Using default features."
}

// Example: Configure a Copy task based on enabled features
task copyFeatureFiles(type: Copy) {
    from 'src/main/resources/features'
    into layout.buildDirectory.dir('features')

    // Include files only if the feature is enabled
    include { FileTreeElement details ->
        def fileName = details.name
        // Assuming file names are like 'auth.feature', 'payment.feature', 'admin.feature'
        def featureName = fileName.tokenize('.')[0]
        return featureConfig.enabledFeatures.contains(featureName)
    }
}

// Example: Configure a task property based on JSON
gradle.ext.featureToggles = featureConfig.enabledFeatures.collectEntries { [(it): true] } +
                           featureConfig.disabledFeatures.collectEntries { [(it): false] }

task showFeatureToggles {
    doLast {
        println "Current Feature Toggles: ${gradle.ext.featureToggles}"
    }
}

Here, the copyFeatureFiles task dynamically determines which files to include based on the content of features.json parsed during the configuration phase. We also create a map of feature toggles stored in gradle.ext, which can be used by other tasks or plugins.

Tips and Considerations

  • Configuration vs. Execution Phase: Most JSON parsing (especially for configuration) happens during Gradle's configuration phase (when the script is evaluated). If JSON processing depends on task outputs or needs to happen during task execution, wrap the logic inside doLast or doFirst blocks. Accessing files or performing actions that change state should generally be done in execution phase blocks.
  • Error Handling: Always consider what happens if the JSON file doesn't exist or is malformed. Use try-catch blocks for robustness, especially when parsing external files. Provide default values or informative warnings.
  • Large JSON Data: For very large JSON files, parsing them entirely into memory during the configuration phase might impact build performance. Consider processing them within a task's doLast block or using streaming parsers if necessary (though JsonSlurper is generally efficient for typical config sizes).
  • Dependencies: The groovy.json package is typically part of the Groovy distribution bundled with Gradle. You usually don't need to add extra dependencies for basic JSON handling.
  • Kotlin DSL: If you are using Kotlin DSL (build.gradle.kts), you would use libraries like Jackson or kotlinx.serialization for JSON handling, as Groovy's built-in capabilities are not directly available in the Kotlin environment.
  • File Paths: Use Gradle's built-in methods like file('path/to/file.json') or layout.projectDirectory.file('path/to/file.json').get().asFile for reliable path handling.

Conclusion

Groovy provides powerful and convenient ways to handle JSON data directly within your Gradle build scripts. Whether you need to read configuration, process task inputs, or generate outputs, JsonSlurper and JsonOutput offer simple and effective solutions. By leveraging these capabilities, you can create more dynamic, data-driven, and flexible build processes. Remember to consider the phase of execution (configuration vs. execution) and implement robust error handling for reliable scripts.

Need help with your JSON?

Try our JSON Formatter tool to automatically identify and fix syntax errors in your JSON. JSON Formatter tool