Question
How can I parse this JSON file in PHP and loop through its unknown top-level names and nested fields?
{
"John": {
"status": "Wait"
},
"Jennifer": {
"status": "Active"
},
"James": {
"status": "Active",
"age": 56,
"count": 10,
"progress": 0.0029857,
"bad": 0
}
}
I can access known values like this:
<?php
$string = file_get_contents('/home/michael/test.json');
$json_a = json_decode($string, true);
echo $json_a['John']['status'];
echo $json_a['Jennifer']['status'];
However, I do not know the names (John, Jennifer, and so on) or every nested key (age, count, and so on) in advance. How can I use foreach to iterate through all names, keys, and values?
Short Answer
You will learn how PHP represents decoded JSON, how to choose arrays or objects with json_decode(), and how nested foreach loops handle JSON with dynamic keys. You will also learn how to safely read optional fields such as age when they may not exist for every record.
Concept
JSON objects contain named properties. In the example, the outer JSON object has dynamic property names such as John, Jennifer, and James. Each name points to another JSON object containing details.
When you call json_decode($json, true), PHP converts JSON objects into associative arrays:
$data = json_decode($json, true);
The resulting shape is conceptually:
[
'John' => ['status' => 'Wait'],
'Jennifer' => ['status' => 'Active'],
'James' => [
'status' => 'Active',
'age' => 56,
'count' => 10,
'progress' => 0.0029857,
'bad' => 0,
],
]
An associative array stores values under keys, rather than only numeric positions. foreach is useful because it gives you both the current key and its value without knowing the keys beforehand.
This matters in real programs because API responses, configuration files, imported data, and user-generated JSON often contain data whose keys or optional fields cannot be hard-coded reliably.
Mental Model
Think of the decoded JSON as a filing cabinet.
- The outer cabinet has drawers labelled
John,Jennifer, andJames. - Each drawer contains a folder of labelled facts, such as
status,age, andcount. - You do not need to know every drawer label first:
foreachopens each drawer in turn. - A second
foreachreads every labelled fact inside that drawer.
The variable before => receives the label (key), and the variable after => receives what is stored under that label (value).
Syntax and Examples
Use json_decode() with true when you want associative arrays, then use nested foreach loops.
<?php
$json = file_get_contents('/home/michael/test.json');
$people = json_decode($json, true, 512, JSON_THROW_ON_ERROR);
foreach ($people as $name => $details) {
echo "$name\n";
foreach ($details as $field => $value) {
echo " $field: $value\n";
}
}
Possible output:
John
status: Wait
Jennifer
status: Active
James
status: Active
age: 56
count: 10
progress: 0.0029857
bad: 0
The outer loop assigns:
$name: a dynamic top-level key, such asJames$details: that person's nested associative array
The inner loop assigns:
$field: a nested key, such asprogress$value: the corresponding value, such as0.0029857
If you only need one known nested field, such as status, use a single loop:
foreach ($people as $name => $details) {
$status = $details['status'] ?? 'Unknown';
echo "$name: $status\n";
}
The ?? operator provides a fallback if a record does not contain status.
Step by Step Execution
Consider this smaller JSON document:
$json = '{
"John": {"status": "Wait"},
"James": {"status": "Active", "age": 56}
}';
$people = json_decode($json, true, 512, JSON_THROW_ON_ERROR);
foreach ($people as $name => $details) {
echo "$name\n";
foreach ($details as $field => $value) {
echo "$field = $value\n";
}
}
Execution trace:
json_decode(..., true)converts the outer JSON object into an associative array.- On the first outer-loop iteration,
$nameisJohnand$detailsis['status' => 'Wait']. - The inner loop runs once:
$fieldisstatusand$valueisWait. - On the second outer-loop iteration,
$nameisJamesand$detailsis['status' => 'Active', 'age' => 56]. - The inner loop runs twice, once for
statusand once forage. - The loops finish after every top-level key and every nested field has been visited.
Real World Use Cases
Dynamic JSON keys appear in many practical situations:
- Configuration files: Feature flags may be grouped by an unknown environment or service name.
- API responses: A reporting API may return account IDs as object keys, with metrics inside each account.
- Import scripts: A JSON export may use product SKUs, usernames, or dates as keys.
- Monitoring data: Server names can be keys, while CPU, memory, and status values are nested fields.
- Translation files: Language codes such as
en,fr, anddecan map to nested message collections.
For example, an API might return server health by host name:
foreach ($servers as $hostname => $metrics) {
$cpu = $metrics['cpu'] ?? null;
echo "$hostname uses $cpu% CPU\n";
}
Real Codebase Usage
In production PHP code, developers usually combine iteration with validation and safe access.
Validate file and JSON input
<?php
$path = '/home/michael/test.json';
if (!is_readable($path)) {
throw new RuntimeException("Cannot read JSON file: $path");
}
$contents = file_get_contents($path);
$people = json_decode($contents, true, 512, JSON_THROW_ON_ERROR);
if (!is_array($people)) {
throw new UnexpectedValueException('Expected the JSON root to be an object.');
}
JSON_THROW_ON_ERROR makes invalid JSON fail immediately with a JsonException, instead of silently returning null.
Use guard clauses for unexpected records
External data can be incomplete or malformed. Skip entries that do not have the expected array structure.
foreach ($people as $name => $details) {
if (!is_array($details)) {
continue;
}
$status = $details['status'] ?? 'Unknown';
echo "$name: $status\n";
}
Transform records into a predictable list
Dynamic-key JSON is sometimes awkward for later processing. A common pattern is to make the key into a regular field.
$rows = [];
foreach ($people as $name => $details) {
if (!is_array($details)) {
continue;
}
$rows[] = [
'name' => $name,
'status' => $details['status'] ?? 'Unknown',
'age' => $details['age'] ?? null,
];
}
Now every item has the same top-level shape, which is convenient for templates, database inserts, filtering, and tests.
Common Mistakes
Leaving array keys unquoted
This is incorrect:
// Broken: status is treated as a constant in older PHP versions.
echo $json_a['John'][status];
Use quotes for string keys:
echo $json_a['John']['status'];
Assuming every field exists
John does not have an age field. This can produce an undefined array key warning:
// Broken for people without an age.
echo $details['age'];
Use a fallback or check for the key:
echo $details['age'] ?? 'Not provided';
Using object syntax after decoding into arrays
With the second argument set to true, the result uses arrays. This is incorrect:
// Broken: $details is an array, not an object.
echo $details->status;
Use array access:
echo $details['status'];
Forgetting that JSON decoding can fail
Invalid JSON returns null by default, which can lead to later errors. Prefer:
$people = json_decode($json, true, 512, JSON_THROW_ON_ERROR);
Handle JsonException at the boundary of your application if invalid user or remote input is possible.
Confusing a JSON object with a JSON array
Your example begins with {}, so it is a JSON object and its values have named keys. A JSON array begins with [] and usually has numeric indexes after decoding.
Comparisons
| Approach | json_decode() call | Access syntax | Best when |
|---|---|---|---|
| Associative arrays | json_decode($json, true) | $person['status'] | You want array functions and clear dynamic-key iteration. |
| PHP objects | json_decode($json) | $person->status | Property names are known and valid PHP property names. |
Nested foreach | One loop per nesting level | foreach ($items as $key => $value) | You need every dynamic key and nested value. |
| Direct access | $people['John']['status'] | Known keys only | The specific key is guaranteed and intentionally targeted. |
When decoding as objects, iteration is still possible:
$people = json_decode($json, false, 512, JSON_THROW_ON_ERROR);
foreach ($people as $name => $details) {
echo $name . ': ' . $details->status . "\n";
}
For this type of data, associative arrays are often simpler because fields may be optional and array access works naturally with ??.
Cheat Sheet
// Decode a JSON object into associative arrays
$data = json_decode($json, true, 512, JSON_THROW_ON_ERROR);
// Loop through dynamic top-level keys
foreach ($data as $key => $value) {
// $key: for example, "James"
// $value: nested array
}
// Loop through nested dynamic keys
foreach ($data as $name => $details) {
foreach ($details as $field => $value) {
echo "$name - $field: $value\n";
}
}
// Safely read an optional field
$status = $details['status'] ?? 'Unknown';
// Check a field explicitly
if (array_key_exists('age', $details)) {
echo $details['age'];
}
Rules to remember:
- Use
trueinjson_decode()for associative arrays. - Quote string array keys:
['status'], not[status]. - Use
=>inforeachwhen you need both key and value. - Use
??for fields that may be missing. - Use
JSON_THROW_ON_ERRORwhen decoding data you need to trust.
FAQ
How do I loop through unknown JSON keys in PHP?
Decode the JSON into an associative array and use foreach ($data as $key => $value). The $key variable receives each unknown property name.
Why does json_decode($json, true) use square brackets?
The true argument tells PHP to convert JSON objects to associative arrays. PHP arrays use square-bracket access, such as $person['status'].
How do I access a nested JSON value in PHP?
For known keys, use nested array access: $data['James']['status']. For unknown keys, iterate with nested foreach loops.
What happens if a JSON field is missing?
Direct access can raise an undefined array key warning. Use $details['age'] ?? null or another appropriate default value.
Should I use json_last_error() or JSON_THROW_ON_ERROR?
For modern PHP, JSON_THROW_ON_ERROR is usually clearer because malformed JSON raises a JsonException at the decoding line.
Can I decode JSON into objects instead of arrays?
Yes. Omit the true argument and use object access such as $person->status. Do not mix object access with array access.
Does foreach preserve the names from the JSON file?
Yes. For a JSON object decoded into an associative array, foreach ($data as $name => $details) gives each original property name in $name.
Mini Project
Description
Build a small PHP command-line report that reads a people-status JSON file. The file uses unknown names as keys, and each person may contain different fields. The report demonstrates decoding JSON, iterating dynamic keys, and safely reading optional values.
Goal
Print one readable summary line for every person and calculate how many people have an Active status.
Requirements
- Read JSON from a file named
people.json. - Decode the JSON as associative arrays.
- Print each person's name and status.
- Print age when it is available.
- Treat a missing status as
Unknown. - Display the total number of active people.
Keep learning
Related questions
Are PDO Prepared Statements Enough to Prevent SQL Injection in PHP?
Learn how PDO prepared statements prevent SQL injection in PHP, what they protect, and the mistakes that still leave MySQL apps vulnerable.
Can You Bind an Array to an IN Clause in PHP PDO?
Learn how PDO handles placeholders in IN() clauses, why arrays cannot be bound directly, and the safe PHP pattern to build dynamic queries.
Choosing the Right MySQL Collation for PHP and UTF-8
Learn how MySQL character sets and collations work with PHP, and how to choose a practical UTF-8 setup for web applications.