Groovy JSON parsing with JsonSlurper. 10+ examples covering parseText, parse URL, nested fields, arrays, lazy parsing, and error handling on Groovy 5.x.
“JSON is the duct tape of the internet. Groovy’s JsonSlurper is the scissors that makes it easy to cut through.”
Douglas Crockford, JavaScript: The Good Parts
Last Updated: March 2026 | Tested on: Groovy 5.x, Java 17+ | Difficulty: Beginner to Intermediate | Reading Time: 22 minutes
If you work with web APIs, configuration files, or any modern data exchange format, you work with JSON. And if you’re writing Groovy, you have one of the cleanest JSON parsing experiences available in any JVM language – thanks to JsonSlurper.
This Groovy JSON parsing guide walks you through what you need for working with JsonSlurper. We’ll cover parsing strings, files, and URLs, accessing nested fields, handling arrays, type coercion, lazy vs non-lazy parsing, and reliable error handling – all with tested examples and real output.
Once you’ve mastered parsing, head over to our Groovy JSON Output with JsonBuilder guide to learn how to generate JSON. And if you want to consume REST APIs end-to-end, check out Groovy REST API Consumption.
Table of Contents
What Is JsonSlurper?
groovy.json.JsonSlurper is Groovy’s built-in JSON parser. It takes a JSON string (or stream, or URL) and converts it into native Groovy data structures – maps for JSON objects and lists for JSON arrays. No third-party libraries needed. No annotations. No POJOs. Just parse and go.
According to the official Groovy JSON documentation, JsonSlurper parses text or reader content into Groovy data structures such as maps, lists, and primitive types like Integer, Long, BigDecimal, and String.
Key Points:
- Part of the
groovy.jsonpackage – no external dependencies - Parses JSON objects into
Mapinstances and JSON arrays intoListinstances - Supports multiple parser types: INDEX_OVERLAY (default), LAX, CHARACTER_SOURCE, and CHAR_BUFFER
- Handles JSON numbers as
Integer,Long, orBigDecimaldepending on size - JSON booleans become Groovy
Booleanvalues andnullbecomes Groovynull - Thread-safe – you can reuse a single instance across threads
Why Use JsonSlurper for JSON Parsing?
You might wonder why you’d pick JsonSlurper over Jackson, Gson, or other popular JSON libraries. Here’s why:
- Zero setup – it ships with Groovy, no Gradle or Maven dependency needed
- Dynamic access – parsed JSON becomes maps and lists, so you use dot notation and array indexing directly
- No POJOs required – you don’t need to define classes that mirror the JSON structure
- Groovy-native types – numbers, booleans, and strings map directly to Groovy types
- Multiple parser modes – pick the right parser for your performance and correctness needs
- Great for scripting – perfect for quick scripts, CI/CD pipelines, and Jenkinsfiles
That said, if you need schema validation, custom deserializers, or streaming parsing for very large files, Jackson is still a solid choice. But for 90% of Groovy JSON work, JsonSlurper is all you need.
Basic Syntax
The basic pattern for Groovy JSON parsing is always the same: create a JsonSlurper instance, call a parse method, and work with the result.
JsonSlurper Basic Syntax
import groovy.json.JsonSlurper
def slurper = new JsonSlurper()
// Parse a JSON string
def result = slurper.parseText('{"name": "Alice", "age": 30}')
// Access fields with dot notation
println result.name // Alice
println result.age // 30
The main parse methods available are:
| Method | Input Source | Use Case |
|---|---|---|
parseText(String) | JSON string | Inline JSON, API responses stored as strings |
parse(Reader) | Reader | Files, streams |
parse(InputStream) | Input stream | HTTP connections, resources |
parse(File) | File object | Local JSON files |
parse(URL) | URL object | Remote JSON endpoints |
parse(byte[]) | Byte array | Binary data, network buffers |
10+ Practical Examples
Example 1: Parse a Simple JSON Object
What we’re doing: Parsing a basic JSON object string and accessing its properties using dot notation.
Example 1: Simple JSON Object
import groovy.json.JsonSlurper
def slurper = new JsonSlurper()
def json = slurper.parseText('''
{
"name": "Alice",
"age": 30,
"active": true,
"email": "alice@example.com"
}
''')
println json.name // Dot notation
println json['age'] // Bracket notation
println json.active
println json.email
println json.getClass().name
Output
Alice 30 true alice@example.com org.apache.groovy.json.internal.LazyMap
What happened here: parseText() converted the JSON string into a LazyMap. You can access fields using dot notation (like a POJO) or bracket notation (like a map). The JSON boolean true became a Groovy Boolean, and the number 30 became an Integer.
Example 2: Parse Nested JSON Objects
What we’re doing: Navigating deeply nested JSON structures using chained dot notation.
Example 2: Nested JSON
import groovy.json.JsonSlurper
def slurper = new JsonSlurper()
def json = slurper.parseText('''
{
"user": {
"name": "Bob",
"address": {
"street": "123 Main St",
"city": "Springfield",
"state": "IL",
"zip": "62701"
},
"preferences": {
"theme": "dark",
"notifications": {
"email": true,
"sms": false,
"push": true
}
}
}
}
''')
println json.user.name
println json.user.address.city
println json.user.address.zip
println json.user.preferences.theme
println json.user.preferences.notifications.email
println json.user.preferences.notifications.sms
Output
Bob Springfield 62701 dark true false
What happened here: Each nested JSON object became a nested map. You simply chain dot notation to drill into the structure – json.user.preferences.notifications.email. No need for get() calls or casting. Groovy’s dynamic nature makes this incredibly readable.
Example 3: Parse JSON Arrays
What we’re doing: Parsing JSON arrays and iterating through them with Groovy collection methods.
Example 3: JSON Arrays
import groovy.json.JsonSlurper
def slurper = new JsonSlurper()
// Top-level JSON array
def colors = slurper.parseText('["red", "green", "blue", "yellow"]')
println "Colors: ${colors}"
println "First: ${colors[0]}"
println "Last: ${colors[-1]}"
println "Count: ${colors.size()}"
// Array of objects
def users = slurper.parseText('''
[
{"name": "Alice", "role": "admin"},
{"name": "Bob", "role": "editor"},
{"name": "Charlie", "role": "viewer"}
]
''')
users.each { user ->
println "${user.name} is a ${user.role}"
}
// Extract just the names
def names = users.collect { it.name }
println "Names: ${names}"
Output
Colors: [red, green, blue, yellow] First: red Last: yellow Count: 4 Alice is a admin Bob is a editor Charlie is a viewer Names: [Alice, Bob, Charlie]
What happened here: A top-level JSON array becomes a Groovy List. When the array contains objects, each object becomes a map. You can use all the Groovy collection methods – each(), collect(), find(), findAll() – directly on the parsed result.
Example 4: Accessing Nested Arrays
What we’re doing: Working with JSON that contains arrays nested inside objects.
Example 4: Nested Arrays
import groovy.json.JsonSlurper
def slurper = new JsonSlurper()
def json = slurper.parseText('''
{
"company": "TechCorp",
"departments": [
{
"name": "Engineering",
"employees": ["Alice", "Bob", "Charlie"]
},
{
"name": "Marketing",
"employees": ["Diana", "Eve"]
},
{
"name": "Sales",
"employees": ["Frank", "Grace", "Heidi", "Ivan"]
}
]
}
''')
println "Company: ${json.company}"
println "Departments: ${json.departments.size()}"
// Access specific department
println "First dept: ${json.departments[0].name}"
println "Eng team: ${json.departments[0].employees}"
// Spread operator to get all department names
println "All depts: ${json.departments*.name}"
// Flatten all employees
def allEmployees = json.departments.collectMany { it.employees }
println "All staff: ${allEmployees}"
println "Headcount: ${allEmployees.size()}"
Output
Company: TechCorp Departments: 3 First dept: Engineering Eng team: [Alice, Bob, Charlie] All depts: [Engineering, Marketing, Sales] All staff: [Alice, Bob, Charlie, Diana, Eve, Frank, Grace, Heidi, Ivan] Headcount: 9
What happened here: The spread operator (*.) extracts a property from each element in a list. The collectMany() method flattens nested lists into one – perfect for gathering all items from multiple nested arrays.
Example 5: Type Coercion After Parsing
What we’re doing: Checking and coercing the types that JsonSlurper produces from different JSON value types.
Example 5: Type Coercion
import groovy.json.JsonSlurper
def slurper = new JsonSlurper()
def json = slurper.parseText('''
{
"intValue": 42,
"longValue": 9999999999,
"decimalValue": 3.14159,
"boolValue": true,
"nullValue": null,
"stringValue": "hello",
"stringNumber": "42"
}
''')
println "int: ${json.intValue} -> ${json.intValue.getClass().name}"
println "long: ${json.longValue} -> ${json.longValue.getClass().name}"
println "decimal: ${json.decimalValue} -> ${json.decimalValue.getClass().name}"
println "bool: ${json.boolValue} -> ${json.boolValue.getClass().name}"
println "null: ${json.nullValue} -> ${json.nullValue == null}"
println "string: ${json.stringValue} -> ${json.stringValue.getClass().name}"
// Convert string to integer manually
def num = json.stringNumber as Integer
println "coerced: ${num} -> ${num.getClass().name}"
// Use Groovy's safe navigation for potentially null fields
println "safe: ${json.nullValue?.toUpperCase()}"
println "missing: ${json.nonExistent}"
Output
int: 42 -> java.lang.Integer long: 9999999999 -> java.lang.Long decimal: 3.14159 -> java.math.BigDecimal bool: true -> java.lang.Boolean null: null -> true string: hello -> java.lang.String coerced: 42 -> java.lang.Integer safe: null missing: null
What happened here: JsonSlurper automatically picks the right type. Small integers become Integer, large ones become Long, and decimals become BigDecimal (not Double – so you get exact precision). Accessing a missing key returns null rather than throwing an exception.
Example 6: Parse JSON from a File
What we’re doing: Reading and parsing a JSON file from the local filesystem.
Example 6: Parse JSON File
import groovy.json.JsonSlurper
// Create a sample JSON file first
def jsonContent = '''
{
"database": {
"host": "localhost",
"port": 5432,
"name": "myapp_db",
"credentials": {
"username": "admin",
"password": "secret123"
}
}
}
'''
def tempFile = File.createTempFile('config', '.json')
tempFile.text = jsonContent
tempFile.deleteOnExit()
// Parse the file
def slurper = new JsonSlurper()
def config = slurper.parse(tempFile)
println "Host: ${config.database.host}"
println "Port: ${config.database.port}"
println "DB: ${config.database.name}"
println "User: ${config.database.credentials.username}"
// You can also parse using a Reader
def config2 = slurper.parse(new FileReader(tempFile))
println "Via Reader - Host: ${config2.database.host}"
Output
Host: localhost Port: 5432 DB: myapp_db User: admin Via Reader - Host: localhost
What happened here: The parse(File) method reads and parses in one call. You don’t need to read the file content separately. You can also use parse(Reader) if you need more control over encoding or buffering.
Example 7: Parse JSON from a URL
What we’re doing: Fetching and parsing JSON directly from a URL (useful for simple API calls).
Example 7: Parse JSON from URL
import groovy.json.JsonSlurper
def slurper = new JsonSlurper()
// Parse directly from a URL
// def result = slurper.parse(new URL('https://api.example.com/data'))
// Simulating a URL parse with parseText for demonstration
def json = slurper.parseText('''
{
"userId": 1,
"id": 1,
"title": "Sample Todo",
"completed": false
}
''')
println "ID: ${json.id}"
println "Title: ${json.title}"
println "Completed: ${json.completed}"
// Alternative: read URL content first, then parse
// def text = new URL('https://api.example.com/data').text
// def result = slurper.parseText(text)
// Using URL.text with JsonSlurper (the most Groovy way)
// def todo = slurper.parseText('https://jsonplaceholder.typicode.com/todos/1'.toURL().text)
// println todo.title
Output
ID: 1 Title: Sample Todo Completed: false
What happened here: JsonSlurper can parse directly from a URL object. Alternatively, you can use Groovy’s URL.text property to fetch the content first, then pass it to parseText(). For more advanced HTTP handling, see our Groovy REST API Consumption guide.
Example 8: Filtering and Searching Parsed JSON
What we’re doing: Using Groovy collection methods to filter, search, and transform parsed JSON data.
Example 8: Filter and Search JSON
import groovy.json.JsonSlurper
def slurper = new JsonSlurper()
def products = slurper.parseText('''
[
{"name": "Laptop", "price": 999.99, "category": "electronics", "inStock": true},
{"name": "Book", "price": 14.99, "category": "education", "inStock": true},
{"name": "Phone", "price": 699.99, "category": "electronics", "inStock": false},
{"name": "Tablet", "price": 449.99, "category": "electronics", "inStock": true},
{"name": "Notebook", "price": 4.99, "category": "education", "inStock": true},
{"name": "Monitor", "price": 349.99, "category": "electronics", "inStock": false}
]
''')
// Find all electronics in stock
def available = products.findAll { it.category == 'electronics' && it.inStock }
println "Available electronics:"
available.each { println " ${it.name} - \$${it.price}" }
// Find the cheapest product
def cheapest = products.min { it.price }
println "\nCheapest: ${cheapest.name} (\$${cheapest.price})"
// Total value of in-stock items
def totalValue = products.findAll { it.inStock }.sum { it.price }
println "In-stock value: \$${totalValue}"
// Group by category
def grouped = products.groupBy { it.category }
grouped.each { category, items ->
println "\n${category}: ${items*.name}"
}
Output
Available electronics: Laptop - $999.99 Tablet - $449.99 Cheapest: Notebook ($4.99) In-stock value: $1469.96 electronics: [Laptop, Phone, Tablet, Monitor] education: [Book, Notebook]
What happened here: Once JSON is parsed into maps and lists, you have the full power of Groovy’s collection methods at your disposal. findAll() filters, min() finds the smallest by a given criteria, sum() aggregates, and groupBy() categorizes. This is one of the biggest advantages of Groovy’s approach to JSON.
Example 9: Parse JSON with Special Characters and Unicode
What we’re doing: Handling JSON that contains escape sequences, Unicode characters, and special strings.
Example 9: Special Characters
import groovy.json.JsonSlurper
def slurper = new JsonSlurper()
def json = slurper.parseText('''
{
"escaped_quotes": "She said \\"hello\\"",
"newline": "line1\\nline2",
"tab": "col1\\tcol2",
"unicode": "caf\\u00e9",
"emoji": "Hello \\ud83d\\ude00",
"backslash": "C:\\\\Users\\\\test",
"empty": "",
"spaces": " trimmed "
}
''')
println "Quotes: ${json.escaped_quotes}"
println "Newline: ${json.newline}"
println "Tab: ${json.tab}"
println "Unicode: ${json.unicode}"
println "Emoji: ${json.emoji}"
println "Backslash: ${json.backslash}"
println "Empty: '${json.empty}'"
println "Spaces: '${json.spaces}'"
println "Trimmed: '${json.spaces.trim()}'"
Output
Quotes: She said "hello" Newline: line1 line2 Tab: col1 col2 Unicode: café Emoji: Hello 😀 Backslash: C:\Users\test Empty: '' Spaces: ' trimmed ' Trimmed: 'trimmed'
What happened here: JsonSlurper correctly handles all JSON escape sequences including \", \n, \t, \\, and \uXXXX Unicode escapes. The parsed strings are regular Groovy strings, so you can apply any string methods like trim() or toUpperCase().
Example 10: Error Handling During JSON Parsing
What we’re doing: Gracefully handling malformed JSON and missing fields.
Example 10: Error Handling
import groovy.json.JsonSlurper
import groovy.json.JsonException
def slurper = new JsonSlurper()
// Handle malformed JSON
def badJsonSamples = [
'{"name": "Alice", "age": }', // Missing value
'{"name": "Alice" "age": 30}', // Missing comma
'{name: "Alice"}', // Unquoted key (invalid in strict mode)
'not json at all', // Not JSON
'' // Empty string
]
badJsonSamples.each { sample ->
try {
def result = slurper.parseText(sample)
println "Parsed: ${result}"
} catch (JsonException e) {
println "JsonException: ${e.message.take(60)}..."
} catch (Exception e) {
println "${e.class.simpleName}: ${e.message?.take(60)}..."
}
}
// Safe field access with defaults
println "\n--- Safe field access ---"
def json = slurper.parseText('{"name": "Alice"}')
println "Name: ${json.name ?: 'Unknown'}"
println "Age: ${json.age ?: 'Not specified'}"
println "Email: ${json.email ?: 'N/A'}"
// Safe nested access
def config = slurper.parseText('{"db": {}}')
println "Host: ${config.db?.host ?: 'localhost'}"
println "Port: ${config.db?.port ?: 5432}"
Output
JsonException: Unable to determine the current character, i... JsonException: Unable to determine the current character, i... Parsed: [name:Alice] JsonException: Unable to determine the current character, i... IllegalArgumentException: Text must not be null or empty... --- Safe field access --- Name: Alice Age: Not specified Email: N/A Host: localhost Port: 5432
What happened here: Malformed JSON throws JsonException. Always wrap parse calls in try-catch when dealing with external data. For missing fields, use the Elvis operator (?:) for defaults and safe navigation (?.) for nested access. Note that unquoted keys may parse successfully with the default LAX-like parser.
Example 11: Parse JSON with Different Parser Types
What we’re doing: Exploring the different parser types available in JsonSlurper and when to use each one.
Example 11: Parser Types
import groovy.json.JsonSlurper
import groovy.json.JsonParserType
def jsonText = '{"name": "Alice", "scores": [95, 87, 92]}'
// INDEX_OVERLAY - default, best for most cases
def slurper1 = new JsonSlurper().setType(JsonParserType.INDEX_OVERLAY)
def result1 = slurper1.parseText(jsonText)
println "INDEX_OVERLAY: ${result1.name}, scores=${result1.scores}"
// CHARACTER_SOURCE - best for large files from streams
def slurper2 = new JsonSlurper().setType(JsonParserType.CHARACTER_SOURCE)
def result2 = slurper2.parseText(jsonText)
println "CHAR_SOURCE: ${result2.name}, scores=${result2.scores}"
// LAX - allows comments and unquoted keys
def slurper3 = new JsonSlurper().setType(JsonParserType.LAX)
def laxJson = '''
{
// This is a comment (not valid in strict JSON!)
name: "Bob",
age: 25,
'nickname': 'Bobby'
}
'''
def result3 = slurper3.parseText(laxJson)
println "LAX: ${result3.name}, age=${result3.age}, nick=${result3.nickname}"
// CHAR_BUFFER - uses char buffer internally
def slurper4 = new JsonSlurper().setType(JsonParserType.CHAR_BUFFER)
def result4 = slurper4.parseText(jsonText)
println "CHAR_BUFFER: ${result4.name}, scores=${result4.scores}"
Output
INDEX_OVERLAY: Alice, scores=[95, 87, 92] CHAR_SOURCE: Alice, scores=[95, 87, 92] LAX: Bob, age=25, nick=Bobby CHAR_BUFFER: Alice, scores=[95, 87, 92]
What happened here: The LAX parser is the most forgiving – it accepts comments, unquoted keys, and single-quoted strings. INDEX_OVERLAY is the fastest for most use cases. Use CHARACTER_SOURCE when parsing from large streams or readers.
Example 12: Real-World Example – Processing an API Response
What we’re doing: Simulating a real API response and extracting meaningful data from a complex JSON structure.
Example 12: Real-World API Response
import groovy.json.JsonSlurper
def slurper = new JsonSlurper()
def response = slurper.parseText('''
{
"status": "success",
"data": {
"page": 1,
"total_pages": 5,
"per_page": 3,
"total": 15,
"results": [
{
"id": 101,
"title": "Introduction to Groovy",
"author": {"name": "Jane Doe", "email": "jane@example.com"},
"tags": ["groovy", "jvm", "tutorial"],
"published": true,
"views": 15420
},
{
"id": 102,
"title": "Advanced JSON Parsing",
"author": {"name": "John Smith", "email": "john@example.com"},
"tags": ["groovy", "json", "advanced"],
"published": true,
"views": 8930
},
{
"id": 103,
"title": "Draft: Groovy DSLs",
"author": {"name": "Jane Doe", "email": "jane@example.com"},
"tags": ["groovy", "dsl"],
"published": false,
"views": 0
}
]
}
}
''')
// Check response status
if (response.status == 'success') {
def data = response.data
println "Page ${data.page} of ${data.total_pages} (${data.total} total articles)"
println "-" * 50
// Only published articles
def published = data.results.findAll { it.published }
published.each { article ->
println " [#${article.id}] ${article.title}"
println " By: ${article.author.name}"
println " Tags: ${article.tags.join(', ')}"
println " Views: ${article.views}"
println()
}
// Most viewed article
def topArticle = data.results.max { it.views }
println "Most viewed: '${topArticle.title}' (${topArticle.views} views)"
// All unique tags
def allTags = data.results.collectMany { it.tags }.unique().sort()
println "All tags: ${allTags}"
}
Output
Page 1 of 5 (15 total articles)
--------------------------------------------------
[#101] Introduction to Groovy
By: Jane Doe
Tags: groovy, jvm, tutorial
Views: 15420
[#102] Advanced JSON Parsing
By: John Smith
Tags: groovy, json, advanced
Views: 8930
Most viewed: 'Introduction to Groovy' (15420 views)
All tags: [advanced, dsl, groovy, json, jvm, tutorial]
What happened here: This is a realistic scenario – parsing a paginated API response with nested objects, arrays, and metadata. We checked the status, filtered for published articles, extracted nested author names, flattened all tags, and found the most popular article. This is where Groovy JsonSlurper truly shines – doing all of this in a few lines of readable code.
Lazy vs Non-Lazy Parsing
By default, JsonSlurper uses the INDEX_OVERLAY parser, which creates lazy maps. This means values aren’t fully parsed until you actually access them. This is great for performance when you only need a subset of the data, but it can cause unexpected behavior in some cases.
Lazy vs Non-Lazy Parsing
import groovy.json.JsonSlurper
import groovy.json.JsonParserType
def jsonText = '{"name": "Alice", "age": 30, "scores": [95, 87, 92]}'
// Default lazy parsing (INDEX_OVERLAY)
def lazySlurper = new JsonSlurper()
def lazyResult = lazySlurper.parseText(jsonText)
println "Lazy map type: ${lazyResult.getClass().name}"
// Non-lazy parsing (CHARACTER_SOURCE)
def eagerSlurper = new JsonSlurper().setType(JsonParserType.CHARACTER_SOURCE)
def eagerResult = eagerSlurper.parseText(jsonText)
println "Eager map type: ${eagerResult.getClass().name}"
// Both produce identical results for reading
println "Lazy name: ${lazyResult.name}"
println "Eager name: ${eagerResult.name}"
// But lazy maps are NOT thread-safe for the first access
// If sharing parsed data across threads, convert to a regular map:
def safeMap = new HashMap(lazyResult)
println "Safe map type: ${safeMap.getClass().name}"
println "Safe name: ${safeMap.name}"
Output
Lazy map type: org.apache.groovy.json.internal.LazyMap Eager map type: java.util.LinkedHashMap Lazy name: Alice Eager name: Alice Safe map type: java.util.HashMap Safe name: Alice
The bottom line: if you’re parsing JSON in a single thread and reading it immediately, the default lazy parser is fine and faster. If you need to store the result for later use or share it across threads, either use CHARACTER_SOURCE or wrap the result in a new HashMap(lazyResult).
Edge Cases and Best Practices
Edge Case: Duplicate Keys
Duplicate Keys
import groovy.json.JsonSlurper
def slurper = new JsonSlurper()
// JSON with duplicate keys - last value wins
def json = slurper.parseText('{"name": "Alice", "name": "Bob"}')
println "Name: ${json.name}" // Bob - last value wins
// Empty JSON object and array
def emptyObj = slurper.parseText('{}')
def emptyArr = slurper.parseText('[]')
println "Empty obj: ${emptyObj} (size: ${emptyObj.size()})"
println "Empty arr: ${emptyArr} (size: ${emptyArr.size()})"
// Deeply nested JSON (stress test)
def deep = slurper.parseText('{"a":{"b":{"c":{"d":{"e":"deep value"}}}}}')
println "Deep: ${deep.a.b.c.d.e}"
Output
Name: Bob Empty obj: [:] (size: 0) Empty arr: [] (size: 0) Deep: deep value
Best Practices
DO:
- Reuse
JsonSlurperinstances – they are thread-safe - Use
?.(safe navigation) and?:(Elvis operator) for optional fields - Wrap external JSON parsing in try-catch blocks
- Use
LAXparser type when dealing with non-standard JSON (e.g., JSON with comments) - Convert lazy maps to regular maps when storing results long-term
DON’T:
- Assume all JSON numbers are
Integer– large numbers becomeLongorBigDecimal - Share lazy-parsed results across threads without converting first
- Catch
ExceptionwhenJsonExceptionis more specific - Parse very large JSON files (100MB+) in one go – use a streaming parser instead
Common Pitfalls
Pitfall 1: Lazy Map Mutation
Problem: Trying to modify a lazy map and getting unexpected results.
Pitfall 1: Lazy Map Issues
import groovy.json.JsonSlurper
def slurper = new JsonSlurper()
def json = slurper.parseText('{"name": "Alice", "age": 30}')
// This works - but the result is a LazyMap
json.email = "alice@example.com"
println json // May have unexpected ordering
// Fix: convert to a regular map first
def map = new LinkedHashMap(slurper.parseText('{"name": "Bob", "age": 25}'))
map.email = "bob@example.com"
println map
Output
[name:Alice, age:30, email:alice@example.com] [name:Bob, age:25, email:bob@example.com]
Solution: If you plan to modify parsed JSON, wrap the result in a new LinkedHashMap() or new HashMap() first to get a standard mutable map.
Pitfall 2: Number Type Assumptions
Problem: Assuming all JSON integers parse to int.
Pitfall 2: Number Types
import groovy.json.JsonSlurper
def slurper = new JsonSlurper()
def json = slurper.parseText('''
{
"small": 42,
"big": 9999999999999,
"decimal": 19.99,
"scientific": 1.5e10
}
''')
println "${json.small} -> ${json.small.getClass().name}"
println "${json.big} -> ${json.big.getClass().name}"
println "${json.decimal} -> ${json.decimal.getClass().name}"
println "${json.scientific} -> ${json.scientific.getClass().name}"
// This can cause issues if you explicitly cast
// int x = json.big // This would overflow!
long safe = json.big as long
println "Safe long: ${safe}"
Output
42 -> java.lang.Integer 9999999999999 -> java.lang.Long 19.99 -> java.math.BigDecimal 1.5E+10 -> java.math.BigDecimal Safe long: 9999999999999
Solution: Never assume the numeric type. Use as long, as BigDecimal, or check the type with instanceof if type precision matters.
Pitfall 3: Null vs Missing Key
Problem: Not distinguishing between a field that is explicitly null and a field that is missing.
Pitfall 3: Null vs Missing
import groovy.json.JsonSlurper
def slurper = new JsonSlurper()
def json = slurper.parseText('{"name": "Alice", "email": null}')
// Both return null - but for different reasons!
println "email: ${json.email}" // null - field exists but value is null
println "phone: ${json.phone}" // null - field does not exist
// How to distinguish:
println "has email? ${json.containsKey('email')}" // true
println "has phone? ${json.containsKey('phone')}" // false
Output
email: null phone: null has email? true has phone? false
Solution: Use containsKey() to check whether a field exists in the parsed JSON, even if its value is null.
Conclusion
Groovy JSON parsing with JsonSlurper is one of the cleanest JSON parsing experiences on the JVM. No annotations, no POJOs, no configuration – just parse and go. It handles config files, API responses, and data pipelines equally well – JsonSlurper turns JSON into native Groovy data structures that you can slice, dice, filter, and transform with all the collection methods you already know.
The key is understanding the type mappings (objects become maps, arrays become lists, numbers become Integer/Long/BigDecimal), knowing when to use the LAX parser for non-standard JSON, and handling lazy maps correctly in multithreaded scenarios.
Now that you can parse JSON, the next step is generating it. Head over to our Groovy JSON Output with JsonBuilder guide to learn how to build JSON from Groovy objects, maps, and custom structures. And for end-to-end API work, check out Groovy REST API Consumption.
Summary
JsonSlurperis built into Groovy – no external dependencies needed- JSON objects parse to maps, JSON arrays parse to lists – use dot notation and indexing
- Use
parseText()for strings,parse(File)for files,parse(URL)for remote JSON - Numbers map to
Integer,Long, orBigDecimal– never assumeint - Use
LAXparser for non-standard JSON with comments or unquoted keys - Convert lazy maps to regular maps when sharing across threads or storing long-term
If you also work with build tools, CI/CD pipelines, or cloud CLIs, check out Command Playground to practice 105+ CLI tools directly in your browser — no install needed.
Up next: Groovy JSON Output with JsonBuilder
Frequently Asked Questions
What is JsonSlurper in Groovy?
JsonSlurper is Groovy’s built-in JSON parser from the groovy.json package. It parses JSON strings, files, URLs, and streams into native Groovy data structures – maps for JSON objects and lists for JSON arrays. No external libraries are needed, and you access parsed fields using dot notation or bracket notation.
How do I parse a JSON string in Groovy?
Create a JsonSlurper instance and call parseText(): def json = new JsonSlurper().parseText('{"name": "Alice"}') . Then access fields with json.name. You can also parse files with parse(File), URLs with parse(URL), and streams with parse(InputStream).
What types does JsonSlurper return for JSON numbers?
JsonSlurper maps JSON integers to java.lang.Integer for small values and java.lang.Long for larger values. Decimal numbers become java.math.BigDecimal, which provides exact precision. Never assume all JSON numbers will be int – always check or cast explicitly when type precision matters.
What is the difference between lazy and non-lazy JSON parsing in Groovy?
The default INDEX_OVERLAY parser creates lazy maps (LazyMap) where values aren’t fully materialized until accessed. This is faster when you only need a subset of the data. The CHARACTER_SOURCE parser creates regular LinkedHashMap instances eagerly. Use eager parsing when sharing results across threads or storing them long-term.
How do I handle malformed JSON in Groovy?
Wrap your parseText() or parse() call in a try-catch block catching groovy.json.JsonException. For missing fields, use the Elvis operator (json.field ?: 'default') and safe navigation (json?.nested?.field). Use containsKey() to distinguish between a field that is explicitly null and a field that is missing entirely.
Related Posts
Previous in Series: Groovy Builder Pattern Annotation
Next in Series: Groovy JSON Output with JsonBuilder
Related Topics You Might Like:
This post is part of the Groovy & Grails Cookbook series on TechnoScripts.com

No comment