Building Gatling Feeders with Nested JSON Templates: Best Practices and Examples
Nested JSON can look like a bowl of spaghetti. Gatling feeders can make it neat, fast, and reusable. The trick is to feed clean data, build the nested body in one safe place, and keep your scenario readable.
TLDR: Use feeders to supply test data, then render nested JSON with a small template or a session function. Do not paste giant JSON strings into every request. For example, one shopping API test can feed 10,000 users, generate 3 to 7 cart items per user, and still keep the request body under control. In one team setup, moving from hardcoded bodies to JSON feeders cut test maintenance time by about 40%.
What Is a Gatling Feeder?
A feeder is a data source for a Gatling test. It pushes values into the virtual user session. Then your HTTP requests can use those values.
Think of it like a snack machine for your test. Each virtual user walks up. The feeder gives it a user ID, a token, a product, or even a whole object. The user eats the data and sends a request. Delicious.
- CSV feeders are great for simple rows.
- JSON feeders are great for structured data.
- Custom feeders are best when data must be generated on the fly.
Nested JSON means objects inside objects. Or arrays inside objects. Or arrays inside arrays. You know, the usual API party.
Image not found in postmetaA Simple Nested JSON Use Case
Imagine an online shop. You want to test this endpoint:
POST /api/orders
The body might look like this:
{
"customer": {
"id": "u123",
"email": "sam@example.com"
},
"cart": {
"currency": "USD",
"items": [
{
"sku": "book-1",
"quantity": 2
}
]
},
"metadata": {
"source": "load-test"
}
}
This is not scary. It is just a box, with smaller boxes inside. Gatling can build this body for every user.
Best Practice 1: Keep Flat Data Flat
Start simple. If your data fits in a CSV file, use CSV. Do not turn every tiny thing into a custom monster.
userId,email,currency
u001,ana@example.com,USD
u002,ben@example.com,EUR
u003,mia@example.com,GBP
Then feed it:
val users = csv("users.csv").circular
Why circular? Because load tests often need more virtual users than rows. With circular, Gatling starts again from the first row. It is like a buffet that never closes.
You can place simple values into a JSON template:
StringBody(
"""
{
"customer": {
"id": "#{userId}",
"email": "#{email}"
},
"cart": {
"currency": "#{currency}",
"items": []
}
}
"""
)
This is fine for basic values. It is readable. It is friendly. It is not trying to win an award.
Best Practice 2: Do Not Fake Arrays With Commas
Arrays are where many tests go kaboom. People try to build JSON like this:
"items": [#{items}]
That can work. But only if items is already valid JSON. If it is not, you get broken bodies. The server gets confused. You get sad. The logs laugh quietly.
A better way is to create the array as real data, then serialize it into JSON.
val orderFeeder = Iterator.continually {
val items = List(
Map("sku" -> "book-1", "quantity" -> 1),
Map("sku" -> "pen-2", "quantity" -> 3)
)
Map(
"userId" -> "u123",
"email" -> "sam@example.com",
"currency" -> "USD",
"itemsJson" -> itemsToJson(items)
)
}
Then your body can insert itemsJson safely:
StringBody(
"""
{
"customer": {
"id": "#{userId}",
"email": "#{email}"
},
"cart": {
"currency": "#{currency}",
"items": #{itemsJson}
}
}
"""
)
Notice one important thing. There are no quotes around #{itemsJson}. It is already JSON. If you add quotes, it becomes a string. That is not what your API wants.
Best Practice 3: Use a Renderer for Complex Bodies
When your JSON gets big, templates can become wobbly. A missing brace ruins the show. A bad comma can steal your afternoon.
For complex nested JSON, build the body in code. Use a JSON library if possible. Then return a valid JSON string.
def buildOrderBody(session: Session): String = {
val userId = session("userId").as[String]
val email = session("email").as[String]
val currency = session("currency").as[String]
s"""
{
"customer": {
"id": "$userId",
"email": "$email"
},
"cart": {
"currency": "$currency",
"items": [
{ "sku": "book-1", "quantity": 2 },
{ "sku": "bag-9", "quantity": 1 }
]
},
"metadata": {
"source": "gatling"
}
}
"""
}
Then call it:
http("create order")
.post("/api/orders")
.body(StringBody(session => buildOrderBody(session)))
.asJson
This keeps the scenario clean. It also makes your body builder easy to test. Yes, test your test helpers. Future you will clap.
Best Practice 4: Separate Data From Shape
Data is the what. Template is the how. Keep them apart.
Your feeder should answer questions like:
- Who is the customer?
- How many items are in the cart?
- Which currency is used?
- Which coupon is applied?
Your JSON template should answer one question:
- What does the API body look like?
This split makes tests easier to change. If the API adds a new field, change the template. If you need more users, change the feeder. No drama. No duct tape.
Best Practice 5: Randomize With Limits
Random data is fun. Too much random data is chaos wearing sunglasses.
Use controlled random values. For example:
- Pick 1 to 5 items per cart.
- Use only valid SKUs from your test catalog.
- Use realistic quantities, like 1 to 3.
- Keep invalid data in separate negative tests.
A custom feeder can do this well:
val skus = Vector("book-1", "pen-2", "bag-9", "cup-4")
val cartFeeder = Iterator.continually {
val itemCount = scala.util.Random.between(1, 6)
val items = (1 to itemCount).map { _ =>
Map(
"sku" -> skus(scala.util.Random.nextInt(skus.size)),
"quantity" -> scala.util.Random.between(1, 4)
)
}.toList
Map(
"cartSize" -> itemCount,
"itemsJson" -> itemsToJson(items)
)
}
Now every order feels different. But it still feels real. That is the sweet spot.
Best Practice 6: Validate the Body Before the Big Run
Never start a 60-minute load test with untested JSON. That is like launching a rocket and then asking if fuel was included.
Before running at scale, do a tiny run:
- Run 1 user.
- Print or log one body.
- Paste it into a JSON validator.
- Check that arrays are arrays.
- Check that numbers are numbers.
- Check that strings are quoted.
Also add response checks:
.check(status.is(201))
.check(jsonPath("$.orderId").exists)
This proves the API accepted your nested body. It also catches silent failures early.
Common Mistakes
- Quoting JSON fragments: Use
"name": "#{name}"for strings, but use"items": #{itemsJson}for JSON arrays. - Putting everything in CSV: CSV is not great for deep objects. Use JSON files or generated feeders instead.
- Using wild random data: Random emails are fine. Random impossible products are not.
- Sharing mutable objects: Each virtual user should receive safe data. Avoid changing shared maps or lists.
- Ignoring escaping: Names like
O'Brianor quotes in text can break naive JSON strings.
A Clean Mental Model
Use this simple flow:
- Feeder gives values to the session.
- Builder or template creates the nested JSON.
- HTTP request sends the body.
- Checks prove the response is correct.
That is the whole loop. Feed. Build. Send. Check. Repeat until your system sweats politely.
Final Tips
- Keep templates small when possible.
- Move big JSON builders into helper functions.
- Name feeder fields clearly, like
customerIdanditemsJson. - Use realistic data volumes.
- Test one request before testing one thousand.
Building Gatling feeders with nested JSON templates does not need to be painful. Treat data like ingredients. Treat templates like recipes. Then let Gatling cook thousands of API meals per second. Bon appétit, load tester.